@octanejs/lexical 0.1.8 → 0.1.10
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/package.json +5 -5
- package/src/LexicalAutoEmbedPlugin.tsrx +31 -6
- package/src/LexicalAutoFocusPlugin.tsrx +5 -1
- package/src/LexicalAutoLinkPlugin.tsrx +9 -1
- package/src/LexicalBlockWithAlignableContents.tsrx +13 -1
- package/src/LexicalCharacterLimitPlugin.tsrx +18 -7
- package/src/LexicalCheckListPlugin.tsrx +5 -1
- package/src/LexicalClearEditorPlugin.tsrx +5 -1
- package/src/LexicalClickableLinkPlugin.tsrx +6 -1
- package/src/LexicalComposer.tsrx +13 -4
- package/src/LexicalContentEditable.tsrx +25 -3
- package/src/LexicalDraggableBlockPlugin.tsrx +68 -31
- package/src/LexicalEditorRefPlugin.tsrx +7 -1
- package/src/LexicalErrorBoundary.tsrx +18 -2
- package/src/LexicalHistoryPlugin.tsrx +7 -1
- package/src/LexicalHorizontalRuleNode.tsrx +10 -7
- package/src/LexicalLinkPlugin.tsrx +7 -1
- package/src/LexicalListPlugin.tsrx +17 -1
- package/src/LexicalMarkdownShortcutPlugin.tsrx +8 -3
- package/src/LexicalNestedComposer.tsrx +21 -3
- package/src/LexicalNodeContextMenuPlugin.tsrx +76 -22
- package/src/LexicalNodeEventPlugin.tsrx +10 -3
- package/src/LexicalNodeMenuPlugin.tsrx +21 -3
- package/src/LexicalOnChangePlugin.tsrx +8 -1
- package/src/LexicalPlainTextPlugin.tsrx +16 -2
- package/src/LexicalRichTextPlugin.tsrx +16 -2
- package/src/LexicalSelectionAlwaysOnDisplay.tsrx +5 -1
- package/src/LexicalTabIndentationPlugin.tsrx +12 -1
- package/src/LexicalTableOfContentsPlugin.tsrx +39 -12
- package/src/LexicalTablePlugin.tsrx +34 -1
- package/src/LexicalTypeaheadMenuPlugin.tsrx +24 -3
- package/src/shared/LexicalContentEditableElement.tsrx +58 -10
- package/src/shared/LexicalDecorators.tsrx +28 -5
- package/src/shared/LexicalMenu.tsrx +38 -7
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@lexical/extension';
|
|
11
11
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
12
12
|
import { useLexicalNodeSelection } from './useLexicalNodeSelection';
|
|
13
|
+
import type { SerializedHorizontalRuleNode } from '@lexical/extension';
|
|
13
14
|
import {
|
|
14
15
|
$applyNodeReplacement,
|
|
15
16
|
addClassNamesToElement,
|
|
@@ -19,11 +20,13 @@ import {
|
|
|
19
20
|
mergeRegister,
|
|
20
21
|
removeClassNamesFromElement,
|
|
21
22
|
} from 'lexical';
|
|
23
|
+
import type { DOMConversionMap, DOMConversionOutput, NodeKey } from 'lexical';
|
|
22
24
|
import { useEffect } from 'octane';
|
|
25
|
+
import type { OctaneNode } from 'octane';
|
|
23
26
|
|
|
24
27
|
export { $isHorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/extension';
|
|
25
28
|
|
|
26
|
-
function HorizontalRuleComponent(props) {
|
|
29
|
+
function HorizontalRuleComponent(props: { nodeKey: NodeKey }) {
|
|
27
30
|
const nodeKey = props.nodeKey;
|
|
28
31
|
const [editor] = useLexicalComposerContext();
|
|
29
32
|
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
|
|
@@ -68,15 +71,15 @@ export class HorizontalRuleNode extends BaseHorizontalRuleNode {
|
|
|
68
71
|
return 'horizontalrule';
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
static clone(node) {
|
|
74
|
+
static clone(node: HorizontalRuleNode): HorizontalRuleNode {
|
|
72
75
|
return new HorizontalRuleNode(node.__key);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
static importJSON(serializedNode) {
|
|
78
|
+
static importJSON(serializedNode: SerializedHorizontalRuleNode): HorizontalRuleNode {
|
|
76
79
|
return $createHorizontalRuleNode().updateFromJSON(serializedNode);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
static importDOM() {
|
|
82
|
+
static importDOM(): DOMConversionMap | null {
|
|
80
83
|
return {
|
|
81
84
|
hr: () => ({
|
|
82
85
|
conversion: $convertHorizontalRuleElement,
|
|
@@ -85,15 +88,15 @@ export class HorizontalRuleNode extends BaseHorizontalRuleNode {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
decorate() {
|
|
91
|
+
decorate(): OctaneNode {
|
|
89
92
|
return <HorizontalRuleComponent nodeKey={this.__key} />;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
function $convertHorizontalRuleElement() {
|
|
96
|
+
function $convertHorizontalRuleElement(): DOMConversionOutput {
|
|
94
97
|
return { node: $createHorizontalRuleNode() };
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
export function $createHorizontalRuleNode() {
|
|
100
|
+
export function $createHorizontalRuleNode(): HorizontalRuleNode {
|
|
98
101
|
return $applyNodeReplacement(new HorizontalRuleNode());
|
|
99
102
|
}
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
// @lexical/extension's framework-agnostic core.
|
|
3
3
|
import { namedSignals } from '@lexical/extension';
|
|
4
4
|
import { LinkNode, registerLink } from '@lexical/link';
|
|
5
|
+
import type { LinkAttributes } from '@lexical/link';
|
|
5
6
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
6
7
|
import { useEffect } from 'octane';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
type Props = {
|
|
10
|
+
validateUrl?: (url: string) => boolean;
|
|
11
|
+
attributes?: LinkAttributes;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function LinkPlugin(props: Props) {
|
|
9
15
|
const [editor] = useLexicalComposerContext();
|
|
10
16
|
const validateUrl = props.validateUrl;
|
|
11
17
|
const attributes = props.attributes;
|
|
@@ -10,7 +10,23 @@ import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
|
10
10
|
import { mergeRegister } from 'lexical';
|
|
11
11
|
import { useEffect } from 'octane';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Props for the {@link ListPlugin} component.
|
|
15
|
+
*/
|
|
16
|
+
export interface ListPluginProps {
|
|
17
|
+
/**
|
|
18
|
+
* When `true`, enforces strict indentation rules for list items, ensuring
|
|
19
|
+
* consistent structure. When `false` (default), indentation is more flexible.
|
|
20
|
+
*/
|
|
21
|
+
hasStrictIndent?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* When `true`, splitting a numbered list will preserve the numbering
|
|
24
|
+
* continuity. When `false` (default), the new split list resets to 1.
|
|
25
|
+
*/
|
|
26
|
+
shouldPreserveNumbering?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ListPlugin(props: ListPluginProps) {
|
|
14
30
|
const [editor] = useLexicalComposerContext();
|
|
15
31
|
const hasStrictIndent =
|
|
16
32
|
props.hasStrictIndent === undefined ? false : props.hasStrictIndent;
|
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
$isHorizontalRuleNode,
|
|
6
6
|
HorizontalRuleNode,
|
|
7
7
|
} from '@lexical/extension';
|
|
8
|
+
import type { ElementTransformer, Transformer } from '@lexical/markdown';
|
|
8
9
|
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
9
10
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
10
11
|
import { useEffect } from 'octane';
|
|
11
12
|
|
|
12
|
-
const HR = {
|
|
13
|
+
const HR: ElementTransformer = {
|
|
13
14
|
dependencies: [HorizontalRuleNode],
|
|
14
15
|
export: (node) => ($isHorizontalRuleNode(node) ? '***' : null),
|
|
15
16
|
regExp: /^(---|\*\*\*|___)\s?$/,
|
|
@@ -26,9 +27,13 @@ const HR = {
|
|
|
26
27
|
type: 'element',
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
export const DEFAULT_TRANSFORMERS = [HR, ...TRANSFORMERS];
|
|
30
|
+
export const DEFAULT_TRANSFORMERS: Transformer[] = [HR, ...TRANSFORMERS];
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
type Props = Readonly<{
|
|
33
|
+
transformers?: Transformer[];
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
export function MarkdownShortcutPlugin(props: Props) {
|
|
32
37
|
const [editor] = useLexicalComposerContext();
|
|
33
38
|
const transformers =
|
|
34
39
|
props.transformers === undefined ? DEFAULT_TRANSFORMERS : props.transformers;
|
|
@@ -3,10 +3,19 @@
|
|
|
3
3
|
// children via context, inheriting the parent's theme/nodes/namespace/editable
|
|
4
4
|
// state by default. The nested editor is created ahead of time and passed in.
|
|
5
5
|
import { createLexicalComposerContext, LexicalComposerContext } from './LexicalComposerContext';
|
|
6
|
+
import type { LexicalComposerContextWithEditor } from './LexicalComposerContext';
|
|
6
7
|
import { CollaborationContext } from './LexicalCollaborationContext';
|
|
7
8
|
import invariant from '@lexical/internal/invariant';
|
|
8
9
|
import warnOnlyOnce from '@lexical/internal/warnOnlyOnce';
|
|
10
|
+
import type {
|
|
11
|
+
EditorThemeClasses,
|
|
12
|
+
Klass,
|
|
13
|
+
LexicalEditor,
|
|
14
|
+
LexicalNode,
|
|
15
|
+
LexicalNodeReplacement,
|
|
16
|
+
} from 'lexical';
|
|
9
17
|
import { createSharedNodeState, getRegisteredNode, getTransformSetFromKlass } from 'lexical';
|
|
18
|
+
import type { OctaneNode } from 'octane';
|
|
10
19
|
import { useContext, useEffect, useMemo, useRef } from 'octane';
|
|
11
20
|
|
|
12
21
|
const initialNodesWarning = warnOnlyOnce(
|
|
@@ -16,7 +25,16 @@ const explicitNamespaceWarning = warnOnlyOnce(
|
|
|
16
25
|
`LexicalNestedComposer initialEditor should explicitly initialize its namespace when the node configuration differs from the parentEditor. For backwards compatibility, the namespace will be initialized from parentEditor until v0.32.0, but this has always had incorrect copy/paste behavior when the configuration differed.\nYou can configure your editor's namespace with createEditor({namespace: 'nested-editor-namespace', nodes: [], parentEditor: $getEditor()}).`,
|
|
17
26
|
);
|
|
18
27
|
|
|
19
|
-
export
|
|
28
|
+
export interface LexicalNestedComposerProps {
|
|
29
|
+
children?: OctaneNode;
|
|
30
|
+
initialEditor: LexicalEditor;
|
|
31
|
+
initialTheme?: EditorThemeClasses;
|
|
32
|
+
initialNodes?: readonly (Klass<LexicalNode> | LexicalNodeReplacement)[];
|
|
33
|
+
skipCollabChecks?: undefined | true;
|
|
34
|
+
skipEditableListener?: undefined | true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function LexicalNestedComposer(props: LexicalNestedComposerProps) {
|
|
20
38
|
const initialEditor = props.initialEditor;
|
|
21
39
|
const initialNodes = props.initialNodes;
|
|
22
40
|
const initialTheme = props.initialTheme;
|
|
@@ -32,7 +50,7 @@ export function LexicalNestedComposer(props) {
|
|
|
32
50
|
|
|
33
51
|
const [parentEditor, { getTheme: getParentTheme }] = parentContext;
|
|
34
52
|
|
|
35
|
-
const composerContext = useMemo(() => {
|
|
53
|
+
const composerContext = useMemo<LexicalComposerContextWithEditor>(() => {
|
|
36
54
|
const composerTheme = initialTheme || getParentTheme() || undefined;
|
|
37
55
|
const context = createLexicalComposerContext(parentContext, composerTheme);
|
|
38
56
|
|
|
@@ -112,7 +130,7 @@ export function LexicalNestedComposer(props) {
|
|
|
112
130
|
|
|
113
131
|
useEffect(() => {
|
|
114
132
|
if (!skipEditableListener) {
|
|
115
|
-
const editableListener = (editable) => initialEditor.setEditable(editable);
|
|
133
|
+
const editableListener = (editable: boolean) => initialEditor.setEditable(editable);
|
|
116
134
|
editableListener(parentEditor.isEditable());
|
|
117
135
|
return parentEditor.registerEditableListener(editableListener);
|
|
118
136
|
}
|
|
@@ -21,9 +21,12 @@ import {
|
|
|
21
21
|
FloatingOverlay,
|
|
22
22
|
FloatingFocusManager,
|
|
23
23
|
} from '@octanejs/floating-ui';
|
|
24
|
+
import type { FloatingStyles } from '@octanejs/floating-ui';
|
|
24
25
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
25
26
|
import { $getNearestNodeFromDOMNode, $getRoot } from 'lexical';
|
|
27
|
+
import type { LexicalNode } from 'lexical';
|
|
26
28
|
import { useEffect, useRef, useState } from 'octane';
|
|
29
|
+
import type { OctaneNode } from 'octane';
|
|
27
30
|
|
|
28
31
|
class MenuOption {
|
|
29
32
|
key: string;
|
|
@@ -43,12 +46,17 @@ class MenuOption {
|
|
|
43
46
|
export class NodeContextMenuOption extends MenuOption {
|
|
44
47
|
type: string;
|
|
45
48
|
title: string;
|
|
46
|
-
icon:
|
|
49
|
+
icon: OctaneNode | null;
|
|
47
50
|
disabled: boolean;
|
|
48
51
|
$onSelect: () => void;
|
|
49
|
-
$showOn?: (node:
|
|
52
|
+
$showOn?: (node: LexicalNode) => boolean;
|
|
50
53
|
|
|
51
|
-
constructor(title, options
|
|
54
|
+
constructor(title: string, options: {
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
icon?: OctaneNode;
|
|
57
|
+
$onSelect: () => void;
|
|
58
|
+
$showOn?: (node: LexicalNode) => boolean;
|
|
59
|
+
}) {
|
|
52
60
|
super(title);
|
|
53
61
|
this.type = 'item';
|
|
54
62
|
this.title = title;
|
|
@@ -63,9 +71,9 @@ export class NodeContextMenuOption extends MenuOption {
|
|
|
63
71
|
|
|
64
72
|
export class NodeContextMenuSeparator extends MenuOption {
|
|
65
73
|
type: string;
|
|
66
|
-
$showOn?: (node:
|
|
74
|
+
$showOn?: (node: LexicalNode) => boolean;
|
|
67
75
|
|
|
68
|
-
constructor(options) {
|
|
76
|
+
constructor(options?: { $showOn?: (node: LexicalNode) => boolean }) {
|
|
69
77
|
super('_separator');
|
|
70
78
|
this.type = 'separator';
|
|
71
79
|
if (options && options.$showOn) {
|
|
@@ -74,10 +82,56 @@ export class NodeContextMenuSeparator extends MenuOption {
|
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
85
|
+
type ContextMenuType = NodeContextMenuOption | NodeContextMenuSeparator;
|
|
86
|
+
|
|
87
|
+
interface MenuSeparatorType {
|
|
88
|
+
className: string | undefined;
|
|
89
|
+
key: string;
|
|
90
|
+
type: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface MenuItemType extends MenuSeparatorType {
|
|
94
|
+
disabled: boolean;
|
|
95
|
+
icon: OctaneNode | null;
|
|
96
|
+
label: string;
|
|
97
|
+
onSelect: () => void;
|
|
98
|
+
title: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type MenuType = MenuItemType | MenuSeparatorType;
|
|
102
|
+
|
|
103
|
+
interface Props {
|
|
104
|
+
label?: string;
|
|
105
|
+
nested?: boolean;
|
|
106
|
+
className?: string;
|
|
107
|
+
itemClassName?: string;
|
|
108
|
+
separatorClassName?: string;
|
|
109
|
+
items: ContextMenuType[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A renderable menu entry as ContextMenuList consumes it: a MenuType flattened so
|
|
113
|
+
// item-only fields are optional (`type` is a plain string, so the union does not
|
|
114
|
+
// discriminate), plus the item's resolved getItemProps() bag —
|
|
115
|
+
// `Record<string, unknown>`, the @octanejs/floating-ui prop-getter return type.
|
|
116
|
+
type ContextMenuListItem = MenuSeparatorType & {
|
|
117
|
+
disabled?: boolean;
|
|
118
|
+
icon?: OctaneNode | null;
|
|
119
|
+
label?: string;
|
|
120
|
+
itemProps: Record<string, unknown>;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type ContextMenuListProps = {
|
|
124
|
+
className: string | undefined;
|
|
125
|
+
setFloating: (node: HTMLElement | null) => void;
|
|
126
|
+
floatingStyles: FloatingStyles;
|
|
127
|
+
floatingProps: Record<string, unknown>;
|
|
128
|
+
items: ContextMenuListItem[];
|
|
129
|
+
};
|
|
130
|
+
|
|
77
131
|
// The floating menu container + its items. Mirrors the React render's inner <div>:
|
|
78
132
|
// the resolved floating props are spread onto it, and each item spreads its own
|
|
79
133
|
// getItemProps() onto a <button> (or <hr> for a separator).
|
|
80
|
-
function ContextMenuList(props) @{
|
|
134
|
+
function ContextMenuList(props: ContextMenuListProps) @{
|
|
81
135
|
<div
|
|
82
136
|
class={props.className}
|
|
83
137
|
ref={props.setFloating}
|
|
@@ -100,19 +154,19 @@ function ContextMenuList(props) @{
|
|
|
100
154
|
</div>
|
|
101
155
|
}
|
|
102
156
|
|
|
103
|
-
export function NodeContextMenuPlugin(props) @{
|
|
157
|
+
export function NodeContextMenuPlugin(props: Props) @{
|
|
104
158
|
const items = props.items;
|
|
105
159
|
const className = props.className;
|
|
106
160
|
const itemClassName = props.itemClassName;
|
|
107
161
|
const separatorClassName = props.separatorClassName;
|
|
108
162
|
|
|
109
163
|
const [editor] = useLexicalComposerContext();
|
|
110
|
-
const [activeIndex, setActiveIndex] = useState(null);
|
|
164
|
+
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
111
165
|
const [isOpen, setIsOpen] = useState(false);
|
|
112
|
-
const [renderItems, setRenderItems] = useState([]);
|
|
166
|
+
const [renderItems, setRenderItems] = useState<MenuType[]>([]);
|
|
113
167
|
|
|
114
|
-
const listItemsRef = useRef([]);
|
|
115
|
-
const listContentRef = useRef([]);
|
|
168
|
+
const listItemsRef = useRef<(HTMLButtonElement | null)[]>([]);
|
|
169
|
+
const listContentRef = useRef<(string | null)[]>([]);
|
|
116
170
|
|
|
117
171
|
const f = useFloating({
|
|
118
172
|
middleware: [
|
|
@@ -149,7 +203,7 @@ export function NodeContextMenuPlugin(props) @{
|
|
|
149
203
|
|
|
150
204
|
// Build the menu model on right-click and open the menu at the cursor.
|
|
151
205
|
useEffect(() => {
|
|
152
|
-
function onContextMenu(e) {
|
|
206
|
+
function onContextMenu(e: MouseEvent) {
|
|
153
207
|
e.preventDefault();
|
|
154
208
|
|
|
155
209
|
f.refs.setPositionReference({
|
|
@@ -167,10 +221,10 @@ export function NodeContextMenuPlugin(props) @{
|
|
|
167
221
|
},
|
|
168
222
|
});
|
|
169
223
|
|
|
170
|
-
let visibleItems = [];
|
|
224
|
+
let visibleItems: ContextMenuType[] = [];
|
|
171
225
|
if (items) {
|
|
172
226
|
editor.read(() => {
|
|
173
|
-
const node = $getNearestNodeFromDOMNode(e.target) ?? $getRoot();
|
|
227
|
+
const node = $getNearestNodeFromDOMNode(e.target as Element) ?? $getRoot();
|
|
174
228
|
if (node) {
|
|
175
229
|
visibleItems = items.filter((option) => (option.$showOn ? option.$showOn(node) : true));
|
|
176
230
|
}
|
|
@@ -187,12 +241,12 @@ export function NodeContextMenuPlugin(props) @{
|
|
|
187
241
|
}
|
|
188
242
|
return {
|
|
189
243
|
className: itemClassName,
|
|
190
|
-
disabled: option.disabled,
|
|
191
|
-
icon: option.icon,
|
|
244
|
+
disabled: (option as NodeContextMenuOption).disabled,
|
|
245
|
+
icon: (option as NodeContextMenuOption).icon,
|
|
192
246
|
key: option.key,
|
|
193
|
-
label: option.title,
|
|
194
|
-
onSelect: () => editor.update(() => option.$onSelect()),
|
|
195
|
-
title: option.title,
|
|
247
|
+
label: (option as NodeContextMenuOption).title,
|
|
248
|
+
onSelect: () => editor.update(() => (option as NodeContextMenuOption).$onSelect()),
|
|
249
|
+
title: (option as NodeContextMenuOption).title,
|
|
196
250
|
type: option.type,
|
|
197
251
|
};
|
|
198
252
|
});
|
|
@@ -216,8 +270,8 @@ export function NodeContextMenuPlugin(props) @{
|
|
|
216
270
|
listItemsRef.current = [];
|
|
217
271
|
const itemsForRender = renderItems.map((item, index) => {
|
|
218
272
|
const onSelect = () => {
|
|
219
|
-
if (item.onSelect) {
|
|
220
|
-
item.onSelect();
|
|
273
|
+
if ((item as MenuItemType).onSelect) {
|
|
274
|
+
(item as MenuItemType).onSelect();
|
|
221
275
|
}
|
|
222
276
|
setIsOpen(false);
|
|
223
277
|
};
|
|
@@ -226,7 +280,7 @@ export function NodeContextMenuPlugin(props) @{
|
|
|
226
280
|
itemProps: getItemProps({
|
|
227
281
|
onClick: onSelect,
|
|
228
282
|
onMouseUp: onSelect,
|
|
229
|
-
ref: (node) => {
|
|
283
|
+
ref: (node: HTMLButtonElement | null) => {
|
|
230
284
|
listItemsRef.current[index] = node;
|
|
231
285
|
},
|
|
232
286
|
tabIndex: activeIndex === index ? 0 : -1,
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
// Ported from @lexical/react/src/LexicalNodeEventPlugin.ts.
|
|
2
2
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
3
3
|
import { $findMatchingParent, $getNearestNodeFromDOMNode } from 'lexical';
|
|
4
|
+
import type { Klass, LexicalEditor, LexicalNode, NodeKey } from 'lexical';
|
|
4
5
|
import { useEffect, useRef } from 'octane';
|
|
5
6
|
|
|
6
7
|
const capturedEvents = new Set(['mouseenter', 'mouseleave']);
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
type Props = {
|
|
10
|
+
nodeType: Klass<LexicalNode>;
|
|
11
|
+
eventType: string;
|
|
12
|
+
eventListener: (event: Event, editor: LexicalEditor, nodeKey: NodeKey) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function NodeEventPlugin(props: Props) {
|
|
9
16
|
const [editor] = useLexicalComposerContext();
|
|
10
17
|
const nodeType = props.nodeType;
|
|
11
18
|
const eventType = props.eventType;
|
|
@@ -17,9 +24,9 @@ export function NodeEventPlugin(props) {
|
|
|
17
24
|
useEffect(() => {
|
|
18
25
|
const isCaptured = capturedEvents.has(eventType);
|
|
19
26
|
|
|
20
|
-
const onEvent = (event) => {
|
|
27
|
+
const onEvent = (event: Event) => {
|
|
21
28
|
editor.update(() => {
|
|
22
|
-
const nearestNode = $getNearestNodeFromDOMNode(event.target);
|
|
29
|
+
const nearestNode = $getNearestNodeFromDOMNode(event.target as Node);
|
|
23
30
|
if (nearestNode !== null) {
|
|
24
31
|
const targetNode =
|
|
25
32
|
isCaptured
|
|
@@ -4,11 +4,29 @@ import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
|
4
4
|
import { LexicalMenu } from './shared/LexicalMenu.tsrx';
|
|
5
5
|
import { useMenuAnchorRef } from './shared/useMenuAnchorRef';
|
|
6
6
|
import { $getNodeByKey, COMMAND_PRIORITY_LOW } from 'lexical';
|
|
7
|
+
import type { CommandListenerPriority, NodeKey, TextNode } from 'lexical';
|
|
7
8
|
import { startTransition, useCallback, useEffect, useState } from 'octane';
|
|
9
|
+
import type { MenuOption, MenuRenderFn, MenuResolution } from './shared/menuShared';
|
|
8
10
|
|
|
9
11
|
export { MenuOption } from './shared/menuShared';
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Props for the {@link LexicalNodeMenuPlugin} component.
|
|
15
|
+
*/
|
|
16
|
+
export type NodeMenuPluginProps<TOption extends MenuOption> = {
|
|
17
|
+
onSelectOption: (option: TOption, textNodeContainingQuery: | TextNode
|
|
18
|
+
| null, closeMenu: () => void, matchingString: string) => void;
|
|
19
|
+
options: TOption[];
|
|
20
|
+
nodeKey: NodeKey | null;
|
|
21
|
+
menuRenderFn?: MenuRenderFn<TOption>;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
onOpen?: (resolution: MenuResolution) => void;
|
|
24
|
+
anchorClassName?: string;
|
|
25
|
+
commandPriority?: CommandListenerPriority;
|
|
26
|
+
parent?: HTMLElement;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function LexicalNodeMenuPlugin<TOption extends MenuOption>(props: NodeMenuPluginProps<TOption>) {
|
|
12
30
|
const options = props.options;
|
|
13
31
|
const nodeKey = props.nodeKey;
|
|
14
32
|
const onClose = props.onClose;
|
|
@@ -21,7 +39,7 @@ export function LexicalNodeMenuPlugin(props) {
|
|
|
21
39
|
const parent = props.parent;
|
|
22
40
|
|
|
23
41
|
const [editor] = useLexicalComposerContext();
|
|
24
|
-
const [resolution, setResolution] = useState(null);
|
|
42
|
+
const [resolution, setResolution] = useState<MenuResolution | null>(null);
|
|
25
43
|
const anchorElementRef = useMenuAnchorRef(resolution, setResolution, anchorClassName, parent);
|
|
26
44
|
|
|
27
45
|
const closeNodeMenu = useCallback(() => {
|
|
@@ -31,7 +49,7 @@ export function LexicalNodeMenuPlugin(props) {
|
|
|
31
49
|
}
|
|
32
50
|
}, [onClose, resolution]);
|
|
33
51
|
|
|
34
|
-
const openNodeMenu = useCallback((res) => {
|
|
52
|
+
const openNodeMenu = useCallback((res: MenuResolution) => {
|
|
35
53
|
setResolution(res);
|
|
36
54
|
if (onOpen != null && resolution === null) {
|
|
37
55
|
onOpen(res);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// Ported from @lexical/react/src/LexicalOnChangePlugin.ts.
|
|
2
2
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
3
3
|
import { HISTORY_MERGE_TAG } from 'lexical';
|
|
4
|
+
import type { EditorState, LexicalEditor } from 'lexical';
|
|
4
5
|
import { useLayoutEffect } from 'octane';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
type Props = {
|
|
8
|
+
ignoreHistoryMergeTagChange?: boolean;
|
|
9
|
+
ignoreSelectionChange?: boolean;
|
|
10
|
+
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function OnChangePlugin(props: Props) {
|
|
7
14
|
const [editor] = useLexicalComposerContext();
|
|
8
15
|
const ignoreHistoryMergeTagChange =
|
|
9
16
|
props.ignoreHistoryMergeTagChange === undefined ? true : props.ignoreHistoryMergeTagChange;
|
|
@@ -5,8 +5,22 @@ import { useLexicalEditable } from './useLexicalEditable';
|
|
|
5
5
|
import { useCanShowPlaceholder } from './shared/useCanShowPlaceholder';
|
|
6
6
|
import { usePlainTextSetup } from './shared/usePlainTextSetup';
|
|
7
7
|
import { Decorators } from './shared/LexicalDecorators.tsrx';
|
|
8
|
+
import type { OctaneNode } from 'octane';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
// Octane analog of @lexical/react's ErrorBoundaryType (a component taking the
|
|
11
|
+
// decorator subtree plus an onError callback).
|
|
12
|
+
type ErrorBoundaryType = (props: {
|
|
13
|
+
children: OctaneNode;
|
|
14
|
+
onError: (error: Error) => void;
|
|
15
|
+
}) => OctaneNode;
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
contentEditable: OctaneNode;
|
|
19
|
+
placeholder?: ((isEditable: boolean) => OctaneNode) | null | OctaneNode;
|
|
20
|
+
ErrorBoundary: ErrorBoundaryType;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function Placeholder(props: { content: Props['placeholder'] }) {
|
|
10
24
|
const [editor] = useLexicalComposerContext();
|
|
11
25
|
const showPlaceholder = useCanShowPlaceholder(editor);
|
|
12
26
|
const editable = useLexicalEditable();
|
|
@@ -21,7 +35,7 @@ function Placeholder(props) {
|
|
|
21
35
|
return content != null ? content : null;
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
export function PlainTextPlugin(props) @{
|
|
38
|
+
export function PlainTextPlugin(props: Props) @{
|
|
25
39
|
const [editor] = useLexicalComposerContext();
|
|
26
40
|
usePlainTextSetup(editor);
|
|
27
41
|
|
|
@@ -7,8 +7,22 @@ import { useLexicalEditable } from './useLexicalEditable';
|
|
|
7
7
|
import { useCanShowPlaceholder } from './shared/useCanShowPlaceholder';
|
|
8
8
|
import { useRichTextSetup } from './shared/useRichTextSetup';
|
|
9
9
|
import { Decorators } from './shared/LexicalDecorators.tsrx';
|
|
10
|
+
import type { OctaneNode } from 'octane';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
// Octane analog of @lexical/react's ErrorBoundaryType (a component taking the
|
|
13
|
+
// decorator subtree plus an onError callback).
|
|
14
|
+
type ErrorBoundaryType = (props: {
|
|
15
|
+
children: OctaneNode;
|
|
16
|
+
onError: (error: Error) => void;
|
|
17
|
+
}) => OctaneNode;
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
contentEditable: OctaneNode;
|
|
21
|
+
placeholder?: ((isEditable: boolean) => OctaneNode) | null | OctaneNode;
|
|
22
|
+
ErrorBoundary: ErrorBoundaryType;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function Placeholder(props: { content: Props['placeholder'] }) {
|
|
12
26
|
const [editor] = useLexicalComposerContext();
|
|
13
27
|
const showPlaceholder = useCanShowPlaceholder(editor);
|
|
14
28
|
const editable = useLexicalEditable();
|
|
@@ -23,7 +37,7 @@ function Placeholder(props) {
|
|
|
23
37
|
return content != null ? content : null;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
|
-
export function RichTextPlugin(props) @{
|
|
40
|
+
export function RichTextPlugin(props: Props) @{
|
|
27
41
|
const [editor] = useLexicalComposerContext();
|
|
28
42
|
useRichTextSetup(editor);
|
|
29
43
|
|
|
@@ -3,7 +3,11 @@ import { selectionAlwaysOnDisplay } from '@lexical/utils';
|
|
|
3
3
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
4
4
|
import { useEffect } from 'octane';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type Props = Readonly<{
|
|
7
|
+
onReposition?: (node: readonly HTMLElement[]) => void;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export function SelectionAlwaysOnDisplay(props: Props) {
|
|
7
11
|
const [editor] = useLexicalComposerContext();
|
|
8
12
|
const onReposition = props.onReposition;
|
|
9
13
|
useEffect(() => selectionAlwaysOnDisplay(editor, onReposition), [editor, onReposition]);
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
// Ported from @lexical/react/src/LexicalTabIndentationPlugin.tsx.
|
|
2
2
|
// registerTabIndentation is from @lexical/extension's framework-agnostic core.
|
|
3
3
|
import { registerTabIndentation } from '@lexical/extension';
|
|
4
|
+
import type { CanIndentPredicate } from '@lexical/extension';
|
|
4
5
|
import { useLexicalComposerContext } from './LexicalComposerContext';
|
|
5
6
|
import { useEffect } from 'octane';
|
|
6
7
|
|
|
7
8
|
export { registerTabIndentation } from '@lexical/extension';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
type Props = {
|
|
11
|
+
maxIndent?: number;
|
|
12
|
+
/**
|
|
13
|
+
* By default, indents are set on all elements for which
|
|
14
|
+
* {@link ElementNode.canIndent} returns true. This option allows you to set
|
|
15
|
+
* indents for specific nodes without overriding the method for others.
|
|
16
|
+
*/
|
|
17
|
+
$canIndent?: CanIndentPredicate;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function TabIndentationPlugin(props: Props) {
|
|
10
21
|
const [editor] = useLexicalComposerContext();
|
|
11
22
|
const maxIndent = props.maxIndent;
|
|
12
23
|
const canIndent = props.$canIndent;
|