@lobehub/editor 4.25.0 → 4.25.1
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/es/editor-kernel/kernel.d.ts +9 -0
- package/es/editor-kernel/kernel.js +71 -11
- package/es/headless.d.ts +12 -0
- package/es/headless.js +83 -12
- package/es/plugins/common/plugin/index.js +12 -3
- package/es/plugins/common/react/ReactPlainText.js +27 -9
- package/es/react/hooks/useEditor.d.ts +24 -2
- package/es/react/hooks/useEditor.js +13 -3
- package/es/react.d.ts +2 -2
- package/es/types/kernel.d.ts +12 -0
- package/package.json +1 -1
|
@@ -24,6 +24,11 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
|
|
|
24
24
|
private hotReloadMode;
|
|
25
25
|
private logger;
|
|
26
26
|
private historyState;
|
|
27
|
+
/**
|
|
28
|
+
* Root-scoped resources are attached to the Lexical editor's root listener
|
|
29
|
+
* so direct Lexical setRootElement(null) calls are safe as well.
|
|
30
|
+
*/
|
|
31
|
+
private unregisterRootLifecycle?;
|
|
27
32
|
private editor?;
|
|
28
33
|
private headlessEditor;
|
|
29
34
|
constructor();
|
|
@@ -44,6 +49,7 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
|
|
|
44
49
|
destroy(): void;
|
|
45
50
|
getRootElement(): HTMLElement | null;
|
|
46
51
|
setRootElement(dom: HTMLElement, editable?: boolean): LexicalEditor;
|
|
52
|
+
setRootElement(dom: null, editable?: boolean): LexicalEditor | null;
|
|
47
53
|
setEditable(editable: boolean): void;
|
|
48
54
|
initNodeEditor(): LexicalEditor;
|
|
49
55
|
initHeadlessEditor(): LexicalEditor;
|
|
@@ -80,6 +86,9 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
|
|
|
80
86
|
registerNodeTransform(transform: INodeRegistrationTransform): () => void;
|
|
81
87
|
registerBeforeEditorInit(hook: IBeforeEditorInitLifecycle): () => void;
|
|
82
88
|
registerRootClassName(className: string): () => void;
|
|
89
|
+
registerRootListener(listener: (rootElement: HTMLElement | null) => void): () => void;
|
|
90
|
+
private getEditorId;
|
|
91
|
+
private registerRootLifecycle;
|
|
83
92
|
private runBeforeEditorInitLifecycle;
|
|
84
93
|
private applyRootClassNames;
|
|
85
94
|
private resolveNodesForInitialization;
|
|
@@ -95,26 +95,46 @@ var init_kernel = __esmMin((() => {
|
|
|
95
95
|
return this.editor || null;
|
|
96
96
|
}
|
|
97
97
|
destroy() {
|
|
98
|
-
|
|
98
|
+
const editor = this.editor;
|
|
99
|
+
const editorId = this.getEditorId();
|
|
99
100
|
this.logger.info(`🗑️ Destroying editor with ${this.pluginsInstances.length} plugins`);
|
|
101
|
+
if (editor) try {
|
|
102
|
+
editor.setRootElement(null);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.logger.warn("Failed to detach editor root during destroy:", error);
|
|
105
|
+
}
|
|
106
|
+
unregisterEditorKernel(editorId);
|
|
100
107
|
try {
|
|
101
|
-
|
|
108
|
+
editor?.setEditorState(createEmptyEditorState());
|
|
102
109
|
} catch (error) {
|
|
103
110
|
this.logger.warn("Failed to reset editor state during destroy:", error);
|
|
104
111
|
}
|
|
105
|
-
this.
|
|
112
|
+
this.unregisterRootLifecycle?.();
|
|
113
|
+
this.unregisterRootLifecycle = void 0;
|
|
114
|
+
let destroyError;
|
|
106
115
|
this.pluginsInstances.forEach((plugin) => {
|
|
107
|
-
|
|
116
|
+
try {
|
|
117
|
+
plugin.destroy?.();
|
|
118
|
+
} catch (error) {
|
|
119
|
+
destroyError ??= error;
|
|
120
|
+
}
|
|
108
121
|
});
|
|
109
122
|
this.pluginsInstances = [];
|
|
110
|
-
this.
|
|
123
|
+
this.dataTypeMap.clear();
|
|
124
|
+
this.decorators = {};
|
|
111
125
|
this.nodeTransforms = [];
|
|
126
|
+
this.nodes = [];
|
|
127
|
+
this.beforeEditorInitHooks = [];
|
|
112
128
|
this.rootClassNames.clear();
|
|
113
129
|
this.serviceMap.clear();
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
130
|
+
this.themes = { [EDITOR_THEME_KEY]: generateEditorId() };
|
|
131
|
+
this.historyState = createEmptyHistoryState();
|
|
132
|
+
this._commands.clear();
|
|
133
|
+
this._commandsClean.clear();
|
|
134
|
+
this.editor = void 0;
|
|
116
135
|
this.headlessEditor = false;
|
|
117
136
|
this.logger.info("✅ Editor destroyed");
|
|
137
|
+
if (destroyError) throw destroyError;
|
|
118
138
|
}
|
|
119
139
|
getRootElement() {
|
|
120
140
|
if (this.headlessEditor) return null;
|
|
@@ -122,12 +142,23 @@ var init_kernel = __esmMin((() => {
|
|
|
122
142
|
}
|
|
123
143
|
setRootElement(dom, editable = true) {
|
|
124
144
|
if (this.editor) {
|
|
125
|
-
if (this.headlessEditor)
|
|
145
|
+
if (this.headlessEditor) {
|
|
146
|
+
if (dom === null) return this.editor;
|
|
147
|
+
throw new Error("Headless editor cannot be attached to a root element.");
|
|
148
|
+
}
|
|
126
149
|
this.logger.warn("[Editor] Editor is already initialized, updating root element only");
|
|
150
|
+
if (dom) registerEditorKernel(this.getEditorId(), this);
|
|
127
151
|
this.editor.setRootElement(dom);
|
|
128
|
-
|
|
152
|
+
if (dom) {
|
|
153
|
+
this.editor.setEditable(editable);
|
|
154
|
+
this.applyRootClassNames(dom);
|
|
155
|
+
}
|
|
129
156
|
return this.editor;
|
|
130
157
|
}
|
|
158
|
+
if (dom === null) {
|
|
159
|
+
unregisterEditorKernel(this.getEditorId());
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
131
162
|
if (this.pluginsInstances.length === 0) {
|
|
132
163
|
this.logger.info(`🔌 Initializing ${this.plugins.length} plugins`);
|
|
133
164
|
for (const plugin of this.plugins) {
|
|
@@ -138,7 +169,7 @@ var init_kernel = __esmMin((() => {
|
|
|
138
169
|
this.runBeforeEditorInitLifecycle();
|
|
139
170
|
const resolvedNodes = this.resolveNodesForInitialization();
|
|
140
171
|
this.logger.info(`📝 Creating editor with ${resolvedNodes.length} nodes`);
|
|
141
|
-
registerEditorKernel(this.
|
|
172
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
142
173
|
const editor = this.editor = createEditor({
|
|
143
174
|
__kernel: this,
|
|
144
175
|
editable,
|
|
@@ -151,9 +182,9 @@ var init_kernel = __esmMin((() => {
|
|
|
151
182
|
theme: this.themes
|
|
152
183
|
});
|
|
153
184
|
this.headlessEditor = false;
|
|
185
|
+
this.registerRootLifecycle(editor);
|
|
154
186
|
this.editor.setRootElement(dom);
|
|
155
187
|
this.applyRootClassNames(dom);
|
|
156
|
-
registerEvent(editor, dom);
|
|
157
188
|
this.pluginsInstances.forEach((plugin) => {
|
|
158
189
|
plugin.onInit?.(editor);
|
|
159
190
|
});
|
|
@@ -193,6 +224,7 @@ var init_kernel = __esmMin((() => {
|
|
|
193
224
|
theme: this.themes
|
|
194
225
|
});
|
|
195
226
|
this.headlessEditor = false;
|
|
227
|
+
this.registerRootLifecycle(editor);
|
|
196
228
|
this.pluginsInstances.forEach((plugin) => {
|
|
197
229
|
plugin.onInit?.(editor);
|
|
198
230
|
});
|
|
@@ -513,6 +545,34 @@ var init_kernel = __esmMin((() => {
|
|
|
513
545
|
if (currentRootElement) currentRootElement.classList.remove(...classNames);
|
|
514
546
|
};
|
|
515
547
|
}
|
|
548
|
+
registerRootListener(listener) {
|
|
549
|
+
if (!this.editor || this.headlessEditor) return noop;
|
|
550
|
+
return this.editor.registerRootListener(listener);
|
|
551
|
+
}
|
|
552
|
+
getEditorId() {
|
|
553
|
+
return this.themes[EDITOR_THEME_KEY];
|
|
554
|
+
}
|
|
555
|
+
registerRootLifecycle(editor) {
|
|
556
|
+
this.unregisterRootLifecycle?.();
|
|
557
|
+
let rootEventCleanup;
|
|
558
|
+
const clearRootEvent = () => {
|
|
559
|
+
rootEventCleanup?.();
|
|
560
|
+
rootEventCleanup = void 0;
|
|
561
|
+
};
|
|
562
|
+
const unregisterRootListener = editor.registerRootListener((rootElement) => {
|
|
563
|
+
clearRootEvent();
|
|
564
|
+
if (!rootElement) {
|
|
565
|
+
unregisterEditorKernel(this.getEditorId());
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
569
|
+
rootEventCleanup = registerEvent(editor, rootElement);
|
|
570
|
+
});
|
|
571
|
+
this.unregisterRootLifecycle = () => {
|
|
572
|
+
clearRootEvent();
|
|
573
|
+
unregisterRootListener();
|
|
574
|
+
};
|
|
575
|
+
}
|
|
516
576
|
runBeforeEditorInitLifecycle() {
|
|
517
577
|
for (const hook of this.beforeEditorInitHooks) hook();
|
|
518
578
|
}
|
package/es/headless.d.ts
CHANGED
|
@@ -357,6 +357,7 @@ interface IEditor {
|
|
|
357
357
|
* @param dom
|
|
358
358
|
*/
|
|
359
359
|
setRootElement(dom: HTMLElement, editable?: boolean): LexicalEditor;
|
|
360
|
+
setRootElement(dom: null, editable?: boolean): LexicalEditor | null;
|
|
360
361
|
/**
|
|
361
362
|
* set editor selection
|
|
362
363
|
* @param selection
|
|
@@ -435,6 +436,17 @@ interface IEditorKernel extends IEditor {
|
|
|
435
436
|
* @returns unregister function
|
|
436
437
|
*/
|
|
437
438
|
registerRootClassName(className: string): () => void;
|
|
439
|
+
/**
|
|
440
|
+
* Register a callback for root attach/detach events.
|
|
441
|
+
*
|
|
442
|
+
* Call this after the kernel's Lexical editor has been initialized (usually
|
|
443
|
+
* from plugin onInit). Lexical immediately invokes a newly registered root
|
|
444
|
+
* listener with the current root, so listeners registered after the initial
|
|
445
|
+
* setRootElement still receive the first root attach. Headless kernels
|
|
446
|
+
* return a no-op cleanup because headless Lexical editors do not support
|
|
447
|
+
* root listeners.
|
|
448
|
+
*/
|
|
449
|
+
registerRootListener(listener: (rootElement: HTMLElement | null) => void): () => void;
|
|
438
450
|
/**
|
|
439
451
|
* Register service
|
|
440
452
|
* @param serviceId
|
package/es/headless.js
CHANGED
|
@@ -818,26 +818,46 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
818
818
|
return this.editor || null;
|
|
819
819
|
}
|
|
820
820
|
destroy() {
|
|
821
|
-
|
|
821
|
+
const editor = this.editor;
|
|
822
|
+
const editorId = this.getEditorId();
|
|
822
823
|
this.logger.info(`🗑️ Destroying editor with ${this.pluginsInstances.length} plugins`);
|
|
824
|
+
if (editor) try {
|
|
825
|
+
editor.setRootElement(null);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
this.logger.warn("Failed to detach editor root during destroy:", error);
|
|
828
|
+
}
|
|
829
|
+
unregisterEditorKernel(editorId);
|
|
823
830
|
try {
|
|
824
|
-
|
|
831
|
+
editor?.setEditorState(createEmptyEditorState());
|
|
825
832
|
} catch (error) {
|
|
826
833
|
this.logger.warn("Failed to reset editor state during destroy:", error);
|
|
827
834
|
}
|
|
828
|
-
this.
|
|
835
|
+
this.unregisterRootLifecycle?.();
|
|
836
|
+
this.unregisterRootLifecycle = void 0;
|
|
837
|
+
let destroyError;
|
|
829
838
|
this.pluginsInstances.forEach((plugin) => {
|
|
830
|
-
|
|
839
|
+
try {
|
|
840
|
+
plugin.destroy?.();
|
|
841
|
+
} catch (error) {
|
|
842
|
+
destroyError ??= error;
|
|
843
|
+
}
|
|
831
844
|
});
|
|
832
845
|
this.pluginsInstances = [];
|
|
833
|
-
this.
|
|
846
|
+
this.dataTypeMap.clear();
|
|
847
|
+
this.decorators = {};
|
|
834
848
|
this.nodeTransforms = [];
|
|
849
|
+
this.nodes = [];
|
|
850
|
+
this.beforeEditorInitHooks = [];
|
|
835
851
|
this.rootClassNames.clear();
|
|
836
852
|
this.serviceMap.clear();
|
|
837
|
-
this.
|
|
838
|
-
this.
|
|
853
|
+
this.themes = { [EDITOR_THEME_KEY]: generateEditorId() };
|
|
854
|
+
this.historyState = createEmptyHistoryState();
|
|
855
|
+
this._commands.clear();
|
|
856
|
+
this._commandsClean.clear();
|
|
857
|
+
this.editor = void 0;
|
|
839
858
|
this.headlessEditor = false;
|
|
840
859
|
this.logger.info("✅ Editor destroyed");
|
|
860
|
+
if (destroyError) throw destroyError;
|
|
841
861
|
}
|
|
842
862
|
getRootElement() {
|
|
843
863
|
if (this.headlessEditor) return null;
|
|
@@ -845,12 +865,23 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
845
865
|
}
|
|
846
866
|
setRootElement(dom, editable = true) {
|
|
847
867
|
if (this.editor) {
|
|
848
|
-
if (this.headlessEditor)
|
|
868
|
+
if (this.headlessEditor) {
|
|
869
|
+
if (dom === null) return this.editor;
|
|
870
|
+
throw new Error("Headless editor cannot be attached to a root element.");
|
|
871
|
+
}
|
|
849
872
|
this.logger.warn("[Editor] Editor is already initialized, updating root element only");
|
|
873
|
+
if (dom) registerEditorKernel(this.getEditorId(), this);
|
|
850
874
|
this.editor.setRootElement(dom);
|
|
851
|
-
|
|
875
|
+
if (dom) {
|
|
876
|
+
this.editor.setEditable(editable);
|
|
877
|
+
this.applyRootClassNames(dom);
|
|
878
|
+
}
|
|
852
879
|
return this.editor;
|
|
853
880
|
}
|
|
881
|
+
if (dom === null) {
|
|
882
|
+
unregisterEditorKernel(this.getEditorId());
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
854
885
|
if (this.pluginsInstances.length === 0) {
|
|
855
886
|
this.logger.info(`🔌 Initializing ${this.plugins.length} plugins`);
|
|
856
887
|
for (const plugin of this.plugins) {
|
|
@@ -861,7 +892,7 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
861
892
|
this.runBeforeEditorInitLifecycle();
|
|
862
893
|
const resolvedNodes = this.resolveNodesForInitialization();
|
|
863
894
|
this.logger.info(`📝 Creating editor with ${resolvedNodes.length} nodes`);
|
|
864
|
-
registerEditorKernel(this.
|
|
895
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
865
896
|
const editor = this.editor = createEditor({
|
|
866
897
|
__kernel: this,
|
|
867
898
|
editable,
|
|
@@ -874,9 +905,9 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
874
905
|
theme: this.themes
|
|
875
906
|
});
|
|
876
907
|
this.headlessEditor = false;
|
|
908
|
+
this.registerRootLifecycle(editor);
|
|
877
909
|
this.editor.setRootElement(dom);
|
|
878
910
|
this.applyRootClassNames(dom);
|
|
879
|
-
registerEvent(editor, dom);
|
|
880
911
|
this.pluginsInstances.forEach((plugin) => {
|
|
881
912
|
plugin.onInit?.(editor);
|
|
882
913
|
});
|
|
@@ -916,6 +947,7 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
916
947
|
theme: this.themes
|
|
917
948
|
});
|
|
918
949
|
this.headlessEditor = false;
|
|
950
|
+
this.registerRootLifecycle(editor);
|
|
919
951
|
this.pluginsInstances.forEach((plugin) => {
|
|
920
952
|
plugin.onInit?.(editor);
|
|
921
953
|
});
|
|
@@ -1236,6 +1268,34 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
1236
1268
|
if (currentRootElement) currentRootElement.classList.remove(...classNames);
|
|
1237
1269
|
};
|
|
1238
1270
|
}
|
|
1271
|
+
registerRootListener(listener) {
|
|
1272
|
+
if (!this.editor || this.headlessEditor) return noop;
|
|
1273
|
+
return this.editor.registerRootListener(listener);
|
|
1274
|
+
}
|
|
1275
|
+
getEditorId() {
|
|
1276
|
+
return this.themes[EDITOR_THEME_KEY];
|
|
1277
|
+
}
|
|
1278
|
+
registerRootLifecycle(editor) {
|
|
1279
|
+
this.unregisterRootLifecycle?.();
|
|
1280
|
+
let rootEventCleanup;
|
|
1281
|
+
const clearRootEvent = () => {
|
|
1282
|
+
rootEventCleanup?.();
|
|
1283
|
+
rootEventCleanup = void 0;
|
|
1284
|
+
};
|
|
1285
|
+
const unregisterRootListener = editor.registerRootListener((rootElement) => {
|
|
1286
|
+
clearRootEvent();
|
|
1287
|
+
if (!rootElement) {
|
|
1288
|
+
unregisterEditorKernel(this.getEditorId());
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
1292
|
+
rootEventCleanup = registerEvent(editor, rootElement);
|
|
1293
|
+
});
|
|
1294
|
+
this.unregisterRootLifecycle = () => {
|
|
1295
|
+
clearRootEvent();
|
|
1296
|
+
unregisterRootListener();
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1239
1299
|
runBeforeEditorInitLifecycle() {
|
|
1240
1300
|
for (const hook of this.beforeEditorInitHooks) hook();
|
|
1241
1301
|
}
|
|
@@ -6076,7 +6136,18 @@ const CommonPlugin = class extends KernelPlugin {
|
|
|
6076
6136
|
if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
|
|
6077
6137
|
return false;
|
|
6078
6138
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
6079
|
-
|
|
6139
|
+
let dragonCleanup;
|
|
6140
|
+
const clearDragon = () => {
|
|
6141
|
+
dragonCleanup?.();
|
|
6142
|
+
dragonCleanup = void 0;
|
|
6143
|
+
};
|
|
6144
|
+
this.register(this.kernel.registerRootListener((rootElement) => {
|
|
6145
|
+
clearDragon();
|
|
6146
|
+
if (!rootElement || !CAN_USE_DOM) return;
|
|
6147
|
+
dragonCleanup = registerDragonSupport(editor);
|
|
6148
|
+
}));
|
|
6149
|
+
this.register(clearDragon);
|
|
6150
|
+
this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), registerHistory(editor, this.kernel.getHistoryState(), 300), registerBlockBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
|
|
6080
6151
|
const selection = $getSelection();
|
|
6081
6152
|
if (!$isRangeSelection(selection)) return false;
|
|
6082
6153
|
const { inCell, inTable } = $isCursorInTable(selection);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
|
|
2
2
|
import { $isCursorInQuote, $isCursorInTable, createBlockNode, init_utils, sampleReader } from "../utils/index.js";
|
|
3
3
|
import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
|
|
4
|
-
import { init_utils as init_utils$1, noop } from "../../../editor-kernel/utils.js";
|
|
5
4
|
import { registerCommands } from "../command/index.js";
|
|
6
5
|
import { CursorNode, registerCursorNode } from "../node/cursor.js";
|
|
7
6
|
import { ILitexmlService } from "../../litexml/service/litexml-service.js";
|
|
@@ -20,7 +19,6 @@ import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, Hea
|
|
|
20
19
|
import { CAN_USE_DOM } from "@lexical/utils";
|
|
21
20
|
import { registerDragonSupport } from "@lexical/dragon";
|
|
22
21
|
//#region src/plugins/common/plugin/index.ts
|
|
23
|
-
init_utils$1();
|
|
24
22
|
init_helper();
|
|
25
23
|
init_plugin();
|
|
26
24
|
init_utils();
|
|
@@ -232,7 +230,18 @@ const CommonPlugin = class extends KernelPlugin {
|
|
|
232
230
|
if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
|
|
233
231
|
return false;
|
|
234
232
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
235
|
-
|
|
233
|
+
let dragonCleanup;
|
|
234
|
+
const clearDragon = () => {
|
|
235
|
+
dragonCleanup?.();
|
|
236
|
+
dragonCleanup = void 0;
|
|
237
|
+
};
|
|
238
|
+
this.register(this.kernel.registerRootListener((rootElement) => {
|
|
239
|
+
clearDragon();
|
|
240
|
+
if (!rootElement || !CAN_USE_DOM) return;
|
|
241
|
+
dragonCleanup = registerDragonSupport(editor);
|
|
242
|
+
}));
|
|
243
|
+
this.register(clearDragon);
|
|
244
|
+
this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), registerHistory(editor, this.kernel.getHistoryState(), 300), registerBlockBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
|
|
236
245
|
const selection = $getSelection();
|
|
237
246
|
if (!$isRangeSelection(selection)) return false;
|
|
238
247
|
const { inCell, inTable } = $isCursorInTable(selection);
|
|
@@ -46,6 +46,12 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
|
|
|
46
46
|
}, [markdownOption]);
|
|
47
47
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
48
48
|
const { props: { type, content, placeholder, lineEmptyPlaceholder } } = Children.only(children);
|
|
49
|
+
const initialDocumentRef = useRef({
|
|
50
|
+
content,
|
|
51
|
+
type
|
|
52
|
+
});
|
|
53
|
+
const editableRef = useRef(editable);
|
|
54
|
+
editableRef.current = editable;
|
|
49
55
|
useLayoutEffect(() => {
|
|
50
56
|
editor.registerPlugin(MarkdownPlugin, {
|
|
51
57
|
autoFormatMarkdown,
|
|
@@ -76,13 +82,22 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
|
|
|
76
82
|
]);
|
|
77
83
|
useEffect(() => {
|
|
78
84
|
const container = editorContainerRef.current;
|
|
79
|
-
if (container
|
|
80
|
-
|
|
81
|
-
editor.
|
|
85
|
+
if (!container) return;
|
|
86
|
+
if (!editor.getLexicalEditor()) {
|
|
87
|
+
editor.setRootElement(container, editableRef.current);
|
|
88
|
+
editor.setDocument(initialDocumentRef.current.type, initialDocumentRef.current.content);
|
|
82
89
|
setIsInitialized(true);
|
|
83
|
-
}
|
|
90
|
+
} else if (editor.getRootElement() !== container) editor.setRootElement(container, editableRef.current);
|
|
91
|
+
return () => {
|
|
92
|
+
if (editor.getRootElement() === container) editor.setRootElement(null);
|
|
93
|
+
};
|
|
94
|
+
}, [editor]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!isInitialized) return;
|
|
97
|
+
const lexicalEditor = editor.getLexicalEditor();
|
|
98
|
+
if (!lexicalEditor) return;
|
|
84
99
|
let previousContent;
|
|
85
|
-
return
|
|
100
|
+
return lexicalEditor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
|
|
86
101
|
onChange?.(editor);
|
|
87
102
|
if (onTextChange && (dirtyElements.size > 0 || dirtyLeaves.size > 0)) {
|
|
88
103
|
const currentContent = JSON.stringify(editor.getDocument(type));
|
|
@@ -95,16 +110,19 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
|
|
|
95
110
|
});
|
|
96
111
|
}, [
|
|
97
112
|
editor,
|
|
113
|
+
isInitialized,
|
|
98
114
|
type,
|
|
99
|
-
content,
|
|
100
115
|
onChange,
|
|
101
|
-
onTextChange
|
|
102
|
-
isInitialized
|
|
116
|
+
onTextChange
|
|
103
117
|
]);
|
|
104
118
|
useEffect(() => {
|
|
105
119
|
if (!isInitialized) return;
|
|
106
120
|
if (typeof editable === "boolean") editor.setEditable(editable);
|
|
107
|
-
}, [
|
|
121
|
+
}, [
|
|
122
|
+
editor,
|
|
123
|
+
isInitialized,
|
|
124
|
+
editable
|
|
125
|
+
]);
|
|
108
126
|
useEffect(() => {
|
|
109
127
|
if (editor && onPressEnter) return editor.registerHighCommand(KEY_DOWN_COMMAND, (event) => {
|
|
110
128
|
if (event.key === "Enter" && !event.isComposing && onPressEnter({
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { IEditor } from "../../types/kernel.js";
|
|
2
2
|
import "../../types/index.js";
|
|
3
3
|
//#region src/react/hooks/useEditor.d.ts
|
|
4
|
-
|
|
4
|
+
interface UseEditorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Destroy the kernel when the owning component is cleaned up.
|
|
7
|
+
*
|
|
8
|
+
* The default is false because React Activity also runs effect cleanup while
|
|
9
|
+
* hiding a subtree. Detaching the root keeps the editor reversible and lets
|
|
10
|
+
* the same kernel be attached again when the subtree becomes visible.
|
|
11
|
+
*
|
|
12
|
+
* `autoDestroy: true` is mutually exclusive with React Activity: Activity
|
|
13
|
+
* cannot distinguish a hidden subtree from a permanently unmounted owner,
|
|
14
|
+
* so its cleanup would destroy the editor during a hide. Use the default
|
|
15
|
+
* reversible detach with Activity, and call `destroy()` (or unmount the
|
|
16
|
+
* owner with `autoDestroy`) only when the instance is no longer needed.
|
|
17
|
+
*
|
|
18
|
+
* Root detach releases the kernel from the library's global root registry
|
|
19
|
+
* and DOM/dragon listeners. It does not guarantee garbage collection when
|
|
20
|
+
* application code keeps the `IEditor` in a store, provider, or other
|
|
21
|
+
* long-lived reference. Remove that reference, unmount the owner, or use
|
|
22
|
+
* `autoDestroy: true`/`destroy()` to release the editor's runtime resources.
|
|
23
|
+
*/
|
|
24
|
+
autoDestroy?: boolean;
|
|
25
|
+
}
|
|
26
|
+
declare const useEditor: ({ autoDestroy }?: UseEditorOptions) => IEditor;
|
|
5
27
|
//#endregion
|
|
6
|
-
export { useEditor };
|
|
28
|
+
export { UseEditorOptions, useEditor };
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import Editor from "../../editor-kernel/index.js";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
3
|
//#region src/react/hooks/useEditor.ts
|
|
4
|
-
const useEditor = () => {
|
|
5
|
-
|
|
4
|
+
const useEditor = ({ autoDestroy = false } = {}) => {
|
|
5
|
+
const editor = useMemo(() => Editor.createEditor(), []);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
return () => {
|
|
8
|
+
if (autoDestroy) {
|
|
9
|
+
editor.destroy();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (editor.getLexicalEditor()) editor.setRootElement(null);
|
|
13
|
+
};
|
|
14
|
+
}, [autoDestroy, editor]);
|
|
15
|
+
return editor;
|
|
6
16
|
};
|
|
7
17
|
//#endregion
|
|
8
18
|
export { useEditor };
|
package/es/react.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import "./react/ChatInputActions/index.js";
|
|
|
13
13
|
import { CodeLanguageSelectProps } from "./react/CodeLanguageSelect/type.js";
|
|
14
14
|
import CodeLanguageSelect from "./react/CodeLanguageSelect/CodeLanguageSelect.js";
|
|
15
15
|
import "./react/CodeLanguageSelect/index.js";
|
|
16
|
-
import { useEditor } from "./react/hooks/useEditor.js";
|
|
16
|
+
import { UseEditorOptions, useEditor } from "./react/hooks/useEditor.js";
|
|
17
17
|
import { EditorState, useEditorState } from "./react/hooks/useEditorState/index.js";
|
|
18
18
|
import { EditorProps } from "./react/Editor/type.js";
|
|
19
19
|
import { withProps } from "./react/Editor/utils.js";
|
|
@@ -25,4 +25,4 @@ import "./react/FloatActions/index.js";
|
|
|
25
25
|
import { SendButtonProps } from "./react/SendButton/type.js";
|
|
26
26
|
import SendButton from "./react/SendButton/SendButton.js";
|
|
27
27
|
import "./react/SendButton/index.js";
|
|
28
|
-
export { ANCHOR_PADDING_CSS_VAR, ChatInput, ChatInputActionBar, type ChatInputActionBarProps, type ChatInputActionEvent, ChatInputActions, type ChatInputActionsProps, type ChatInputProps, CodeLanguageSelect, type CodeLanguageSelectProps, type CollaborationProviderFactory, DEFAULT_BLOCK_ANCHOR_PADDING, Editor, type EditorCollaborationConfig, type EditorProps, EditorProvider, type EditorProviderConfig, type EditorProviderProps, type EditorState, FloatActions, type FloatActionsProps, SendButton, type SendButtonProps, useEditor, useEditorContent, useEditorState, withProps };
|
|
28
|
+
export { ANCHOR_PADDING_CSS_VAR, ChatInput, ChatInputActionBar, type ChatInputActionBarProps, type ChatInputActionEvent, ChatInputActions, type ChatInputActionsProps, type ChatInputProps, CodeLanguageSelect, type CodeLanguageSelectProps, type CollaborationProviderFactory, DEFAULT_BLOCK_ANCHOR_PADDING, Editor, type EditorCollaborationConfig, type EditorProps, EditorProvider, type EditorProviderConfig, type EditorProviderProps, type EditorState, FloatActions, type FloatActionsProps, SendButton, type SendButtonProps, type UseEditorOptions, useEditor, useEditorContent, useEditorState, withProps };
|
package/es/types/kernel.d.ts
CHANGED
|
@@ -225,6 +225,7 @@ interface IEditor {
|
|
|
225
225
|
* @param dom
|
|
226
226
|
*/
|
|
227
227
|
setRootElement(dom: HTMLElement, editable?: boolean): LexicalEditor;
|
|
228
|
+
setRootElement(dom: null, editable?: boolean): LexicalEditor | null;
|
|
228
229
|
/**
|
|
229
230
|
* set editor selection
|
|
230
231
|
* @param selection
|
|
@@ -303,6 +304,17 @@ interface IEditorKernel extends IEditor {
|
|
|
303
304
|
* @returns unregister function
|
|
304
305
|
*/
|
|
305
306
|
registerRootClassName(className: string): () => void;
|
|
307
|
+
/**
|
|
308
|
+
* Register a callback for root attach/detach events.
|
|
309
|
+
*
|
|
310
|
+
* Call this after the kernel's Lexical editor has been initialized (usually
|
|
311
|
+
* from plugin onInit). Lexical immediately invokes a newly registered root
|
|
312
|
+
* listener with the current root, so listeners registered after the initial
|
|
313
|
+
* setRootElement still receive the first root attach. Headless kernels
|
|
314
|
+
* return a no-op cleanup because headless Lexical editors do not support
|
|
315
|
+
* root listeners.
|
|
316
|
+
*/
|
|
317
|
+
registerRootListener(listener: (rootElement: HTMLElement | null) => void): () => void;
|
|
306
318
|
/**
|
|
307
319
|
* Register service
|
|
308
320
|
* @param serviceId
|
package/package.json
CHANGED