@domternal/react 0.4.0
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 +41 -0
- package/dist/Domternal.d.ts +79 -0
- package/dist/Domternal.d.ts.map +1 -0
- package/dist/DomternalEditor.d.ts +48 -0
- package/dist/DomternalEditor.d.ts.map +1 -0
- package/dist/DomternalFloatingMenu.d.ts +13 -0
- package/dist/DomternalFloatingMenu.d.ts.map +1 -0
- package/dist/EditorContent.d.ts +22 -0
- package/dist/EditorContent.d.ts.map +1 -0
- package/dist/EditorContext.d.ts +38 -0
- package/dist/EditorContext.d.ts.map +1 -0
- package/dist/bubble-menu/DomternalBubbleMenu.d.ts +21 -0
- package/dist/bubble-menu/DomternalBubbleMenu.d.ts.map +1 -0
- package/dist/bubble-menu/useBubbleMenu.d.ts +26 -0
- package/dist/bubble-menu/useBubbleMenu.d.ts.map +1 -0
- package/dist/emoji-picker/DomternalEmojiPicker.d.ts +10 -0
- package/dist/emoji-picker/DomternalEmojiPicker.d.ts.map +1 -0
- package/dist/emoji-picker/useEmojiPicker.d.ts +22 -0
- package/dist/emoji-picker/useEmojiPicker.d.ts.map +1 -0
- package/dist/index.d.ts +467 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1720 -0
- package/dist/index.js.map +1 -0
- package/dist/node-views/NodeViewContent.d.ts +11 -0
- package/dist/node-views/NodeViewContent.d.ts.map +1 -0
- package/dist/node-views/NodeViewWrapper.d.ts +11 -0
- package/dist/node-views/NodeViewWrapper.d.ts.map +1 -0
- package/dist/node-views/ReactNodeViewContext.d.ts +12 -0
- package/dist/node-views/ReactNodeViewContext.d.ts.map +1 -0
- package/dist/node-views/ReactNodeViewRenderer.d.ts +101 -0
- package/dist/node-views/ReactNodeViewRenderer.d.ts.map +1 -0
- package/dist/toolbar/DomternalToolbar.d.ts +11 -0
- package/dist/toolbar/DomternalToolbar.d.ts.map +1 -0
- package/dist/toolbar/ToolbarButton.d.ts +14 -0
- package/dist/toolbar/ToolbarButton.d.ts.map +1 -0
- package/dist/toolbar/ToolbarDropdown.d.ts +16 -0
- package/dist/toolbar/ToolbarDropdown.d.ts.map +1 -0
- package/dist/toolbar/ToolbarDropdownPanel.d.ts +9 -0
- package/dist/toolbar/ToolbarDropdownPanel.d.ts.map +1 -0
- package/dist/toolbar/useComputedStyle.d.ts +12 -0
- package/dist/toolbar/useComputedStyle.d.ts.map +1 -0
- package/dist/toolbar/useKeyboardNav.d.ts +6 -0
- package/dist/toolbar/useKeyboardNav.d.ts.map +1 -0
- package/dist/toolbar/useToolbarController.d.ts +19 -0
- package/dist/toolbar/useToolbarController.d.ts.map +1 -0
- package/dist/toolbar/useToolbarIcons.d.ts +11 -0
- package/dist/toolbar/useToolbarIcons.d.ts.map +1 -0
- package/dist/toolbar/useTooltip.d.ts +5 -0
- package/dist/toolbar/useTooltip.d.ts.map +1 -0
- package/dist/useEditor.d.ts +79 -0
- package/dist/useEditor.d.ts.map +1 -0
- package/dist/useEditorState.d.ts +27 -0
- package/dist/useEditorState.d.ts.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1720 @@
|
|
|
1
|
+
import { createContext, forwardRef, useImperativeHandle, useRef, useEffect, useState, useMemo, useCallback, useSyncExternalStore, useContext, Fragment, createElement } from 'react';
|
|
2
|
+
import { Editor, Document, Paragraph, Text, BaseKeymap, History, PluginKey, createFloatingMenuPlugin, ToolbarController, positionFloatingOnce, defaultIcons, createBubbleMenuPlugin } from '@domternal/core';
|
|
3
|
+
export { Editor, generateHTML, generateJSON, generateText } from '@domternal/core';
|
|
4
|
+
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
5
|
+
import { createRoot } from 'react-dom/client';
|
|
6
|
+
|
|
7
|
+
// src/useEditor.ts
|
|
8
|
+
var DEFAULT_EXTENSIONS = [Document, Paragraph, Text, BaseKeymap, History];
|
|
9
|
+
function useEditor(options = {}, deps) {
|
|
10
|
+
const {
|
|
11
|
+
extensions = [],
|
|
12
|
+
content = "",
|
|
13
|
+
editable = true,
|
|
14
|
+
autofocus = false,
|
|
15
|
+
outputFormat = "html"
|
|
16
|
+
} = options;
|
|
17
|
+
const [editor, setEditor] = useState(null);
|
|
18
|
+
const editorRef = useRef(null);
|
|
19
|
+
const instanceRef = useRef(null);
|
|
20
|
+
const pendingContentRef = useRef(null);
|
|
21
|
+
const callbacksRef = useRef(options);
|
|
22
|
+
callbacksRef.current = options;
|
|
23
|
+
const contentRef = useRef(content);
|
|
24
|
+
contentRef.current = content;
|
|
25
|
+
const formatRef = useRef(outputFormat);
|
|
26
|
+
formatRef.current = outputFormat;
|
|
27
|
+
const extensionsRef = useRef(extensions);
|
|
28
|
+
const depsRef = useRef(deps);
|
|
29
|
+
function wireEvents(ed) {
|
|
30
|
+
ed.on("transaction", ({ transaction }) => {
|
|
31
|
+
const cbs = callbacksRef.current;
|
|
32
|
+
if (transaction.docChanged) {
|
|
33
|
+
cbs.onUpdate?.({ editor: ed });
|
|
34
|
+
}
|
|
35
|
+
if (!transaction.docChanged && transaction.selectionSet) {
|
|
36
|
+
cbs.onSelectionChange?.({ editor: ed });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
ed.on("focus", ({ event }) => {
|
|
40
|
+
callbacksRef.current.onFocus?.({ editor: ed, event });
|
|
41
|
+
});
|
|
42
|
+
ed.on("blur", ({ event }) => {
|
|
43
|
+
callbacksRef.current.onBlur?.({ editor: ed, event });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function createEditorInstance(element, initialContent, focus) {
|
|
47
|
+
const ed = new Editor({
|
|
48
|
+
element,
|
|
49
|
+
extensions: [...DEFAULT_EXTENSIONS, ...extensions],
|
|
50
|
+
content: initialContent,
|
|
51
|
+
editable,
|
|
52
|
+
autofocus: focus
|
|
53
|
+
});
|
|
54
|
+
wireEvents(ed);
|
|
55
|
+
instanceRef.current = ed;
|
|
56
|
+
extensionsRef.current = extensions;
|
|
57
|
+
depsRef.current = deps;
|
|
58
|
+
setEditor(ed);
|
|
59
|
+
callbacksRef.current.onCreate?.(ed);
|
|
60
|
+
return ed;
|
|
61
|
+
}
|
|
62
|
+
function destroyCurrentEditor() {
|
|
63
|
+
const current = instanceRef.current;
|
|
64
|
+
if (current && !current.isDestroyed) {
|
|
65
|
+
pendingContentRef.current = current.getJSON();
|
|
66
|
+
callbacksRef.current.onDestroy?.();
|
|
67
|
+
current.destroy();
|
|
68
|
+
}
|
|
69
|
+
instanceRef.current = null;
|
|
70
|
+
setEditor(null);
|
|
71
|
+
}
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const element = editorRef.current ?? document.createElement("div");
|
|
74
|
+
const initialContent = pendingContentRef.current ?? content;
|
|
75
|
+
pendingContentRef.current = null;
|
|
76
|
+
createEditorInstance(element, initialContent, autofocus);
|
|
77
|
+
return () => {
|
|
78
|
+
destroyCurrentEditor();
|
|
79
|
+
};
|
|
80
|
+
}, []);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (instanceRef.current && !instanceRef.current.isDestroyed) {
|
|
83
|
+
instanceRef.current.setEditable(editable);
|
|
84
|
+
}
|
|
85
|
+
}, [editable]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!instanceRef.current || instanceRef.current.isDestroyed) return;
|
|
88
|
+
if (extensions === extensionsRef.current) return;
|
|
89
|
+
const element = instanceRef.current.view.dom.parentElement ?? document.createElement("div");
|
|
90
|
+
destroyCurrentEditor();
|
|
91
|
+
const initialContent = pendingContentRef.current ?? "";
|
|
92
|
+
pendingContentRef.current = null;
|
|
93
|
+
createEditorInstance(element, initialContent, false);
|
|
94
|
+
}, [extensions]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!deps || !instanceRef.current || instanceRef.current.isDestroyed) return;
|
|
97
|
+
if (depsRef.current === deps) return;
|
|
98
|
+
if (depsRef.current && deps.length === depsRef.current.length && deps.every((d, i) => d === depsRef.current[i])) return;
|
|
99
|
+
const element = instanceRef.current.view.dom.parentElement ?? document.createElement("div");
|
|
100
|
+
destroyCurrentEditor();
|
|
101
|
+
const initialContent = pendingContentRef.current ?? "";
|
|
102
|
+
pendingContentRef.current = null;
|
|
103
|
+
createEditorInstance(element, initialContent, false);
|
|
104
|
+
}, deps ?? []);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const ed = instanceRef.current;
|
|
107
|
+
if (!ed || ed.isDestroyed) return;
|
|
108
|
+
const format = formatRef.current;
|
|
109
|
+
if (format === "html") {
|
|
110
|
+
if (content !== ed.getHTML()) {
|
|
111
|
+
ed.setContent(content, false);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
if (JSON.stringify(content) !== JSON.stringify(ed.getJSON())) {
|
|
115
|
+
ed.setContent(content, false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}, [content]);
|
|
119
|
+
return { editor, editorRef };
|
|
120
|
+
}
|
|
121
|
+
function useEditorState(editor, selector) {
|
|
122
|
+
if (selector) {
|
|
123
|
+
return useEditorStateSelector(editor, selector);
|
|
124
|
+
}
|
|
125
|
+
return useEditorStateFull(editor);
|
|
126
|
+
}
|
|
127
|
+
function useEditorStateFull(editor) {
|
|
128
|
+
const [state, setState] = useState(() => getFullState(editor));
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!editor || editor.isDestroyed) {
|
|
131
|
+
setState(getFullState(null));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setState(getFullState(editor));
|
|
135
|
+
const onTransaction = ({ transaction }) => {
|
|
136
|
+
setState((prev) => {
|
|
137
|
+
if (!transaction.docChanged) {
|
|
138
|
+
const editable2 = editor.isEditable;
|
|
139
|
+
if (prev.isEditable === editable2) return prev;
|
|
140
|
+
return { ...prev, isEditable: editable2 };
|
|
141
|
+
}
|
|
142
|
+
const html = editor.getHTML();
|
|
143
|
+
const json = editor.getJSON();
|
|
144
|
+
const empty = editor.isEmpty;
|
|
145
|
+
const editable = editor.isEditable;
|
|
146
|
+
if (prev.htmlContent === html && prev.isEmpty === empty && prev.isEditable === editable) return prev;
|
|
147
|
+
return { ...prev, htmlContent: html, jsonContent: json, isEmpty: empty, isEditable: editable };
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
const onFocus = () => {
|
|
151
|
+
setState((prev) => prev.isFocused ? prev : { ...prev, isFocused: true });
|
|
152
|
+
};
|
|
153
|
+
const onBlur = () => {
|
|
154
|
+
setState((prev) => !prev.isFocused ? prev : { ...prev, isFocused: false });
|
|
155
|
+
};
|
|
156
|
+
editor.on("transaction", onTransaction);
|
|
157
|
+
editor.on("focus", onFocus);
|
|
158
|
+
editor.on("blur", onBlur);
|
|
159
|
+
return () => {
|
|
160
|
+
editor.off("transaction", onTransaction);
|
|
161
|
+
editor.off("focus", onFocus);
|
|
162
|
+
editor.off("blur", onBlur);
|
|
163
|
+
};
|
|
164
|
+
}, [editor]);
|
|
165
|
+
return state;
|
|
166
|
+
}
|
|
167
|
+
function getFullState(editor) {
|
|
168
|
+
if (!editor || editor.isDestroyed) {
|
|
169
|
+
return { htmlContent: "", jsonContent: null, isEmpty: true, isFocused: false, isEditable: true };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
htmlContent: editor.getHTML(),
|
|
173
|
+
jsonContent: editor.getJSON(),
|
|
174
|
+
isEmpty: editor.isEmpty,
|
|
175
|
+
isFocused: editor.isFocused,
|
|
176
|
+
isEditable: editor.isEditable
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function useEditorStateSelector(editor, selector) {
|
|
180
|
+
const selectorRef = useRef(selector);
|
|
181
|
+
selectorRef.current = selector;
|
|
182
|
+
const subscribe = useCallback(
|
|
183
|
+
(callback) => {
|
|
184
|
+
if (!editor || editor.isDestroyed) return () => {
|
|
185
|
+
};
|
|
186
|
+
editor.on("transaction", callback);
|
|
187
|
+
editor.on("focus", callback);
|
|
188
|
+
editor.on("blur", callback);
|
|
189
|
+
return () => {
|
|
190
|
+
editor.off("transaction", callback);
|
|
191
|
+
editor.off("focus", callback);
|
|
192
|
+
editor.off("blur", callback);
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
[editor]
|
|
196
|
+
);
|
|
197
|
+
const getSnapshot = useCallback(() => {
|
|
198
|
+
if (!editor || editor.isDestroyed) return void 0;
|
|
199
|
+
return selectorRef.current(editor);
|
|
200
|
+
}, [editor]);
|
|
201
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
202
|
+
}
|
|
203
|
+
var EditorContext = createContext({ editor: null });
|
|
204
|
+
function EditorProvider({ editor, children }) {
|
|
205
|
+
const value = useMemo(() => ({ editor }), [editor]);
|
|
206
|
+
return /* @__PURE__ */ jsx(EditorContext.Provider, { value, children });
|
|
207
|
+
}
|
|
208
|
+
function useCurrentEditor() {
|
|
209
|
+
return useContext(EditorContext);
|
|
210
|
+
}
|
|
211
|
+
function useToolbarController(editor, layout) {
|
|
212
|
+
const [groups, setGroups] = useState([]);
|
|
213
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
214
|
+
const [openDropdown, setOpenDropdown] = useState(null);
|
|
215
|
+
const [activeVersion, setActiveVersion] = useState(0);
|
|
216
|
+
const controllerRef = useRef(null);
|
|
217
|
+
const toolbarRef = useRef(null);
|
|
218
|
+
const cleanupFloatingRef = useRef(null);
|
|
219
|
+
const clickOutsideRef = useRef(null);
|
|
220
|
+
const dismissOverlayRef = useRef(null);
|
|
221
|
+
const editorElRef = useRef(null);
|
|
222
|
+
const syncStateRafRef = useRef(0);
|
|
223
|
+
const syncState = useCallback(() => {
|
|
224
|
+
cancelAnimationFrame(syncStateRafRef.current);
|
|
225
|
+
syncStateRafRef.current = requestAnimationFrame(() => {
|
|
226
|
+
const controller = controllerRef.current;
|
|
227
|
+
if (!controller) return;
|
|
228
|
+
const controllerGroups = controller.groups;
|
|
229
|
+
setGroups((prev) => prev.length !== controllerGroups.length ? controllerGroups : prev);
|
|
230
|
+
setFocusedIndex(controller.focusedIndex);
|
|
231
|
+
setOpenDropdown(controller.openDropdown);
|
|
232
|
+
setActiveVersion((v) => v + 1);
|
|
233
|
+
});
|
|
234
|
+
}, []);
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (!editor || editor.isDestroyed) return;
|
|
237
|
+
const controller = new ToolbarController(
|
|
238
|
+
editor,
|
|
239
|
+
syncState,
|
|
240
|
+
layout
|
|
241
|
+
);
|
|
242
|
+
controller.subscribe();
|
|
243
|
+
controllerRef.current = controller;
|
|
244
|
+
syncState();
|
|
245
|
+
const clickOutside = (e) => {
|
|
246
|
+
if (controller.openDropdown && toolbarRef.current && !toolbarRef.current.contains(e.target)) {
|
|
247
|
+
cleanupFloatingRef.current?.();
|
|
248
|
+
cleanupFloatingRef.current = null;
|
|
249
|
+
controller.closeDropdown();
|
|
250
|
+
syncState();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
clickOutsideRef.current = clickOutside;
|
|
254
|
+
document.addEventListener("mousedown", clickOutside);
|
|
255
|
+
const editorEl = editor.view.dom.closest(".dm-editor");
|
|
256
|
+
editorElRef.current = editorEl;
|
|
257
|
+
if (editorEl) {
|
|
258
|
+
const dismiss = () => {
|
|
259
|
+
if (controller.openDropdown) {
|
|
260
|
+
cleanupFloatingRef.current?.();
|
|
261
|
+
cleanupFloatingRef.current = null;
|
|
262
|
+
controller.closeDropdown();
|
|
263
|
+
syncState();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
dismissOverlayRef.current = dismiss;
|
|
267
|
+
editorEl.addEventListener("dm:dismiss-overlays", dismiss);
|
|
268
|
+
}
|
|
269
|
+
return () => {
|
|
270
|
+
cancelAnimationFrame(syncStateRafRef.current);
|
|
271
|
+
cleanupFloatingRef.current?.();
|
|
272
|
+
cleanupFloatingRef.current = null;
|
|
273
|
+
if (clickOutsideRef.current) {
|
|
274
|
+
document.removeEventListener("mousedown", clickOutsideRef.current);
|
|
275
|
+
clickOutsideRef.current = null;
|
|
276
|
+
}
|
|
277
|
+
if (dismissOverlayRef.current && editorElRef.current) {
|
|
278
|
+
editorElRef.current.removeEventListener("dm:dismiss-overlays", dismissOverlayRef.current);
|
|
279
|
+
dismissOverlayRef.current = null;
|
|
280
|
+
editorElRef.current = null;
|
|
281
|
+
}
|
|
282
|
+
controller.destroy();
|
|
283
|
+
controllerRef.current = null;
|
|
284
|
+
};
|
|
285
|
+
}, [editor, layout, syncState]);
|
|
286
|
+
const isActive = useCallback((name) => {
|
|
287
|
+
return controllerRef.current?.activeMap.get(name) ?? false;
|
|
288
|
+
}, []);
|
|
289
|
+
const isDisabled = useCallback((name) => {
|
|
290
|
+
return controllerRef.current?.disabledMap.get(name) ?? false;
|
|
291
|
+
}, []);
|
|
292
|
+
const isDropdownActive = useCallback((dropdown) => {
|
|
293
|
+
if (dropdown.layout === "grid") return false;
|
|
294
|
+
if (dropdown.dynamicLabel) return false;
|
|
295
|
+
const controller = controllerRef.current;
|
|
296
|
+
if (!controller) return false;
|
|
297
|
+
return dropdown.items.some((item) => controller.activeMap.get(item.name) ?? false);
|
|
298
|
+
}, []);
|
|
299
|
+
const getAriaExpanded = useCallback((item) => {
|
|
300
|
+
if (!item.emitEvent) return null;
|
|
301
|
+
return controllerRef.current?.expandedMap.get(item.name) ? "true" : null;
|
|
302
|
+
}, []);
|
|
303
|
+
const getFlatIndex = useCallback((name) => {
|
|
304
|
+
return controllerRef.current?.getFlatIndex(name) ?? -1;
|
|
305
|
+
}, []);
|
|
306
|
+
const handleDropdownToggle = useCallback((dropdown) => {
|
|
307
|
+
const controller = controllerRef.current;
|
|
308
|
+
if (!controller) return;
|
|
309
|
+
cleanupFloatingRef.current?.();
|
|
310
|
+
cleanupFloatingRef.current = null;
|
|
311
|
+
controller.toggleDropdown(dropdown.name);
|
|
312
|
+
syncState();
|
|
313
|
+
if (controller.openDropdown) {
|
|
314
|
+
requestAnimationFrame(() => {
|
|
315
|
+
const trigger = toolbarRef.current?.querySelector('[aria-expanded="true"]');
|
|
316
|
+
const panel = trigger?.parentElement?.querySelector(".dm-toolbar-dropdown-panel");
|
|
317
|
+
if (trigger && panel) {
|
|
318
|
+
const placement = dropdown.layout === "grid" ? "bottom" : "bottom-start";
|
|
319
|
+
cleanupFloatingRef.current = positionFloatingOnce(trigger, panel, {
|
|
320
|
+
placement,
|
|
321
|
+
offsetValue: 4
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}, [syncState]);
|
|
327
|
+
const closeDropdown = useCallback(() => {
|
|
328
|
+
cleanupFloatingRef.current?.();
|
|
329
|
+
cleanupFloatingRef.current = null;
|
|
330
|
+
controllerRef.current?.closeDropdown();
|
|
331
|
+
syncState();
|
|
332
|
+
}, [syncState]);
|
|
333
|
+
return {
|
|
334
|
+
controller: controllerRef,
|
|
335
|
+
groups,
|
|
336
|
+
focusedIndex,
|
|
337
|
+
openDropdown,
|
|
338
|
+
activeVersion,
|
|
339
|
+
toolbarRef,
|
|
340
|
+
isActive,
|
|
341
|
+
isDisabled,
|
|
342
|
+
isDropdownActive,
|
|
343
|
+
getAriaExpanded,
|
|
344
|
+
getFlatIndex,
|
|
345
|
+
handleDropdownToggle,
|
|
346
|
+
closeDropdown,
|
|
347
|
+
syncState
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
var DROPDOWN_CARET = '<svg class="dm-dropdown-caret" width="10" height="10" viewBox="0 0 10 10"><path d="M2 4l3 3 3-3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
351
|
+
function useToolbarIcons(icons) {
|
|
352
|
+
const cacheRef = useRef(/* @__PURE__ */ new Map());
|
|
353
|
+
const prevIconsRef = useRef(icons);
|
|
354
|
+
if (icons !== prevIconsRef.current) {
|
|
355
|
+
cacheRef.current.clear();
|
|
356
|
+
prevIconsRef.current = icons;
|
|
357
|
+
}
|
|
358
|
+
const resolveIconSvg = useCallback((name) => {
|
|
359
|
+
if (icons) {
|
|
360
|
+
return icons[name] ?? "";
|
|
361
|
+
}
|
|
362
|
+
return defaultIcons[name] ?? "";
|
|
363
|
+
}, [icons]);
|
|
364
|
+
const getCachedIcon = useCallback((name) => {
|
|
365
|
+
const cache = cacheRef.current;
|
|
366
|
+
const key = `i:${name}`;
|
|
367
|
+
let cached = cache.get(key);
|
|
368
|
+
if (!cached) {
|
|
369
|
+
cached = resolveIconSvg(name);
|
|
370
|
+
cache.set(key, cached);
|
|
371
|
+
}
|
|
372
|
+
return cached;
|
|
373
|
+
}, [resolveIconSvg]);
|
|
374
|
+
const getCachedTriggerLabel = useCallback((label, isIcon) => {
|
|
375
|
+
const cache = cacheRef.current;
|
|
376
|
+
const key = `tl:${label}:${isIcon ? "1" : "0"}`;
|
|
377
|
+
let cached = cache.get(key);
|
|
378
|
+
if (!cached) {
|
|
379
|
+
const content = isIcon ? resolveIconSvg(label) : label;
|
|
380
|
+
cached = `<span class="dm-toolbar-trigger-label">${content}</span>${DROPDOWN_CARET}`;
|
|
381
|
+
cache.set(key, cached);
|
|
382
|
+
}
|
|
383
|
+
return cached;
|
|
384
|
+
}, [resolveIconSvg]);
|
|
385
|
+
const getCachedTriggerIcon = useCallback((iconName) => {
|
|
386
|
+
const cache = cacheRef.current;
|
|
387
|
+
const key = `t:${iconName}`;
|
|
388
|
+
let cached = cache.get(key);
|
|
389
|
+
if (!cached) {
|
|
390
|
+
cached = resolveIconSvg(iconName) + DROPDOWN_CARET;
|
|
391
|
+
cache.set(key, cached);
|
|
392
|
+
}
|
|
393
|
+
return cached;
|
|
394
|
+
}, [resolveIconSvg]);
|
|
395
|
+
const getCachedItemContent = useCallback((iconName, label, displayMode) => {
|
|
396
|
+
const mode = displayMode ?? "icon-text";
|
|
397
|
+
const cache = cacheRef.current;
|
|
398
|
+
const key = `dc:${iconName}:${label}:${mode}`;
|
|
399
|
+
let cached = cache.get(key);
|
|
400
|
+
if (!cached) {
|
|
401
|
+
if (mode === "text") {
|
|
402
|
+
cached = label;
|
|
403
|
+
} else if (mode === "icon") {
|
|
404
|
+
cached = resolveIconSvg(iconName);
|
|
405
|
+
} else {
|
|
406
|
+
cached = resolveIconSvg(iconName) + " " + label;
|
|
407
|
+
}
|
|
408
|
+
cache.set(key, cached);
|
|
409
|
+
}
|
|
410
|
+
return cached;
|
|
411
|
+
}, [resolveIconSvg]);
|
|
412
|
+
const getDropdownTriggerHtml = useCallback((dropdown, activeItem) => {
|
|
413
|
+
if (dropdown.layout === "grid") {
|
|
414
|
+
const color = activeItem?.color ?? dropdown.defaultIndicatorColor ?? null;
|
|
415
|
+
const cache = cacheRef.current;
|
|
416
|
+
const key = `tr:${dropdown.icon}:${color ?? ""}`;
|
|
417
|
+
let cached = cache.get(key);
|
|
418
|
+
if (!cached) {
|
|
419
|
+
cached = resolveIconSvg(dropdown.icon) + DROPDOWN_CARET;
|
|
420
|
+
if (color) {
|
|
421
|
+
cached += `<span class="dm-toolbar-color-indicator" style="background-color: ${color}"></span>`;
|
|
422
|
+
}
|
|
423
|
+
cache.set(key, cached);
|
|
424
|
+
}
|
|
425
|
+
return cached;
|
|
426
|
+
}
|
|
427
|
+
if (dropdown.dynamicLabel) {
|
|
428
|
+
if (activeItem) return getCachedTriggerLabel(activeItem.label);
|
|
429
|
+
if (dropdown.dynamicLabelFallback) return getCachedTriggerLabel(dropdown.dynamicLabelFallback);
|
|
430
|
+
return getCachedTriggerLabel(dropdown.icon, true);
|
|
431
|
+
}
|
|
432
|
+
const icon = dropdown.dynamicIcon && activeItem ? activeItem.icon : dropdown.icon;
|
|
433
|
+
return getCachedTriggerIcon(icon);
|
|
434
|
+
}, [resolveIconSvg, getCachedTriggerLabel, getCachedTriggerIcon]);
|
|
435
|
+
return {
|
|
436
|
+
resolveIconSvg,
|
|
437
|
+
getCachedIcon,
|
|
438
|
+
getCachedTriggerLabel,
|
|
439
|
+
getCachedTriggerIcon,
|
|
440
|
+
getCachedItemContent,
|
|
441
|
+
getDropdownTriggerHtml
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
445
|
+
function useTooltip() {
|
|
446
|
+
const getTooltip = useCallback((item) => {
|
|
447
|
+
if (item.shortcut) {
|
|
448
|
+
const parts = item.shortcut.split("-");
|
|
449
|
+
const mapped = parts.map((p) => {
|
|
450
|
+
if (p === "Mod") return isMac ? "\u2318" : "Ctrl";
|
|
451
|
+
if (p === "Shift") return isMac ? "\u21E7" : "Shift";
|
|
452
|
+
if (p === "Alt") return isMac ? "\u2325" : "Alt";
|
|
453
|
+
return p.toUpperCase();
|
|
454
|
+
});
|
|
455
|
+
const shortcut = isMac ? mapped.join("") : mapped.join("+");
|
|
456
|
+
return `${item.label} (${shortcut})`;
|
|
457
|
+
}
|
|
458
|
+
return item.label;
|
|
459
|
+
}, []);
|
|
460
|
+
return { getTooltip };
|
|
461
|
+
}
|
|
462
|
+
function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
|
|
463
|
+
const focusCurrentButton = useCallback(() => {
|
|
464
|
+
const idx = controllerRef.current?.focusedIndex ?? 0;
|
|
465
|
+
const buttons = toolbarRef.current?.querySelectorAll(".dm-toolbar-button");
|
|
466
|
+
buttons?.[idx]?.focus();
|
|
467
|
+
}, []);
|
|
468
|
+
const onKeyDown = useCallback((event) => {
|
|
469
|
+
const controller = controllerRef.current;
|
|
470
|
+
if (!controller) return;
|
|
471
|
+
switch (event.key) {
|
|
472
|
+
case "ArrowRight":
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
controller.navigateNext();
|
|
475
|
+
focusCurrentButton();
|
|
476
|
+
break;
|
|
477
|
+
case "ArrowLeft":
|
|
478
|
+
event.preventDefault();
|
|
479
|
+
controller.navigatePrev();
|
|
480
|
+
focusCurrentButton();
|
|
481
|
+
break;
|
|
482
|
+
case "Home":
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
controller.navigateFirst();
|
|
485
|
+
focusCurrentButton();
|
|
486
|
+
break;
|
|
487
|
+
case "End":
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
controller.navigateLast();
|
|
490
|
+
focusCurrentButton();
|
|
491
|
+
break;
|
|
492
|
+
case "Escape":
|
|
493
|
+
if (controller.openDropdown) {
|
|
494
|
+
event.preventDefault();
|
|
495
|
+
closeDropdown();
|
|
496
|
+
focusCurrentButton();
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}, [closeDropdown, focusCurrentButton]);
|
|
501
|
+
return { onKeyDown, focusCurrentButton };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/toolbar/useComputedStyle.ts
|
|
505
|
+
function getComputedStyleAtCursor(editor, prop) {
|
|
506
|
+
try {
|
|
507
|
+
const { from } = editor.state.selection;
|
|
508
|
+
const resolved = editor.view.domAtPos(from);
|
|
509
|
+
const el = resolved.node instanceof HTMLElement ? resolved.node : resolved.node.parentElement;
|
|
510
|
+
if (!el) return null;
|
|
511
|
+
return el.style.getPropertyValue(prop) || window.getComputedStyle(el).getPropertyValue(prop) || null;
|
|
512
|
+
} catch {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function getInlineStyleAtCursor(editor, prop) {
|
|
517
|
+
try {
|
|
518
|
+
const { from } = editor.state.selection;
|
|
519
|
+
const resolved = editor.view.domAtPos(from);
|
|
520
|
+
const el = resolved.node instanceof HTMLElement ? resolved.node : resolved.node.parentElement;
|
|
521
|
+
if (!el) return null;
|
|
522
|
+
return el.style.getPropertyValue(prop) || null;
|
|
523
|
+
} catch {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function ToolbarButton({
|
|
528
|
+
item,
|
|
529
|
+
isActive,
|
|
530
|
+
isDisabled,
|
|
531
|
+
tabIndex,
|
|
532
|
+
tooltip,
|
|
533
|
+
iconHtml,
|
|
534
|
+
ariaExpanded,
|
|
535
|
+
onClick,
|
|
536
|
+
onFocus
|
|
537
|
+
}) {
|
|
538
|
+
return /* @__PURE__ */ jsx(
|
|
539
|
+
"button",
|
|
540
|
+
{
|
|
541
|
+
type: "button",
|
|
542
|
+
className: `dm-toolbar-button${isActive ? " dm-toolbar-button--active" : ""}`,
|
|
543
|
+
"aria-pressed": isActive,
|
|
544
|
+
"aria-expanded": ariaExpanded === "true" ? true : void 0,
|
|
545
|
+
"aria-label": item.label,
|
|
546
|
+
title: tooltip,
|
|
547
|
+
tabIndex,
|
|
548
|
+
disabled: isDisabled,
|
|
549
|
+
dangerouslySetInnerHTML: { __html: iconHtml },
|
|
550
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
551
|
+
onClick: (e) => onClick(item, e),
|
|
552
|
+
onFocus: () => onFocus(item.name)
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
function ToolbarDropdownPanel({
|
|
557
|
+
dropdown,
|
|
558
|
+
isActive,
|
|
559
|
+
getCachedItemContent,
|
|
560
|
+
onItemClick
|
|
561
|
+
}) {
|
|
562
|
+
if (dropdown.layout === "grid") {
|
|
563
|
+
return /* @__PURE__ */ jsx(
|
|
564
|
+
"div",
|
|
565
|
+
{
|
|
566
|
+
className: "dm-toolbar-dropdown-panel dm-color-palette",
|
|
567
|
+
role: "menu",
|
|
568
|
+
style: { "--dm-palette-columns": String(dropdown.gridColumns ?? 10) },
|
|
569
|
+
children: dropdown.items.map(
|
|
570
|
+
(sub) => sub.color ? /* @__PURE__ */ jsx(
|
|
571
|
+
"button",
|
|
572
|
+
{
|
|
573
|
+
type: "button",
|
|
574
|
+
className: `dm-color-swatch${isActive(sub.name) ? " dm-color-swatch--active" : ""}`,
|
|
575
|
+
role: "menuitem",
|
|
576
|
+
"aria-label": sub.label,
|
|
577
|
+
title: sub.label,
|
|
578
|
+
style: { backgroundColor: sub.color },
|
|
579
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
580
|
+
onClick: (e) => onItemClick(sub, e)
|
|
581
|
+
},
|
|
582
|
+
sub.name
|
|
583
|
+
) : /* @__PURE__ */ jsx(
|
|
584
|
+
"button",
|
|
585
|
+
{
|
|
586
|
+
type: "button",
|
|
587
|
+
className: "dm-color-palette-reset",
|
|
588
|
+
role: "menuitem",
|
|
589
|
+
"aria-label": sub.label,
|
|
590
|
+
dangerouslySetInnerHTML: { __html: getCachedItemContent(sub.icon, sub.label) },
|
|
591
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
592
|
+
onClick: (e) => onItemClick(sub, e)
|
|
593
|
+
},
|
|
594
|
+
sub.name
|
|
595
|
+
)
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
return /* @__PURE__ */ jsx(
|
|
601
|
+
"div",
|
|
602
|
+
{
|
|
603
|
+
className: "dm-toolbar-dropdown-panel",
|
|
604
|
+
role: "menu",
|
|
605
|
+
"data-display-mode": dropdown.displayMode ?? null,
|
|
606
|
+
children: dropdown.items.map((sub) => /* @__PURE__ */ jsx(
|
|
607
|
+
"button",
|
|
608
|
+
{
|
|
609
|
+
type: "button",
|
|
610
|
+
className: `dm-toolbar-dropdown-item${isActive(sub.name) ? " dm-toolbar-dropdown-item--active" : ""}`,
|
|
611
|
+
role: "menuitem",
|
|
612
|
+
"aria-label": sub.label,
|
|
613
|
+
ref: (el) => {
|
|
614
|
+
if (el && sub.style) el.setAttribute("style", sub.style);
|
|
615
|
+
},
|
|
616
|
+
dangerouslySetInnerHTML: { __html: getCachedItemContent(sub.icon, sub.label, dropdown.displayMode) },
|
|
617
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
618
|
+
onClick: (e) => onItemClick(sub, e)
|
|
619
|
+
},
|
|
620
|
+
sub.name
|
|
621
|
+
))
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
function ToolbarDropdown({
|
|
626
|
+
dropdown,
|
|
627
|
+
isOpen,
|
|
628
|
+
isActive,
|
|
629
|
+
isDropdownActive,
|
|
630
|
+
isDisabled,
|
|
631
|
+
tabIndex,
|
|
632
|
+
triggerHtml,
|
|
633
|
+
getCachedItemContent,
|
|
634
|
+
onToggle,
|
|
635
|
+
onItemClick,
|
|
636
|
+
onFocus
|
|
637
|
+
}) {
|
|
638
|
+
return /* @__PURE__ */ jsxs("div", { className: "dm-toolbar-dropdown-wrapper", children: [
|
|
639
|
+
/* @__PURE__ */ jsx(
|
|
640
|
+
"button",
|
|
641
|
+
{
|
|
642
|
+
type: "button",
|
|
643
|
+
className: `dm-toolbar-button dm-toolbar-dropdown-trigger${isDropdownActive ? " dm-toolbar-button--active" : ""}`,
|
|
644
|
+
"aria-expanded": isOpen,
|
|
645
|
+
"aria-haspopup": "true",
|
|
646
|
+
"aria-label": dropdown.label,
|
|
647
|
+
title: dropdown.label,
|
|
648
|
+
tabIndex,
|
|
649
|
+
disabled: isDisabled,
|
|
650
|
+
"data-dropdown": dropdown.name,
|
|
651
|
+
dangerouslySetInnerHTML: { __html: triggerHtml },
|
|
652
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
653
|
+
onClick: () => onToggle(dropdown),
|
|
654
|
+
onFocus: () => onFocus(dropdown.name)
|
|
655
|
+
}
|
|
656
|
+
),
|
|
657
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
658
|
+
ToolbarDropdownPanel,
|
|
659
|
+
{
|
|
660
|
+
dropdown,
|
|
661
|
+
isActive,
|
|
662
|
+
getCachedItemContent,
|
|
663
|
+
onItemClick
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
] });
|
|
667
|
+
}
|
|
668
|
+
function DomternalToolbar({ editor: editorProp, icons, layout }) {
|
|
669
|
+
const { editor: contextEditor } = useCurrentEditor();
|
|
670
|
+
const editor = editorProp ?? contextEditor;
|
|
671
|
+
const {
|
|
672
|
+
controller: controllerRef,
|
|
673
|
+
groups,
|
|
674
|
+
focusedIndex,
|
|
675
|
+
openDropdown,
|
|
676
|
+
toolbarRef,
|
|
677
|
+
isActive,
|
|
678
|
+
isDisabled,
|
|
679
|
+
isDropdownActive,
|
|
680
|
+
getAriaExpanded,
|
|
681
|
+
getFlatIndex,
|
|
682
|
+
handleDropdownToggle,
|
|
683
|
+
closeDropdown
|
|
684
|
+
} = useToolbarController(editor, layout);
|
|
685
|
+
const {
|
|
686
|
+
getCachedIcon,
|
|
687
|
+
getCachedItemContent,
|
|
688
|
+
getDropdownTriggerHtml
|
|
689
|
+
} = useToolbarIcons(icons);
|
|
690
|
+
const { getTooltip } = useTooltip();
|
|
691
|
+
const { onKeyDown } = useKeyboardNav(controllerRef, toolbarRef, closeDropdown);
|
|
692
|
+
const onButtonClick = useCallback((item, event) => {
|
|
693
|
+
if (!editor) return;
|
|
694
|
+
if (controllerRef.current?.openDropdown) {
|
|
695
|
+
closeDropdown();
|
|
696
|
+
}
|
|
697
|
+
if (item.emitEvent) {
|
|
698
|
+
const anchor = event.currentTarget;
|
|
699
|
+
editor.emit(item.emitEvent, { anchorElement: anchor });
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
controllerRef.current?.executeCommand(item);
|
|
703
|
+
}, [editor, closeDropdown]);
|
|
704
|
+
const onDropdownItemClick = useCallback((item, event) => {
|
|
705
|
+
if (!editor) return;
|
|
706
|
+
let anchor;
|
|
707
|
+
if (item.emitEvent) {
|
|
708
|
+
const wrapper = event.currentTarget.closest(".dm-toolbar-dropdown-wrapper");
|
|
709
|
+
anchor = wrapper?.querySelector(".dm-toolbar-dropdown-trigger");
|
|
710
|
+
}
|
|
711
|
+
closeDropdown();
|
|
712
|
+
if (item.emitEvent) {
|
|
713
|
+
editor.emit(item.emitEvent, { anchorElement: anchor });
|
|
714
|
+
} else {
|
|
715
|
+
controllerRef.current?.executeCommand(item);
|
|
716
|
+
}
|
|
717
|
+
}, [editor, closeDropdown]);
|
|
718
|
+
const onButtonFocus = useCallback((name) => {
|
|
719
|
+
const index = controllerRef.current?.getFlatIndex(name) ?? -1;
|
|
720
|
+
if (index >= 0) {
|
|
721
|
+
controllerRef.current?.setFocusedIndex(index);
|
|
722
|
+
}
|
|
723
|
+
}, []);
|
|
724
|
+
if (!editor) return null;
|
|
725
|
+
return /* @__PURE__ */ jsx(
|
|
726
|
+
"div",
|
|
727
|
+
{
|
|
728
|
+
ref: toolbarRef,
|
|
729
|
+
className: "dm-toolbar",
|
|
730
|
+
role: "toolbar",
|
|
731
|
+
"aria-label": "Editor formatting",
|
|
732
|
+
onKeyDown,
|
|
733
|
+
children: groups.map((group, gi) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
734
|
+
gi > 0 && /* @__PURE__ */ jsx("div", { className: "dm-toolbar-separator", role: "separator" }),
|
|
735
|
+
/* @__PURE__ */ jsx("div", { className: "dm-toolbar-group", role: "group", "aria-label": group.name || "Tools", children: group.items.map((item) => {
|
|
736
|
+
if (item.type === "button") {
|
|
737
|
+
const btn = item;
|
|
738
|
+
return /* @__PURE__ */ jsx(
|
|
739
|
+
ToolbarButton,
|
|
740
|
+
{
|
|
741
|
+
item: btn,
|
|
742
|
+
isActive: isActive(btn.name),
|
|
743
|
+
isDisabled: isDisabled(btn.name),
|
|
744
|
+
tabIndex: getFlatIndex(btn.name) === focusedIndex ? 0 : -1,
|
|
745
|
+
tooltip: getTooltip(btn),
|
|
746
|
+
iconHtml: getCachedIcon(btn.icon),
|
|
747
|
+
ariaExpanded: getAriaExpanded(btn),
|
|
748
|
+
onClick: onButtonClick,
|
|
749
|
+
onFocus: onButtonFocus
|
|
750
|
+
},
|
|
751
|
+
btn.name
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
if (item.type === "dropdown") {
|
|
755
|
+
const dd = item;
|
|
756
|
+
const activeItem = dd.items.find((sub) => controllerRef.current?.activeMap.get(sub.name));
|
|
757
|
+
let triggerHtml = getDropdownTriggerHtml(dd, activeItem);
|
|
758
|
+
if (dd.dynamicLabel && !activeItem && dd.computedStyleProperty) {
|
|
759
|
+
let computed;
|
|
760
|
+
if (dd.computedStyleProperty === "font-family") {
|
|
761
|
+
computed = getInlineStyleAtCursor(editor, dd.computedStyleProperty);
|
|
762
|
+
if (computed) {
|
|
763
|
+
const first = computed.split(",")[0]?.replace(/['"]+/g, "").trim();
|
|
764
|
+
computed = first || null;
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
computed = getComputedStyleAtCursor(editor, dd.computedStyleProperty);
|
|
768
|
+
}
|
|
769
|
+
if (computed) {
|
|
770
|
+
triggerHtml = `<span class="dm-toolbar-trigger-label">${computed}</span>${DROPDOWN_CARET}`;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return /* @__PURE__ */ jsx(
|
|
774
|
+
ToolbarDropdown,
|
|
775
|
+
{
|
|
776
|
+
dropdown: dd,
|
|
777
|
+
isOpen: openDropdown === dd.name,
|
|
778
|
+
isActive,
|
|
779
|
+
isDropdownActive: isDropdownActive(dd),
|
|
780
|
+
isDisabled: isDisabled(dd.name),
|
|
781
|
+
tabIndex: getFlatIndex(dd.name) === focusedIndex ? 0 : -1,
|
|
782
|
+
triggerHtml,
|
|
783
|
+
getCachedItemContent,
|
|
784
|
+
onToggle: handleDropdownToggle,
|
|
785
|
+
onItemClick: onDropdownItemClick,
|
|
786
|
+
onFocus: onButtonFocus
|
|
787
|
+
},
|
|
788
|
+
dd.name
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return null;
|
|
792
|
+
}) })
|
|
793
|
+
] }, group.name))
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
function isInsideTableCell($pos) {
|
|
798
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
799
|
+
const name = $pos.node(d).type.name;
|
|
800
|
+
if (name === "tableCell" || name === "tableHeader") return true;
|
|
801
|
+
}
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
function findCellNode(pos) {
|
|
805
|
+
for (let d = pos.depth; d > 0; d--) {
|
|
806
|
+
const node = pos.node(d);
|
|
807
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") return node;
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
function useBubbleMenu(options) {
|
|
812
|
+
const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts } = options;
|
|
813
|
+
const menuRef = useRef(null);
|
|
814
|
+
const pluginKeyRef = useRef(new PluginKey("reactBubbleMenu-" + Math.random().toString(36).slice(2, 8)));
|
|
815
|
+
const [resolvedItems, setResolvedItems] = useState([]);
|
|
816
|
+
const [activeVersion, setActiveVersion] = useState(0);
|
|
817
|
+
const activeMapRef = useRef(/* @__PURE__ */ new Map());
|
|
818
|
+
const disabledMapRef = useRef(/* @__PURE__ */ new Map());
|
|
819
|
+
const itemMapRef = useRef(/* @__PURE__ */ new Map());
|
|
820
|
+
const bubbleDefaultsRef = useRef(/* @__PURE__ */ new Map());
|
|
821
|
+
const resolvedItemsRef = useRef([]);
|
|
822
|
+
useEffect(() => {
|
|
823
|
+
if (!editor || editor.isDestroyed || !menuRef.current) return;
|
|
824
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const item of editor.toolbarItems) {
|
|
826
|
+
if (item.type === "button") {
|
|
827
|
+
itemMap.set(item.name, item);
|
|
828
|
+
} else if (item.type === "dropdown") {
|
|
829
|
+
for (const sub of item.items) {
|
|
830
|
+
itemMap.set(sub.name, sub);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
itemMapRef.current = itemMap;
|
|
835
|
+
const bubbleDefaults = /* @__PURE__ */ new Map();
|
|
836
|
+
const byCtx = /* @__PURE__ */ new Map();
|
|
837
|
+
const addItem = (btn) => {
|
|
838
|
+
const ctx = btn["bubbleMenu"];
|
|
839
|
+
if (!ctx) return;
|
|
840
|
+
let arr = byCtx.get(ctx);
|
|
841
|
+
if (!arr) {
|
|
842
|
+
arr = [];
|
|
843
|
+
byCtx.set(ctx, arr);
|
|
844
|
+
}
|
|
845
|
+
arr.push(btn);
|
|
846
|
+
};
|
|
847
|
+
for (const item of editor.toolbarItems) {
|
|
848
|
+
if (item.type === "button") addItem(item);
|
|
849
|
+
else if (item.type === "dropdown") {
|
|
850
|
+
for (const sub of item.items) addItem(sub);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
for (const [ctx, ctxItems] of byCtx) {
|
|
854
|
+
ctxItems.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
|
|
855
|
+
const result = [];
|
|
856
|
+
let lastGroup;
|
|
857
|
+
let sepIdx = 0;
|
|
858
|
+
for (const item of ctxItems) {
|
|
859
|
+
if (lastGroup !== void 0 && item.group !== lastGroup) {
|
|
860
|
+
result.push({ type: "separator", name: `bsep-${sepIdx++}` });
|
|
861
|
+
}
|
|
862
|
+
result.push(item);
|
|
863
|
+
lastGroup = item.group;
|
|
864
|
+
}
|
|
865
|
+
bubbleDefaults.set(ctx, result);
|
|
866
|
+
}
|
|
867
|
+
bubbleDefaultsRef.current = bubbleDefaults;
|
|
868
|
+
const resolveNames = (names) => {
|
|
869
|
+
const result = [];
|
|
870
|
+
let sepIdx = 0;
|
|
871
|
+
for (const name of names) {
|
|
872
|
+
if (name === "|") {
|
|
873
|
+
result.push({ type: "separator", name: `sep-${sepIdx++}` });
|
|
874
|
+
} else {
|
|
875
|
+
const item = itemMap.get(name);
|
|
876
|
+
if (item) result.push(item);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return result;
|
|
880
|
+
};
|
|
881
|
+
const getFormatItems = () => {
|
|
882
|
+
return Array.from(itemMap.values()).filter((item) => item.group === "format").sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
|
|
883
|
+
};
|
|
884
|
+
const detectContext = (selection, ctxs) => {
|
|
885
|
+
if ("$anchorCell" in selection) return null;
|
|
886
|
+
if (selection.node) return selection.node.type.name;
|
|
887
|
+
if (selection.empty) return null;
|
|
888
|
+
const fromCell = findCellNode(selection.$from);
|
|
889
|
+
if (fromCell) {
|
|
890
|
+
const toCell = findCellNode(selection.$to);
|
|
891
|
+
if (toCell && fromCell !== toCell) return null;
|
|
892
|
+
return "table";
|
|
893
|
+
}
|
|
894
|
+
const fromName = selection.$from.parent.type.name;
|
|
895
|
+
if (fromName in ctxs) return fromName;
|
|
896
|
+
if ("text" in ctxs && selection.$from.parent.type.spec.marks !== "") return "text";
|
|
897
|
+
const toName = selection.$to.parent.type.name;
|
|
898
|
+
if (toName in ctxs) return toName;
|
|
899
|
+
if ("text" in ctxs && selection.$to.parent.type.spec.marks !== "") return "text";
|
|
900
|
+
return null;
|
|
901
|
+
};
|
|
902
|
+
const filterBySchema = (contextName, schemaItems) => {
|
|
903
|
+
if (contextName === "text" || contextName === "table") return schemaItems;
|
|
904
|
+
const schema = editor.state.schema;
|
|
905
|
+
if (!schema) return schemaItems;
|
|
906
|
+
const nodeType = schema.nodes[contextName];
|
|
907
|
+
if (!nodeType) return schemaItems;
|
|
908
|
+
return schemaItems.filter((item) => {
|
|
909
|
+
const markName = typeof item.isActive === "string" ? item.isActive : null;
|
|
910
|
+
if (!markName) return true;
|
|
911
|
+
const markType = schema.marks?.[markName];
|
|
912
|
+
if (!markType) return true;
|
|
913
|
+
return nodeType.allowsMarkType(markType);
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
let shouldShowFn = shouldShow;
|
|
917
|
+
if (!shouldShowFn) {
|
|
918
|
+
if (contexts) {
|
|
919
|
+
shouldShowFn = ({ state }) => {
|
|
920
|
+
const context = detectContext(state.selection, contexts);
|
|
921
|
+
if (!context) return false;
|
|
922
|
+
if (context in contexts) {
|
|
923
|
+
const val = contexts[context];
|
|
924
|
+
if (val === null) return false;
|
|
925
|
+
return val === true || Array.isArray(val) && val.length > 0;
|
|
926
|
+
}
|
|
927
|
+
return bubbleDefaults.has(context);
|
|
928
|
+
};
|
|
929
|
+
} else {
|
|
930
|
+
shouldShowFn = ({ state }) => {
|
|
931
|
+
if (state.selection.empty || state.selection.node) return false;
|
|
932
|
+
if (isInsideTableCell(state.selection.$from)) return false;
|
|
933
|
+
return state.selection.$from.parent.type.spec.marks !== "" || state.selection.$to.parent.type.spec.marks !== "";
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const plugin = createBubbleMenuPlugin({
|
|
938
|
+
pluginKey: pluginKeyRef.current,
|
|
939
|
+
editor,
|
|
940
|
+
element: menuRef.current,
|
|
941
|
+
shouldShow: shouldShowFn,
|
|
942
|
+
placement,
|
|
943
|
+
offset,
|
|
944
|
+
updateDelay
|
|
945
|
+
});
|
|
946
|
+
editor.registerPlugin(plugin);
|
|
947
|
+
const setItems = (newItems) => {
|
|
948
|
+
resolvedItemsRef.current = newItems;
|
|
949
|
+
setResolvedItems(newItems);
|
|
950
|
+
};
|
|
951
|
+
if (contexts) {
|
|
952
|
+
updateContextItems(editor, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
|
|
953
|
+
} else if (items) {
|
|
954
|
+
setItems(resolveNames(items));
|
|
955
|
+
} else {
|
|
956
|
+
setItems(resolveNames(["bold", "italic", "underline"]));
|
|
957
|
+
}
|
|
958
|
+
const updateStates = (ed) => {
|
|
959
|
+
let canProxy = null;
|
|
960
|
+
try {
|
|
961
|
+
canProxy = ed.can();
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
for (const item of resolvedItemsRef.current) {
|
|
965
|
+
if (item.type === "separator") continue;
|
|
966
|
+
activeMapRef.current.set(item.name, ToolbarController.resolveActive(ed, item));
|
|
967
|
+
try {
|
|
968
|
+
const canCmd = canProxy?.[item.command];
|
|
969
|
+
disabledMapRef.current.set(item.name, canCmd ? !(item.commandArgs?.length ? canCmd(...item.commandArgs) : canCmd()) : false);
|
|
970
|
+
} catch {
|
|
971
|
+
disabledMapRef.current.set(item.name, false);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
const transactionHandler = () => {
|
|
976
|
+
if (contexts) {
|
|
977
|
+
updateContextItems(editor, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
|
|
978
|
+
}
|
|
979
|
+
updateStates(editor);
|
|
980
|
+
setActiveVersion((v) => v + 1);
|
|
981
|
+
};
|
|
982
|
+
editor.on("transaction", transactionHandler);
|
|
983
|
+
updateStates(editor);
|
|
984
|
+
return () => {
|
|
985
|
+
editor.off("transaction", transactionHandler);
|
|
986
|
+
if (!editor.isDestroyed) {
|
|
987
|
+
editor.unregisterPlugin(pluginKeyRef.current);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}, [editor]);
|
|
991
|
+
function updateContextItems(ed, ctxs, detectContext, resolveNames, getFormatItems, filterBySchema, defaults, setItems) {
|
|
992
|
+
const ctx = detectContext(ed.state.selection, ctxs);
|
|
993
|
+
if (!ctx) {
|
|
994
|
+
setItems([]);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (ctx in ctxs) {
|
|
998
|
+
const val = ctxs[ctx];
|
|
999
|
+
if (val === null || Array.isArray(val) && val.length === 0) {
|
|
1000
|
+
setItems([]);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
if (val === true) {
|
|
1004
|
+
setItems(filterBySchema(ctx, getFormatItems()));
|
|
1005
|
+
} else if (Array.isArray(val)) {
|
|
1006
|
+
const resolved = resolveNames(val);
|
|
1007
|
+
const buttons = resolved.filter((i) => i.type !== "separator");
|
|
1008
|
+
const filtered = new Set(filterBySchema(ctx, buttons).map((b) => b.name));
|
|
1009
|
+
setItems(resolved.filter((i) => i.type === "separator" || filtered.has(i.name)));
|
|
1010
|
+
}
|
|
1011
|
+
} else {
|
|
1012
|
+
setItems(defaults.get(ctx) ?? []);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const isItemActive = (item) => {
|
|
1016
|
+
return activeMapRef.current.get(item.name) ?? false;
|
|
1017
|
+
};
|
|
1018
|
+
const isItemDisabled = (item) => {
|
|
1019
|
+
return disabledMapRef.current.get(item.name) ?? false;
|
|
1020
|
+
};
|
|
1021
|
+
const executeCommand = (item) => {
|
|
1022
|
+
if (!editor) return;
|
|
1023
|
+
if (item.emitEvent) {
|
|
1024
|
+
editor.emit(item.emitEvent, {});
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
ToolbarController.executeItem(editor, item);
|
|
1028
|
+
};
|
|
1029
|
+
return {
|
|
1030
|
+
menuRef,
|
|
1031
|
+
resolvedItems,
|
|
1032
|
+
isItemActive,
|
|
1033
|
+
isItemDisabled,
|
|
1034
|
+
executeCommand,
|
|
1035
|
+
activeVersion,
|
|
1036
|
+
getCachedIcon: (name) => defaultIcons[name] ?? ""
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function DomternalBubbleMenu({
|
|
1040
|
+
editor: editorProp,
|
|
1041
|
+
shouldShow,
|
|
1042
|
+
placement,
|
|
1043
|
+
offset,
|
|
1044
|
+
updateDelay,
|
|
1045
|
+
items,
|
|
1046
|
+
contexts,
|
|
1047
|
+
children
|
|
1048
|
+
}) {
|
|
1049
|
+
const { editor: contextEditor } = useCurrentEditor();
|
|
1050
|
+
const editor = editorProp ?? contextEditor;
|
|
1051
|
+
const htmlCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
1052
|
+
const {
|
|
1053
|
+
menuRef,
|
|
1054
|
+
resolvedItems,
|
|
1055
|
+
isItemActive,
|
|
1056
|
+
isItemDisabled,
|
|
1057
|
+
executeCommand,
|
|
1058
|
+
getCachedIcon
|
|
1059
|
+
} = useBubbleMenu({
|
|
1060
|
+
editor,
|
|
1061
|
+
shouldShow,
|
|
1062
|
+
placement,
|
|
1063
|
+
offset,
|
|
1064
|
+
updateDelay,
|
|
1065
|
+
items,
|
|
1066
|
+
contexts
|
|
1067
|
+
});
|
|
1068
|
+
const getCachedHtml = (name) => {
|
|
1069
|
+
const cache = htmlCacheRef.current;
|
|
1070
|
+
const cached = cache.get(name);
|
|
1071
|
+
if (cached) return cached;
|
|
1072
|
+
const html = getCachedIcon(name);
|
|
1073
|
+
cache.set(name, html);
|
|
1074
|
+
return html;
|
|
1075
|
+
};
|
|
1076
|
+
return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "dm-bubble-menu", role: "toolbar", "aria-label": "Text formatting", children: [
|
|
1077
|
+
resolvedItems.map((item) => {
|
|
1078
|
+
if (item.type === "separator") {
|
|
1079
|
+
return /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }, item.name);
|
|
1080
|
+
}
|
|
1081
|
+
const btn = item;
|
|
1082
|
+
const active = isItemActive(btn);
|
|
1083
|
+
return /* @__PURE__ */ jsx(
|
|
1084
|
+
"button",
|
|
1085
|
+
{
|
|
1086
|
+
type: "button",
|
|
1087
|
+
className: `dm-toolbar-button${active ? " dm-toolbar-button--active" : ""}`,
|
|
1088
|
+
disabled: isItemDisabled(btn),
|
|
1089
|
+
title: btn.label,
|
|
1090
|
+
"aria-label": btn.label,
|
|
1091
|
+
"aria-pressed": active,
|
|
1092
|
+
dangerouslySetInnerHTML: { __html: getCachedHtml(btn.icon) },
|
|
1093
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1094
|
+
onClick: () => executeCommand(btn)
|
|
1095
|
+
},
|
|
1096
|
+
btn.name
|
|
1097
|
+
);
|
|
1098
|
+
}),
|
|
1099
|
+
children
|
|
1100
|
+
] });
|
|
1101
|
+
}
|
|
1102
|
+
function DomternalFloatingMenu({
|
|
1103
|
+
editor: editorProp,
|
|
1104
|
+
shouldShow,
|
|
1105
|
+
offset = 0,
|
|
1106
|
+
children
|
|
1107
|
+
}) {
|
|
1108
|
+
const { editor: contextEditor } = useCurrentEditor();
|
|
1109
|
+
const editor = editorProp ?? contextEditor;
|
|
1110
|
+
const menuRef = useRef(null);
|
|
1111
|
+
const pluginKeyRef = useRef(
|
|
1112
|
+
new PluginKey("reactFloatingMenu-" + Math.random().toString(36).slice(2, 8))
|
|
1113
|
+
);
|
|
1114
|
+
const shouldShowRef = useRef(shouldShow);
|
|
1115
|
+
shouldShowRef.current = shouldShow;
|
|
1116
|
+
const offsetRef = useRef(offset);
|
|
1117
|
+
offsetRef.current = offset;
|
|
1118
|
+
useEffect(() => {
|
|
1119
|
+
if (!editor || editor.isDestroyed || !menuRef.current) return;
|
|
1120
|
+
const plugin = createFloatingMenuPlugin({
|
|
1121
|
+
pluginKey: pluginKeyRef.current,
|
|
1122
|
+
editor,
|
|
1123
|
+
element: menuRef.current,
|
|
1124
|
+
...shouldShowRef.current && { shouldShow: shouldShowRef.current },
|
|
1125
|
+
offset: offsetRef.current
|
|
1126
|
+
});
|
|
1127
|
+
editor.registerPlugin(plugin);
|
|
1128
|
+
return () => {
|
|
1129
|
+
if (!editor.isDestroyed) {
|
|
1130
|
+
editor.unregisterPlugin(pluginKeyRef.current);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
}, [editor]);
|
|
1134
|
+
return /* @__PURE__ */ jsx("div", { ref: menuRef, className: "dm-floating-menu", children });
|
|
1135
|
+
}
|
|
1136
|
+
function useEmojiPicker(editor, emojis) {
|
|
1137
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
1138
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
1139
|
+
const [activeCategory, setActiveCategory] = useState("");
|
|
1140
|
+
const pickerRef = useRef(null);
|
|
1141
|
+
const anchorRef = useRef(null);
|
|
1142
|
+
const cleanupFloatingRef = useRef(null);
|
|
1143
|
+
const clickOutsideRef = useRef(null);
|
|
1144
|
+
const keydownRef = useRef(null);
|
|
1145
|
+
const isOpenRef = useRef(false);
|
|
1146
|
+
const categories = useMemo(() => {
|
|
1147
|
+
const map = /* @__PURE__ */ new Map();
|
|
1148
|
+
for (const item of emojis) {
|
|
1149
|
+
let list = map.get(item.group);
|
|
1150
|
+
if (!list) {
|
|
1151
|
+
list = [];
|
|
1152
|
+
map.set(item.group, list);
|
|
1153
|
+
}
|
|
1154
|
+
list.push(item);
|
|
1155
|
+
}
|
|
1156
|
+
return map;
|
|
1157
|
+
}, [emojis]);
|
|
1158
|
+
const categoryNames = useMemo(() => [...categories.keys()], [categories]);
|
|
1159
|
+
const filteredEmojis = useMemo(() => {
|
|
1160
|
+
const query = searchQuery.toLowerCase();
|
|
1161
|
+
if (!query) return [];
|
|
1162
|
+
const storage = getEmojiStorage(editor);
|
|
1163
|
+
const searchFn = storage?.["searchEmoji"];
|
|
1164
|
+
if (searchFn) return searchFn(query);
|
|
1165
|
+
return emojis.filter(
|
|
1166
|
+
(item) => item.name.includes(query) || item.group.toLowerCase().includes(query)
|
|
1167
|
+
);
|
|
1168
|
+
}, [searchQuery, emojis, editor]);
|
|
1169
|
+
const frequentlyUsed = useMemo(() => {
|
|
1170
|
+
if (!isOpen) return [];
|
|
1171
|
+
const storage = getEmojiStorage(editor);
|
|
1172
|
+
const getFreq = storage?.["getFrequentlyUsed"];
|
|
1173
|
+
if (!getFreq) return [];
|
|
1174
|
+
const names = getFreq();
|
|
1175
|
+
if (!names.length) return [];
|
|
1176
|
+
const nameMap = storage["_nameMap"];
|
|
1177
|
+
if (!nameMap) return [];
|
|
1178
|
+
return names.slice(0, 16).map((n) => nameMap.get(n)).filter(Boolean);
|
|
1179
|
+
}, [isOpen, editor]);
|
|
1180
|
+
const removeGlobalListeners = useCallback(() => {
|
|
1181
|
+
if (clickOutsideRef.current) {
|
|
1182
|
+
document.removeEventListener("mousedown", clickOutsideRef.current);
|
|
1183
|
+
clickOutsideRef.current = null;
|
|
1184
|
+
}
|
|
1185
|
+
if (keydownRef.current) {
|
|
1186
|
+
document.removeEventListener("keydown", keydownRef.current);
|
|
1187
|
+
keydownRef.current = null;
|
|
1188
|
+
}
|
|
1189
|
+
}, []);
|
|
1190
|
+
const close = useCallback(() => {
|
|
1191
|
+
cleanupFloatingRef.current?.();
|
|
1192
|
+
cleanupFloatingRef.current = null;
|
|
1193
|
+
isOpenRef.current = false;
|
|
1194
|
+
setIsOpen(false);
|
|
1195
|
+
setStorageOpen(editor, false);
|
|
1196
|
+
setSearchQuery("");
|
|
1197
|
+
anchorRef.current = null;
|
|
1198
|
+
removeGlobalListeners();
|
|
1199
|
+
editor?.view.focus();
|
|
1200
|
+
}, [editor, removeGlobalListeners]);
|
|
1201
|
+
const addGlobalListeners = useCallback(() => {
|
|
1202
|
+
clickOutsideRef.current = (e) => {
|
|
1203
|
+
const target = e.target;
|
|
1204
|
+
if (pickerRef.current && !pickerRef.current.contains(target) && target !== anchorRef.current && !anchorRef.current?.contains(target)) {
|
|
1205
|
+
requestAnimationFrame(() => close());
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
document.addEventListener("mousedown", clickOutsideRef.current);
|
|
1209
|
+
keydownRef.current = (e) => {
|
|
1210
|
+
if (e.key === "Escape") {
|
|
1211
|
+
e.preventDefault();
|
|
1212
|
+
close();
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
document.addEventListener("keydown", keydownRef.current);
|
|
1216
|
+
}, [close]);
|
|
1217
|
+
useEffect(() => {
|
|
1218
|
+
if (!editor || editor.isDestroyed) return;
|
|
1219
|
+
const handler = (...args) => {
|
|
1220
|
+
const data = args[0];
|
|
1221
|
+
if (isOpenRef.current) {
|
|
1222
|
+
close();
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
anchorRef.current = data?.anchorElement ?? null;
|
|
1226
|
+
isOpenRef.current = true;
|
|
1227
|
+
setIsOpen(true);
|
|
1228
|
+
setStorageOpen(editor, true);
|
|
1229
|
+
setSearchQuery("");
|
|
1230
|
+
if (categoryNames.length > 0 && categoryNames[0]) {
|
|
1231
|
+
setActiveCategory(categoryNames[0]);
|
|
1232
|
+
}
|
|
1233
|
+
addGlobalListeners();
|
|
1234
|
+
requestAnimationFrame(() => {
|
|
1235
|
+
const panel = pickerRef.current?.querySelector(".dm-emoji-picker");
|
|
1236
|
+
if (panel && anchorRef.current) {
|
|
1237
|
+
cleanupFloatingRef.current?.();
|
|
1238
|
+
cleanupFloatingRef.current = positionFloatingOnce(anchorRef.current, panel, {
|
|
1239
|
+
placement: "bottom",
|
|
1240
|
+
offsetValue: 4
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
const input = pickerRef.current?.querySelector(".dm-emoji-picker-search input");
|
|
1244
|
+
input?.focus();
|
|
1245
|
+
});
|
|
1246
|
+
};
|
|
1247
|
+
editor.on("insertEmoji", handler);
|
|
1248
|
+
return () => {
|
|
1249
|
+
removeGlobalListeners();
|
|
1250
|
+
editor.off("insertEmoji", handler);
|
|
1251
|
+
};
|
|
1252
|
+
}, [editor]);
|
|
1253
|
+
const selectEmoji = useCallback((item) => {
|
|
1254
|
+
if (!editor) return;
|
|
1255
|
+
const cmd = editor.commands;
|
|
1256
|
+
if (cmd["insertEmoji"]) {
|
|
1257
|
+
cmd["insertEmoji"](item.name);
|
|
1258
|
+
}
|
|
1259
|
+
close();
|
|
1260
|
+
}, [editor, close]);
|
|
1261
|
+
const onSearch = useCallback((event) => {
|
|
1262
|
+
setSearchQuery(event.target.value);
|
|
1263
|
+
}, []);
|
|
1264
|
+
const scrollToCategory = useCallback((cat) => {
|
|
1265
|
+
setSearchQuery("");
|
|
1266
|
+
setActiveCategory(cat);
|
|
1267
|
+
requestAnimationFrame(() => {
|
|
1268
|
+
const grid = pickerRef.current?.querySelector(".dm-emoji-picker-grid");
|
|
1269
|
+
if (!grid) return;
|
|
1270
|
+
const label = grid.querySelector(`[data-category="${cat}"]`);
|
|
1271
|
+
if (label) {
|
|
1272
|
+
grid.scrollTo({ top: label.offsetTop - grid.offsetTop, behavior: "smooth" });
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
}, []);
|
|
1276
|
+
const activeCategoryRef = useRef(activeCategory);
|
|
1277
|
+
activeCategoryRef.current = activeCategory;
|
|
1278
|
+
const searchQueryRef = useRef(searchQuery);
|
|
1279
|
+
searchQueryRef.current = searchQuery;
|
|
1280
|
+
const onGridScroll = useCallback(() => {
|
|
1281
|
+
if (searchQueryRef.current) return;
|
|
1282
|
+
const grid = pickerRef.current?.querySelector(".dm-emoji-picker-grid");
|
|
1283
|
+
if (!grid) return;
|
|
1284
|
+
const labels = Array.from(grid.querySelectorAll(".dm-emoji-picker-category-label[data-category]"));
|
|
1285
|
+
let currentCat = "";
|
|
1286
|
+
for (const label of labels) {
|
|
1287
|
+
if (label.offsetTop - grid.offsetTop <= grid.scrollTop + 20) {
|
|
1288
|
+
currentCat = label.getAttribute("data-category") ?? "";
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (currentCat && currentCat !== activeCategoryRef.current) {
|
|
1292
|
+
setActiveCategory(currentCat);
|
|
1293
|
+
}
|
|
1294
|
+
}, []);
|
|
1295
|
+
return {
|
|
1296
|
+
isOpen,
|
|
1297
|
+
searchQuery,
|
|
1298
|
+
activeCategory,
|
|
1299
|
+
categories,
|
|
1300
|
+
categoryNames,
|
|
1301
|
+
filteredEmojis,
|
|
1302
|
+
frequentlyUsed,
|
|
1303
|
+
pickerRef,
|
|
1304
|
+
selectEmoji,
|
|
1305
|
+
onSearch,
|
|
1306
|
+
scrollToCategory,
|
|
1307
|
+
onGridScroll,
|
|
1308
|
+
close
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function getEmojiStorage(editor) {
|
|
1312
|
+
if (!editor) return null;
|
|
1313
|
+
const storage = editor.storage;
|
|
1314
|
+
return storage["emoji"] ?? null;
|
|
1315
|
+
}
|
|
1316
|
+
function setStorageOpen(editor, open) {
|
|
1317
|
+
if (!editor) return;
|
|
1318
|
+
const storage = getEmojiStorage(editor);
|
|
1319
|
+
if (storage) storage["isOpen"] = open;
|
|
1320
|
+
editor.view.dispatch(editor.view.state.tr);
|
|
1321
|
+
}
|
|
1322
|
+
var CATEGORY_ICONS = {
|
|
1323
|
+
"Smileys & Emotion": "\u{1F600}",
|
|
1324
|
+
"People & Body": "\u{1F44B}",
|
|
1325
|
+
"Animals & Nature": "\u{1F431}",
|
|
1326
|
+
"Food & Drink": "\u{1F355}",
|
|
1327
|
+
"Travel & Places": "\u{1F697}",
|
|
1328
|
+
"Activities": "\u26BD",
|
|
1329
|
+
"Objects": "\u{1F4A1}",
|
|
1330
|
+
"Symbols": "\u{1F523}",
|
|
1331
|
+
"Flags": "\u{1F3C1}"
|
|
1332
|
+
};
|
|
1333
|
+
function categoryIcon(cat) {
|
|
1334
|
+
return CATEGORY_ICONS[cat] ?? cat.charAt(0);
|
|
1335
|
+
}
|
|
1336
|
+
function formatName(name) {
|
|
1337
|
+
return name.replace(/_/g, " ");
|
|
1338
|
+
}
|
|
1339
|
+
function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
1340
|
+
const { editor: contextEditor } = useCurrentEditor();
|
|
1341
|
+
const editor = editorProp ?? contextEditor;
|
|
1342
|
+
const {
|
|
1343
|
+
isOpen,
|
|
1344
|
+
searchQuery,
|
|
1345
|
+
activeCategory,
|
|
1346
|
+
categoryNames,
|
|
1347
|
+
filteredEmojis,
|
|
1348
|
+
frequentlyUsed,
|
|
1349
|
+
pickerRef,
|
|
1350
|
+
selectEmoji,
|
|
1351
|
+
onSearch,
|
|
1352
|
+
scrollToCategory,
|
|
1353
|
+
onGridScroll,
|
|
1354
|
+
close,
|
|
1355
|
+
categories
|
|
1356
|
+
} = useEmojiPicker(editor, emojis);
|
|
1357
|
+
if (!isOpen) return /* @__PURE__ */ jsx("div", { ref: pickerRef, className: "dm-emoji-picker-host" });
|
|
1358
|
+
return /* @__PURE__ */ jsx("div", { ref: pickerRef, className: "dm-emoji-picker-host", children: /* @__PURE__ */ jsxs("div", { className: "dm-emoji-picker", children: [
|
|
1359
|
+
/* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-search", children: /* @__PURE__ */ jsx(
|
|
1360
|
+
"input",
|
|
1361
|
+
{
|
|
1362
|
+
type: "text",
|
|
1363
|
+
placeholder: "Search emoji...",
|
|
1364
|
+
value: searchQuery,
|
|
1365
|
+
onChange: onSearch,
|
|
1366
|
+
onKeyDown: (e) => {
|
|
1367
|
+
if (e.key === "Escape") close();
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
) }),
|
|
1371
|
+
/* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-tabs", role: "tablist", children: categoryNames.map((cat) => /* @__PURE__ */ jsx(
|
|
1372
|
+
"button",
|
|
1373
|
+
{
|
|
1374
|
+
type: "button",
|
|
1375
|
+
className: `dm-emoji-picker-tab${activeCategory === cat ? " dm-emoji-picker-tab--active" : ""}`,
|
|
1376
|
+
title: cat,
|
|
1377
|
+
"aria-label": cat,
|
|
1378
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1379
|
+
onClick: () => scrollToCategory(cat),
|
|
1380
|
+
children: categoryIcon(cat)
|
|
1381
|
+
},
|
|
1382
|
+
cat
|
|
1383
|
+
)) }),
|
|
1384
|
+
/* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-grid", onScroll: onGridScroll, children: searchQuery ? /* @__PURE__ */ jsx(Fragment$1, { children: filteredEmojis.length > 0 ? filteredEmojis.map((item) => /* @__PURE__ */ jsx(
|
|
1385
|
+
"button",
|
|
1386
|
+
{
|
|
1387
|
+
type: "button",
|
|
1388
|
+
className: "dm-emoji-swatch",
|
|
1389
|
+
title: formatName(item.name),
|
|
1390
|
+
"aria-label": formatName(item.name),
|
|
1391
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1392
|
+
onClick: () => selectEmoji(item),
|
|
1393
|
+
children: item.emoji
|
|
1394
|
+
},
|
|
1395
|
+
item.name
|
|
1396
|
+
)) : /* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-empty", children: "No emoji found" }) }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1397
|
+
frequentlyUsed.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1398
|
+
/* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-category-label", children: "Frequently Used" }),
|
|
1399
|
+
frequentlyUsed.map((item) => /* @__PURE__ */ jsx(
|
|
1400
|
+
"button",
|
|
1401
|
+
{
|
|
1402
|
+
type: "button",
|
|
1403
|
+
className: "dm-emoji-swatch",
|
|
1404
|
+
title: formatName(item.name),
|
|
1405
|
+
"aria-label": formatName(item.name),
|
|
1406
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1407
|
+
onClick: () => selectEmoji(item),
|
|
1408
|
+
children: item.emoji
|
|
1409
|
+
},
|
|
1410
|
+
item.name
|
|
1411
|
+
))
|
|
1412
|
+
] }),
|
|
1413
|
+
categoryNames.map((cat) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1414
|
+
/* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-category-label", "data-category": cat, children: cat }),
|
|
1415
|
+
(categories.get(cat) ?? []).map((item) => /* @__PURE__ */ jsx(
|
|
1416
|
+
"button",
|
|
1417
|
+
{
|
|
1418
|
+
type: "button",
|
|
1419
|
+
className: "dm-emoji-swatch",
|
|
1420
|
+
title: formatName(item.name),
|
|
1421
|
+
"aria-label": formatName(item.name),
|
|
1422
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1423
|
+
onClick: () => selectEmoji(item),
|
|
1424
|
+
children: item.emoji
|
|
1425
|
+
},
|
|
1426
|
+
item.name
|
|
1427
|
+
))
|
|
1428
|
+
] }, cat))
|
|
1429
|
+
] }) })
|
|
1430
|
+
] }) });
|
|
1431
|
+
}
|
|
1432
|
+
function Domternal({ children, deps, ...options }) {
|
|
1433
|
+
const { editor } = useEditor(options, deps);
|
|
1434
|
+
return /* @__PURE__ */ jsx(EditorProvider, { editor, children });
|
|
1435
|
+
}
|
|
1436
|
+
function DomternalContent({ className }) {
|
|
1437
|
+
const { editor } = useCurrentEditor();
|
|
1438
|
+
const containerRef = useRef(null);
|
|
1439
|
+
useEffect(() => {
|
|
1440
|
+
const container = containerRef.current;
|
|
1441
|
+
if (!container || !editor || editor.isDestroyed) return;
|
|
1442
|
+
const editorDom = editor.view.dom;
|
|
1443
|
+
if (editorDom.parentElement !== container) {
|
|
1444
|
+
container.appendChild(editorDom);
|
|
1445
|
+
}
|
|
1446
|
+
}, [editor]);
|
|
1447
|
+
const classes = className ? `dm-editor ${className}` : "dm-editor";
|
|
1448
|
+
return /* @__PURE__ */ jsx("div", { className: classes, children: /* @__PURE__ */ jsx("div", { ref: containerRef }) });
|
|
1449
|
+
}
|
|
1450
|
+
function DomternalLoading({ children }) {
|
|
1451
|
+
const { editor } = useCurrentEditor();
|
|
1452
|
+
if (editor) return null;
|
|
1453
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
1454
|
+
}
|
|
1455
|
+
function DomternalToolbarSub(props) {
|
|
1456
|
+
return /* @__PURE__ */ jsx(DomternalToolbar, { ...props });
|
|
1457
|
+
}
|
|
1458
|
+
function DomternalBubbleMenuSub(props) {
|
|
1459
|
+
return /* @__PURE__ */ jsx(DomternalBubbleMenu, { ...props });
|
|
1460
|
+
}
|
|
1461
|
+
function DomternalFloatingMenuSub(props) {
|
|
1462
|
+
return /* @__PURE__ */ jsx(DomternalFloatingMenu, { ...props });
|
|
1463
|
+
}
|
|
1464
|
+
function DomternalEmojiPickerSub(props) {
|
|
1465
|
+
return /* @__PURE__ */ jsx(DomternalEmojiPicker, { ...props });
|
|
1466
|
+
}
|
|
1467
|
+
DomternalContent.displayName = "Domternal.Content";
|
|
1468
|
+
DomternalLoading.displayName = "Domternal.Loading";
|
|
1469
|
+
DomternalToolbarSub.displayName = "Domternal.Toolbar";
|
|
1470
|
+
DomternalBubbleMenuSub.displayName = "Domternal.BubbleMenu";
|
|
1471
|
+
DomternalFloatingMenuSub.displayName = "Domternal.FloatingMenu";
|
|
1472
|
+
DomternalEmojiPickerSub.displayName = "Domternal.EmojiPicker";
|
|
1473
|
+
Domternal.Content = DomternalContent;
|
|
1474
|
+
Domternal.Loading = DomternalLoading;
|
|
1475
|
+
Domternal.Toolbar = DomternalToolbarSub;
|
|
1476
|
+
Domternal.BubbleMenu = DomternalBubbleMenuSub;
|
|
1477
|
+
Domternal.FloatingMenu = DomternalFloatingMenuSub;
|
|
1478
|
+
Domternal.EmojiPicker = DomternalEmojiPickerSub;
|
|
1479
|
+
var DomternalEditor = forwardRef(
|
|
1480
|
+
function DomternalEditor2(props, ref) {
|
|
1481
|
+
const {
|
|
1482
|
+
className,
|
|
1483
|
+
outputFormat = "html",
|
|
1484
|
+
value,
|
|
1485
|
+
onChange,
|
|
1486
|
+
content,
|
|
1487
|
+
children,
|
|
1488
|
+
...editorOptions
|
|
1489
|
+
} = props;
|
|
1490
|
+
const { editor, editorRef } = useEditor({
|
|
1491
|
+
...editorOptions,
|
|
1492
|
+
content: content ?? value ?? "",
|
|
1493
|
+
outputFormat
|
|
1494
|
+
});
|
|
1495
|
+
const state = useEditorState(editor);
|
|
1496
|
+
useImperativeHandle(ref, () => ({
|
|
1497
|
+
editor,
|
|
1498
|
+
htmlContent: state.htmlContent,
|
|
1499
|
+
jsonContent: state.jsonContent,
|
|
1500
|
+
isEmpty: state.isEmpty,
|
|
1501
|
+
isFocused: state.isFocused,
|
|
1502
|
+
isEditable: state.isEditable
|
|
1503
|
+
}), [editor, state]);
|
|
1504
|
+
const prevValueRef = useRef(value);
|
|
1505
|
+
useEffect(() => {
|
|
1506
|
+
if (value === void 0 || !editor || editor.isDestroyed) return;
|
|
1507
|
+
if (value === prevValueRef.current) return;
|
|
1508
|
+
prevValueRef.current = value;
|
|
1509
|
+
if (outputFormat === "html") {
|
|
1510
|
+
if (value !== editor.getHTML()) {
|
|
1511
|
+
editor.setContent(value, false);
|
|
1512
|
+
}
|
|
1513
|
+
} else {
|
|
1514
|
+
if (JSON.stringify(value) !== JSON.stringify(editor.getJSON())) {
|
|
1515
|
+
editor.setContent(value, false);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}, [value, editor, outputFormat]);
|
|
1519
|
+
const onChangeRef = useRef(onChange);
|
|
1520
|
+
onChangeRef.current = onChange;
|
|
1521
|
+
useEffect(() => {
|
|
1522
|
+
if (!editor || editor.isDestroyed || !onChangeRef.current) return;
|
|
1523
|
+
const handler = () => {
|
|
1524
|
+
const cb = onChangeRef.current;
|
|
1525
|
+
if (!cb) return;
|
|
1526
|
+
const val = outputFormat === "html" ? editor.getHTML() : editor.getJSON();
|
|
1527
|
+
cb(val);
|
|
1528
|
+
};
|
|
1529
|
+
editor.on("update", handler);
|
|
1530
|
+
return () => {
|
|
1531
|
+
editor.off("update", handler);
|
|
1532
|
+
};
|
|
1533
|
+
}, [editor, outputFormat]);
|
|
1534
|
+
const classes = className ? `dm-editor ${className}` : "dm-editor";
|
|
1535
|
+
return /* @__PURE__ */ jsxs(EditorProvider, { editor, children: [
|
|
1536
|
+
children,
|
|
1537
|
+
/* @__PURE__ */ jsx("div", { className: classes, children: /* @__PURE__ */ jsx("div", { ref: editorRef }) })
|
|
1538
|
+
] });
|
|
1539
|
+
}
|
|
1540
|
+
);
|
|
1541
|
+
function EditorContent({ editor, innerRef, ...htmlProps }) {
|
|
1542
|
+
const containerRef = useRef(null);
|
|
1543
|
+
useEffect(() => {
|
|
1544
|
+
const container = containerRef.current;
|
|
1545
|
+
if (!container || !editor || editor.isDestroyed) return;
|
|
1546
|
+
const editorDom = editor.view.dom;
|
|
1547
|
+
if (editorDom.parentElement !== container) {
|
|
1548
|
+
container.appendChild(editorDom);
|
|
1549
|
+
}
|
|
1550
|
+
return () => {
|
|
1551
|
+
};
|
|
1552
|
+
}, [editor]);
|
|
1553
|
+
return /* @__PURE__ */ jsx(
|
|
1554
|
+
"div",
|
|
1555
|
+
{
|
|
1556
|
+
ref: (node) => {
|
|
1557
|
+
containerRef.current = node;
|
|
1558
|
+
if (typeof innerRef === "function") innerRef(node);
|
|
1559
|
+
else if (innerRef) innerRef.current = node;
|
|
1560
|
+
},
|
|
1561
|
+
...htmlProps
|
|
1562
|
+
}
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
var ReactNodeViewContext = createContext(null);
|
|
1566
|
+
var ReactNodeViewProvider = ReactNodeViewContext.Provider;
|
|
1567
|
+
function useReactNodeView() {
|
|
1568
|
+
const context = useContext(ReactNodeViewContext);
|
|
1569
|
+
if (!context) {
|
|
1570
|
+
throw new Error("useReactNodeView must be used within a ReactNodeViewRenderer component");
|
|
1571
|
+
}
|
|
1572
|
+
return context;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/node-views/ReactNodeViewRenderer.ts
|
|
1576
|
+
function ReactNodeViewRenderer(component, options = {}) {
|
|
1577
|
+
const constructor = (node, _view, getPos, decorations) => {
|
|
1578
|
+
const ctx = constructor.__domternalContext;
|
|
1579
|
+
const editor = ctx?.editor;
|
|
1580
|
+
const extension = ctx?.extension ?? { name: node.type.name, options: {} };
|
|
1581
|
+
return new ReactNodeView(component, {
|
|
1582
|
+
editor,
|
|
1583
|
+
node,
|
|
1584
|
+
getPos,
|
|
1585
|
+
decorations,
|
|
1586
|
+
extension
|
|
1587
|
+
}, options);
|
|
1588
|
+
};
|
|
1589
|
+
return constructor;
|
|
1590
|
+
}
|
|
1591
|
+
var ReactNodeView = class {
|
|
1592
|
+
dom;
|
|
1593
|
+
contentDOM = null;
|
|
1594
|
+
root;
|
|
1595
|
+
component;
|
|
1596
|
+
editor;
|
|
1597
|
+
node;
|
|
1598
|
+
getPos;
|
|
1599
|
+
decorations;
|
|
1600
|
+
extension;
|
|
1601
|
+
selected = false;
|
|
1602
|
+
constructor(component, init, options) {
|
|
1603
|
+
this.component = component;
|
|
1604
|
+
this.editor = init.editor;
|
|
1605
|
+
this.node = init.node;
|
|
1606
|
+
this.getPos = init.getPos;
|
|
1607
|
+
this.decorations = init.decorations;
|
|
1608
|
+
this.extension = init.extension;
|
|
1609
|
+
const isInline = init.node.type.spec.group === "inline";
|
|
1610
|
+
const tag = options.as ?? (isInline ? "span" : "div");
|
|
1611
|
+
this.dom = document.createElement(tag);
|
|
1612
|
+
this.dom.setAttribute("data-node-view-wrapper", "");
|
|
1613
|
+
if (options.className) {
|
|
1614
|
+
this.dom.className = options.className;
|
|
1615
|
+
}
|
|
1616
|
+
if (options.contentDOMElement !== null) {
|
|
1617
|
+
const contentTag = options.contentDOMElement ?? (isInline ? "span" : "div");
|
|
1618
|
+
this.contentDOM = document.createElement(contentTag);
|
|
1619
|
+
this.contentDOM.setAttribute("data-node-view-content", "");
|
|
1620
|
+
this.contentDOM.style.whiteSpace = "pre-wrap";
|
|
1621
|
+
}
|
|
1622
|
+
this.root = createRoot(this.dom);
|
|
1623
|
+
this.render();
|
|
1624
|
+
}
|
|
1625
|
+
render() {
|
|
1626
|
+
const contextValue = {
|
|
1627
|
+
onDragStart: (event) => {
|
|
1628
|
+
if (this.editor.view.dragging) {
|
|
1629
|
+
event.dataTransfer?.setData("text/plain", this.node.textContent);
|
|
1630
|
+
}
|
|
1631
|
+
},
|
|
1632
|
+
nodeViewContentRef: (el) => {
|
|
1633
|
+
if (el && this.contentDOM && !el.contains(this.contentDOM)) {
|
|
1634
|
+
el.appendChild(this.contentDOM);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
const props = {
|
|
1639
|
+
editor: this.editor,
|
|
1640
|
+
node: this.node,
|
|
1641
|
+
selected: this.selected,
|
|
1642
|
+
getPos: this.getPos,
|
|
1643
|
+
extension: this.extension,
|
|
1644
|
+
decorations: this.decorations,
|
|
1645
|
+
updateAttributes: (attrs) => {
|
|
1646
|
+
const pos = this.getPos();
|
|
1647
|
+
const { tr } = this.editor.view.state;
|
|
1648
|
+
tr.setNodeMarkup(pos, void 0, { ...this.node.attrs, ...attrs });
|
|
1649
|
+
this.editor.view.dispatch(tr);
|
|
1650
|
+
},
|
|
1651
|
+
deleteNode: () => {
|
|
1652
|
+
const pos = this.getPos();
|
|
1653
|
+
const { tr } = this.editor.view.state;
|
|
1654
|
+
tr.delete(pos, pos + this.node.nodeSize);
|
|
1655
|
+
this.editor.view.dispatch(tr);
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
this.root.render(
|
|
1659
|
+
createElement(
|
|
1660
|
+
ReactNodeViewProvider,
|
|
1661
|
+
{ value: contextValue },
|
|
1662
|
+
createElement(this.component, props)
|
|
1663
|
+
)
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
update(node, decorations) {
|
|
1667
|
+
if (node.type.name !== this.node.type.name) return false;
|
|
1668
|
+
this.node = node;
|
|
1669
|
+
this.decorations = decorations;
|
|
1670
|
+
this.render();
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
selectNode() {
|
|
1674
|
+
this.selected = true;
|
|
1675
|
+
this.render();
|
|
1676
|
+
}
|
|
1677
|
+
deselectNode() {
|
|
1678
|
+
this.selected = false;
|
|
1679
|
+
this.render();
|
|
1680
|
+
}
|
|
1681
|
+
destroy() {
|
|
1682
|
+
const root = this.root;
|
|
1683
|
+
setTimeout(() => root.unmount(), 0);
|
|
1684
|
+
}
|
|
1685
|
+
ignoreMutation(mutation) {
|
|
1686
|
+
if (!this.contentDOM) return true;
|
|
1687
|
+
return !this.contentDOM.contains(mutation.target);
|
|
1688
|
+
}
|
|
1689
|
+
stopEvent() {
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
function NodeViewWrapper({ as: Tag = "div", style, ...props }) {
|
|
1694
|
+
const { onDragStart } = useReactNodeView();
|
|
1695
|
+
return /* @__PURE__ */ jsx(
|
|
1696
|
+
Tag,
|
|
1697
|
+
{
|
|
1698
|
+
...props,
|
|
1699
|
+
"data-node-view-wrapper": "",
|
|
1700
|
+
style: { whiteSpace: "normal", ...style },
|
|
1701
|
+
onDragStart
|
|
1702
|
+
}
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
function NodeViewContent({ as: Tag = "div", style, ...props }) {
|
|
1706
|
+
const { nodeViewContentRef } = useReactNodeView();
|
|
1707
|
+
return /* @__PURE__ */ jsx(
|
|
1708
|
+
Tag,
|
|
1709
|
+
{
|
|
1710
|
+
...props,
|
|
1711
|
+
ref: nodeViewContentRef,
|
|
1712
|
+
"data-node-view-content": "",
|
|
1713
|
+
style: { whiteSpace: "pre-wrap", ...style }
|
|
1714
|
+
}
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
export { DEFAULT_EXTENSIONS, Domternal, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalToolbar, EditorContent, EditorProvider, NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useCurrentEditor, useEditor, useEditorState, useReactNodeView };
|
|
1719
|
+
//# sourceMappingURL=index.js.map
|
|
1720
|
+
//# sourceMappingURL=index.js.map
|