@octanejs/lexical 0.1.9 → 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.
Files changed (34) hide show
  1. package/package.json +5 -5
  2. package/src/LexicalAutoEmbedPlugin.tsrx +31 -6
  3. package/src/LexicalAutoFocusPlugin.tsrx +5 -1
  4. package/src/LexicalAutoLinkPlugin.tsrx +9 -1
  5. package/src/LexicalBlockWithAlignableContents.tsrx +13 -1
  6. package/src/LexicalCharacterLimitPlugin.tsrx +18 -7
  7. package/src/LexicalCheckListPlugin.tsrx +5 -1
  8. package/src/LexicalClearEditorPlugin.tsrx +5 -1
  9. package/src/LexicalClickableLinkPlugin.tsrx +6 -1
  10. package/src/LexicalComposer.tsrx +13 -4
  11. package/src/LexicalContentEditable.tsrx +25 -3
  12. package/src/LexicalDraggableBlockPlugin.tsrx +68 -31
  13. package/src/LexicalEditorRefPlugin.tsrx +7 -1
  14. package/src/LexicalErrorBoundary.tsrx +18 -2
  15. package/src/LexicalHistoryPlugin.tsrx +7 -1
  16. package/src/LexicalHorizontalRuleNode.tsrx +10 -7
  17. package/src/LexicalLinkPlugin.tsrx +7 -1
  18. package/src/LexicalListPlugin.tsrx +17 -1
  19. package/src/LexicalMarkdownShortcutPlugin.tsrx +8 -3
  20. package/src/LexicalNestedComposer.tsrx +21 -3
  21. package/src/LexicalNodeContextMenuPlugin.tsrx +76 -22
  22. package/src/LexicalNodeEventPlugin.tsrx +10 -3
  23. package/src/LexicalNodeMenuPlugin.tsrx +21 -3
  24. package/src/LexicalOnChangePlugin.tsrx +8 -1
  25. package/src/LexicalPlainTextPlugin.tsrx +16 -2
  26. package/src/LexicalRichTextPlugin.tsrx +16 -2
  27. package/src/LexicalSelectionAlwaysOnDisplay.tsrx +5 -1
  28. package/src/LexicalTabIndentationPlugin.tsrx +12 -1
  29. package/src/LexicalTableOfContentsPlugin.tsrx +39 -12
  30. package/src/LexicalTablePlugin.tsrx +34 -1
  31. package/src/LexicalTypeaheadMenuPlugin.tsrx +24 -3
  32. package/src/shared/LexicalContentEditableElement.tsrx +58 -10
  33. package/src/shared/LexicalDecorators.tsrx +28 -5
  34. 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
- export function LinkPlugin(props) {
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
- export function ListPlugin(props) {
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
- export function MarkdownShortcutPlugin(props) {
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 function LexicalNestedComposer(props) {
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: unknown;
49
+ icon: OctaneNode | null;
47
50
  disabled: boolean;
48
51
  $onSelect: () => void;
49
- $showOn?: (node: unknown) => boolean;
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: unknown) => boolean;
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
- export function NodeEventPlugin(props) {
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
- export function LexicalNodeMenuPlugin(props) {
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
- export function OnChangePlugin(props) {
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
- function Placeholder(props) {
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
- function Placeholder(props) {
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
- export function SelectionAlwaysOnDisplay(props) {
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
- export function TabIndentationPlugin(props) {
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;