@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.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/package.json +104 -0
- package/src/LexicalAutoEmbedPlugin.tsrx +144 -0
- package/src/LexicalAutoFocusPlugin.tsrx +25 -0
- package/src/LexicalAutoLinkPlugin.tsrx +32 -0
- package/src/LexicalBlockWithAlignableContents.tsrx +85 -0
- package/src/LexicalCharacterLimitPlugin.tsrx +66 -0
- package/src/LexicalCheckListPlugin.tsrx +16 -0
- package/src/LexicalClearEditorPlugin.tsrx +12 -0
- package/src/LexicalClickableLinkPlugin.tsrx +17 -0
- package/src/LexicalCollaborationContext.ts +67 -0
- package/src/LexicalComposer.tsrx +100 -0
- package/src/LexicalComposerContext.ts +49 -0
- package/src/LexicalContentEditable.tsrx +48 -0
- package/src/LexicalDecoratorBlockNode.ts +78 -0
- package/src/LexicalDraggableBlockPlugin.tsrx +499 -0
- package/src/LexicalEditorRefPlugin.tsrx +19 -0
- package/src/LexicalErrorBoundary.tsrx +23 -0
- package/src/LexicalHashtagPlugin.tsrx +17 -0
- package/src/LexicalHistoryPlugin.tsrx +23 -0
- package/src/LexicalHorizontalRuleNode.tsrx +99 -0
- package/src/LexicalHorizontalRulePlugin.tsrx +34 -0
- package/src/LexicalLinkPlugin.tsrx +27 -0
- package/src/LexicalListPlugin.tsrx +37 -0
- package/src/LexicalMarkdownShortcutPlugin.tsrx +39 -0
- package/src/LexicalNestedComposer.tsrx +124 -0
- package/src/LexicalNodeContextMenuPlugin.tsrx +252 -0
- package/src/LexicalNodeEventPlugin.tsrx +48 -0
- package/src/LexicalNodeMenuPlugin.tsrx +84 -0
- package/src/LexicalOnChangePlugin.tsrx +36 -0
- package/src/LexicalPlainTextPlugin.tsrx +33 -0
- package/src/LexicalRichTextPlugin.tsrx +35 -0
- package/src/LexicalSelectionAlwaysOnDisplay.tsrx +11 -0
- package/src/LexicalTabIndentationPlugin.tsrx +21 -0
- package/src/LexicalTableOfContentsPlugin.tsrx +213 -0
- package/src/LexicalTablePlugin.tsrx +67 -0
- package/src/LexicalTypeaheadMenuPlugin.tsrx +141 -0
- package/src/autoEmbedShared.ts +43 -0
- package/src/index.ts +99 -0
- package/src/shared/LexicalContentEditableElement.tsrx +101 -0
- package/src/shared/LexicalDecorators.tsrx +88 -0
- package/src/shared/LexicalMenu.tsrx +275 -0
- package/src/shared/internal.ts +32 -0
- package/src/shared/menuShared.ts +152 -0
- package/src/shared/point.ts +46 -0
- package/src/shared/rect.ts +136 -0
- package/src/shared/useCanShowPlaceholder.ts +38 -0
- package/src/shared/useCharacterLimit.ts +288 -0
- package/src/shared/useDynamicPositioning.ts +72 -0
- package/src/shared/useMenuAnchorRef.ts +131 -0
- package/src/shared/usePlainTextSetup.ts +15 -0
- package/src/shared/useRichTextSetup.ts +16 -0
- package/src/tsrx-modules.d.ts +4 -0
- package/src/typeaheadShared.ts +132 -0
- package/src/types.ts +29 -0
- package/src/useLexicalEditable.ts +22 -0
- package/src/useLexicalIsTextContentEmpty.ts +35 -0
- package/src/useLexicalNodeSelection.ts +91 -0
- package/src/useLexicalSlotRef.ts +26 -0
- package/src/useLexicalSubscription.ts +55 -0
- package/src/useLexicalTextEntity.ts +24 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { LexicalEditor, RangeSelection } from 'lexical';
|
|
2
|
+
import type { MenuTextMatch, TriggerFn } from './shared/menuShared';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
$getSelection,
|
|
6
|
+
$isRangeSelection,
|
|
7
|
+
$isTextNode,
|
|
8
|
+
getDOMSelection,
|
|
9
|
+
getDOMSelectionPoints,
|
|
10
|
+
} from 'lexical';
|
|
11
|
+
import { useCallback } from 'octane';
|
|
12
|
+
|
|
13
|
+
// Non-component helpers + the trigger hook from LexicalTypeaheadMenuPlugin.tsx.
|
|
14
|
+
|
|
15
|
+
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
16
|
+
|
|
17
|
+
export function getTextUpToAnchor(selection: RangeSelection): string | null {
|
|
18
|
+
const anchor = selection.anchor;
|
|
19
|
+
if (anchor.type !== 'text') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const anchorNode = anchor.getNode();
|
|
23
|
+
if (!anchorNode.isSimpleText()) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const anchorOffset = anchor.offset;
|
|
27
|
+
return anchorNode.getTextContent().slice(0, anchorOffset);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function tryToPositionRange(
|
|
31
|
+
leadOffset: number,
|
|
32
|
+
range: Range,
|
|
33
|
+
editorWindow: Window,
|
|
34
|
+
rootElement: HTMLElement | null,
|
|
35
|
+
): boolean {
|
|
36
|
+
const domSelection = getDOMSelection(editorWindow);
|
|
37
|
+
if (domSelection === null || !domSelection.isCollapsed) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const points = getDOMSelectionPoints(domSelection, rootElement);
|
|
41
|
+
const anchorNode = points.anchorNode;
|
|
42
|
+
const startOffset = leadOffset;
|
|
43
|
+
const endOffset = points.anchorOffset;
|
|
44
|
+
if (anchorNode == null || endOffset == null) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
range.setStart(anchorNode, startOffset);
|
|
49
|
+
range.setEnd(anchorNode, endOffset);
|
|
50
|
+
} catch (_error) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getQueryTextForSearch(editor: LexicalEditor): string | null {
|
|
57
|
+
let text = null;
|
|
58
|
+
editor.read('latest', () => {
|
|
59
|
+
const selection = $getSelection();
|
|
60
|
+
if (!$isRangeSelection(selection)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
text = getTextUpToAnchor(selection);
|
|
64
|
+
});
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isSelectionOnEntityBoundary(editor: LexicalEditor, offset: number): boolean {
|
|
69
|
+
if (offset !== 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return editor.read('latest', () => {
|
|
73
|
+
const selection = $getSelection();
|
|
74
|
+
if ($isRangeSelection(selection)) {
|
|
75
|
+
const anchor = selection.anchor;
|
|
76
|
+
const anchorNode = anchor.getNode();
|
|
77
|
+
const prevSibling = anchorNode.getPreviousSibling();
|
|
78
|
+
return $isTextNode(prevSibling) && prevSibling.isTextEntity();
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Two required user args (trigger, options), so the trailing slot is positional.
|
|
85
|
+
export function useBasicTypeaheadTriggerMatch(
|
|
86
|
+
trigger: string,
|
|
87
|
+
options: {
|
|
88
|
+
minLength?: number;
|
|
89
|
+
maxLength?: number;
|
|
90
|
+
punctuation?: string;
|
|
91
|
+
allowWhitespace?: boolean;
|
|
92
|
+
},
|
|
93
|
+
slot?: symbol,
|
|
94
|
+
): TriggerFn {
|
|
95
|
+
const minLength = options.minLength ?? 1;
|
|
96
|
+
const maxLength = options.maxLength ?? 75;
|
|
97
|
+
const punctuation = options.punctuation ?? PUNCTUATION;
|
|
98
|
+
const allowWhitespace = options.allowWhitespace ?? false;
|
|
99
|
+
return useCallback(
|
|
100
|
+
(text: string) => {
|
|
101
|
+
const validCharsSuffix = allowWhitespace ? '' : '\\s';
|
|
102
|
+
const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
|
|
103
|
+
const TypeaheadTriggerRegex = new RegExp(
|
|
104
|
+
'(^|\\s|\\()(' +
|
|
105
|
+
'[' +
|
|
106
|
+
trigger +
|
|
107
|
+
']' +
|
|
108
|
+
'((?:' +
|
|
109
|
+
validChars +
|
|
110
|
+
'){0,' +
|
|
111
|
+
maxLength +
|
|
112
|
+
'})' +
|
|
113
|
+
')$',
|
|
114
|
+
);
|
|
115
|
+
const match = TypeaheadTriggerRegex.exec(text);
|
|
116
|
+
if (match !== null) {
|
|
117
|
+
const maybeLeadingWhitespace = match[1];
|
|
118
|
+
const matchingString = match[3];
|
|
119
|
+
if (matchingString.length >= minLength) {
|
|
120
|
+
return {
|
|
121
|
+
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
122
|
+
matchingString,
|
|
123
|
+
replaceableString: match[2],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
[allowWhitespace, trigger, punctuation, maxLength, minLength],
|
|
130
|
+
slot,
|
|
131
|
+
);
|
|
132
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EditorState,
|
|
3
|
+
EditorThemeClasses,
|
|
4
|
+
HTMLConfig,
|
|
5
|
+
Klass,
|
|
6
|
+
LexicalEditor,
|
|
7
|
+
LexicalNode,
|
|
8
|
+
LexicalNodeReplacement,
|
|
9
|
+
} from 'lexical';
|
|
10
|
+
|
|
11
|
+
// Real types for LexicalComposer's config (its component lives in a `.tsrx`, which
|
|
12
|
+
// tsgo treats as `any` via the ambient module — so the typed surface lives here).
|
|
13
|
+
|
|
14
|
+
export type InitialEditorStateType =
|
|
15
|
+
| null
|
|
16
|
+
| string
|
|
17
|
+
| EditorState
|
|
18
|
+
| ((editor: LexicalEditor) => void);
|
|
19
|
+
|
|
20
|
+
export type InitialConfigType = Readonly<{
|
|
21
|
+
namespace: string;
|
|
22
|
+
nodes?: readonly (Klass<LexicalNode> | LexicalNodeReplacement)[];
|
|
23
|
+
onError: (error: Error, editor: LexicalEditor) => void;
|
|
24
|
+
onWarn?: (error: Error, editor: LexicalEditor) => void;
|
|
25
|
+
editable?: boolean;
|
|
26
|
+
theme?: EditorThemeClasses;
|
|
27
|
+
editorState?: InitialEditorStateType;
|
|
28
|
+
html?: HTMLConfig;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
|
|
3
|
+
import { useLexicalSubscription, type LexicalSubscription } from './useLexicalSubscription';
|
|
4
|
+
|
|
5
|
+
// Ported from @lexical/react/src/useLexicalEditable.ts. Composes exactly one base
|
|
6
|
+
// hook (useLexicalSubscription), so it forwards the caller's slot straight through.
|
|
7
|
+
|
|
8
|
+
function subscription(editor: LexicalEditor): LexicalSubscription<boolean> {
|
|
9
|
+
return {
|
|
10
|
+
initialValueFn: () => editor.isEditable(),
|
|
11
|
+
subscribe: (callback) => editor.registerEditableListener(callback),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the current value for {@link LexicalEditor.isEditable} via
|
|
17
|
+
* {@link useLexicalSubscription}. Prefer this over observing
|
|
18
|
+
* {@link LexicalEditor.registerEditableListener} manually.
|
|
19
|
+
*/
|
|
20
|
+
export function useLexicalEditable(slot?: symbol): boolean {
|
|
21
|
+
return useLexicalSubscription(subscription, slot);
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
|
|
3
|
+
import { $isRootTextContentEmptyCurry } from '@lexical/text';
|
|
4
|
+
import { useState, useLayoutEffect } from 'octane';
|
|
5
|
+
|
|
6
|
+
import { splitSlot, subSlot } from './shared/internal';
|
|
7
|
+
|
|
8
|
+
// Ported from @lexical/react/src/useLexicalIsTextContentEmpty.ts. `trim` is an
|
|
9
|
+
// OPTIONAL user arg, so the trailing compiler-injected slot is found with
|
|
10
|
+
// splitSlot (positional resolution would mistake the slot for `trim`). Public
|
|
11
|
+
// signature is `(editor: LexicalEditor, trim?: boolean)`.
|
|
12
|
+
export function useLexicalIsTextContentEmpty(...args: any[]): boolean {
|
|
13
|
+
const [user, slot] = splitSlot(args);
|
|
14
|
+
const editor = user[0] as LexicalEditor;
|
|
15
|
+
const trim = user[1] as boolean | undefined;
|
|
16
|
+
|
|
17
|
+
const [isEmpty, setIsEmpty] = useState(
|
|
18
|
+
editor.read('latest', $isRootTextContentEmptyCurry(editor.isComposing(), trim)),
|
|
19
|
+
subSlot(slot, 'ultce:state'),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
useLayoutEffect(
|
|
23
|
+
() => {
|
|
24
|
+
return editor.registerUpdateListener(({ editorState }) => {
|
|
25
|
+
const isComposing = editor.isComposing();
|
|
26
|
+
const currentIsEmpty = editorState.read($isRootTextContentEmptyCurry(isComposing, trim));
|
|
27
|
+
setIsEmpty(currentIsEmpty);
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
[editor, trim],
|
|
31
|
+
subSlot(slot, 'ultce:le'),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return isEmpty;
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { LexicalEditor, NodeKey } from 'lexical';
|
|
2
|
+
|
|
3
|
+
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
4
|
+
import {
|
|
5
|
+
$createNodeSelection,
|
|
6
|
+
$getNodeByKey,
|
|
7
|
+
$getSelection,
|
|
8
|
+
$isNodeSelection,
|
|
9
|
+
$setSelection,
|
|
10
|
+
} from 'lexical';
|
|
11
|
+
import { useCallback, useEffect, useState } from 'octane';
|
|
12
|
+
|
|
13
|
+
import { subSlot } from './shared/internal';
|
|
14
|
+
|
|
15
|
+
// Ported from @lexical/react/src/useLexicalNodeSelection.ts. Plain `.ts` hook;
|
|
16
|
+
// composes four base hooks, each given a distinct sub-slot.
|
|
17
|
+
|
|
18
|
+
function isNodeSelected(editor: LexicalEditor, key: NodeKey): boolean {
|
|
19
|
+
return editor.read('latest', () => {
|
|
20
|
+
const node = $getNodeByKey(key);
|
|
21
|
+
if (node === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return node.isSelected();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useLexicalNodeSelection(
|
|
29
|
+
key: NodeKey,
|
|
30
|
+
slot?: symbol,
|
|
31
|
+
): [boolean, (selected: boolean) => void, () => void] {
|
|
32
|
+
const [editor] = useLexicalComposerContext();
|
|
33
|
+
|
|
34
|
+
const [isSelected, setIsSelected] = useState(
|
|
35
|
+
() => isNodeSelected(editor, key),
|
|
36
|
+
subSlot(slot, 'ulns:state'),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
useEffect(
|
|
40
|
+
() => {
|
|
41
|
+
let isMounted = true;
|
|
42
|
+
const unregister = editor.registerUpdateListener(() => {
|
|
43
|
+
if (isMounted) {
|
|
44
|
+
setIsSelected(isNodeSelected(editor, key));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
isMounted = false;
|
|
49
|
+
unregister();
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
[editor, key],
|
|
53
|
+
subSlot(slot, 'ulns:le'),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const setSelected = useCallback(
|
|
57
|
+
(selected: boolean) => {
|
|
58
|
+
editor.update(() => {
|
|
59
|
+
let selection = $getSelection();
|
|
60
|
+
if (!$isNodeSelection(selection)) {
|
|
61
|
+
selection = $createNodeSelection();
|
|
62
|
+
$setSelection(selection);
|
|
63
|
+
}
|
|
64
|
+
if ($isNodeSelection(selection)) {
|
|
65
|
+
if (selected) {
|
|
66
|
+
selection.add(key);
|
|
67
|
+
} else {
|
|
68
|
+
selection.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
[editor, key],
|
|
74
|
+
subSlot(slot, 'ulns:set'),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const clearSelected = useCallback(
|
|
78
|
+
() => {
|
|
79
|
+
editor.update(() => {
|
|
80
|
+
const selection = $getSelection();
|
|
81
|
+
if ($isNodeSelection(selection)) {
|
|
82
|
+
selection.clear();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
[editor],
|
|
87
|
+
subSlot(slot, 'ulns:clear'),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return [isSelected, setSelected, clearSelected];
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LexicalEditor, NodeKey } from 'lexical';
|
|
2
|
+
|
|
3
|
+
import { mountSlotContainer, unmountSlotContainer } from 'lexical';
|
|
4
|
+
import { useCallback } from 'octane';
|
|
5
|
+
|
|
6
|
+
// Ported from @lexical/react/src/useLexicalSlotRef.ts. Three required user args, so
|
|
7
|
+
// the trailing slot is positional.
|
|
8
|
+
export function useLexicalSlotRef<T extends HTMLElement = HTMLElement>(
|
|
9
|
+
editor: LexicalEditor,
|
|
10
|
+
nodeKey: NodeKey,
|
|
11
|
+
slotName: string,
|
|
12
|
+
slot?: symbol,
|
|
13
|
+
): (target: T | null) => (() => void) | void {
|
|
14
|
+
return useCallback(
|
|
15
|
+
(target: T | null) => {
|
|
16
|
+
if (target) {
|
|
17
|
+
const container = mountSlotContainer(editor, nodeKey, slotName, target);
|
|
18
|
+
if (container) {
|
|
19
|
+
return unmountSlotContainer.bind(null, editor, nodeKey, container);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
[editor, nodeKey, slotName],
|
|
24
|
+
slot,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
import { useMemo, useRef, useState, useLayoutEffect } from 'octane';
|
|
3
|
+
|
|
4
|
+
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
5
|
+
import { subSlot } from './shared/internal';
|
|
6
|
+
|
|
7
|
+
// Ported from @lexical/react/src/useLexicalSubscription.tsx. A plain `.ts` hook:
|
|
8
|
+
// the caller's compiled call site injects `slot` as the trailing arg, and each
|
|
9
|
+
// composed base hook gets a distinct sub-slot (per-call-site identity).
|
|
10
|
+
|
|
11
|
+
export type LexicalSubscription<T> = {
|
|
12
|
+
initialValueFn: () => T;
|
|
13
|
+
subscribe: (callback: (value: T) => void) => () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shortcut to Lexical subscriptions when values are used for render.
|
|
18
|
+
* @param subscription - Creates the {@link LexicalSubscription}. Its identity must
|
|
19
|
+
* be stable (module scope or `useCallback`).
|
|
20
|
+
*/
|
|
21
|
+
export function useLexicalSubscription<T>(
|
|
22
|
+
subscription: (editor: LexicalEditor) => LexicalSubscription<T>,
|
|
23
|
+
slot?: symbol,
|
|
24
|
+
): T {
|
|
25
|
+
const [editor] = useLexicalComposerContext();
|
|
26
|
+
const initializedSubscription = useMemo(
|
|
27
|
+
() => subscription(editor),
|
|
28
|
+
[editor, subscription],
|
|
29
|
+
subSlot(slot, 'uls:memo'),
|
|
30
|
+
);
|
|
31
|
+
const [value, setValue] = useState<T>(
|
|
32
|
+
() => initializedSubscription.initialValueFn(),
|
|
33
|
+
subSlot(slot, 'uls:state'),
|
|
34
|
+
);
|
|
35
|
+
const valueRef = useRef<T>(value, subSlot(slot, 'uls:ref'));
|
|
36
|
+
useLayoutEffect(
|
|
37
|
+
() => {
|
|
38
|
+
const { initialValueFn, subscribe } = initializedSubscription;
|
|
39
|
+
const currentValue = initialValueFn();
|
|
40
|
+
if (valueRef.current !== currentValue) {
|
|
41
|
+
valueRef.current = currentValue;
|
|
42
|
+
setValue(currentValue);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return subscribe((newValue: T) => {
|
|
46
|
+
valueRef.current = newValue;
|
|
47
|
+
setValue(newValue);
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
[initializedSubscription, subscription],
|
|
51
|
+
subSlot(slot, 'uls:le'),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EntityMatch } from '@lexical/text';
|
|
2
|
+
import type { Klass, TextNode } from 'lexical';
|
|
3
|
+
|
|
4
|
+
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
5
|
+
import { registerLexicalTextEntity } from '@lexical/text';
|
|
6
|
+
import { mergeRegister } from 'lexical';
|
|
7
|
+
import { useEffect } from 'octane';
|
|
8
|
+
|
|
9
|
+
// Ported from @lexical/react/src/useLexicalTextEntity.ts. Three required user args,
|
|
10
|
+
// so the trailing slot is positional (4th).
|
|
11
|
+
export function useLexicalTextEntity<T extends TextNode>(
|
|
12
|
+
getMatch: (text: string) => null | EntityMatch,
|
|
13
|
+
targetNode: Klass<T>,
|
|
14
|
+
createNode: (textNode: TextNode) => T,
|
|
15
|
+
slot?: symbol,
|
|
16
|
+
): void {
|
|
17
|
+
const [editor] = useLexicalComposerContext();
|
|
18
|
+
|
|
19
|
+
useEffect(
|
|
20
|
+
() => mergeRegister(...registerLexicalTextEntity(editor, getMatch, targetNode, createNode)),
|
|
21
|
+
[createNode, editor, getMatch, targetNode],
|
|
22
|
+
slot,
|
|
23
|
+
);
|
|
24
|
+
}
|