@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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -0
  3. package/dist/Domternal.d.ts +79 -0
  4. package/dist/Domternal.d.ts.map +1 -0
  5. package/dist/DomternalEditor.d.ts +48 -0
  6. package/dist/DomternalEditor.d.ts.map +1 -0
  7. package/dist/DomternalFloatingMenu.d.ts +13 -0
  8. package/dist/DomternalFloatingMenu.d.ts.map +1 -0
  9. package/dist/EditorContent.d.ts +22 -0
  10. package/dist/EditorContent.d.ts.map +1 -0
  11. package/dist/EditorContext.d.ts +38 -0
  12. package/dist/EditorContext.d.ts.map +1 -0
  13. package/dist/bubble-menu/DomternalBubbleMenu.d.ts +21 -0
  14. package/dist/bubble-menu/DomternalBubbleMenu.d.ts.map +1 -0
  15. package/dist/bubble-menu/useBubbleMenu.d.ts +26 -0
  16. package/dist/bubble-menu/useBubbleMenu.d.ts.map +1 -0
  17. package/dist/emoji-picker/DomternalEmojiPicker.d.ts +10 -0
  18. package/dist/emoji-picker/DomternalEmojiPicker.d.ts.map +1 -0
  19. package/dist/emoji-picker/useEmojiPicker.d.ts +22 -0
  20. package/dist/emoji-picker/useEmojiPicker.d.ts.map +1 -0
  21. package/dist/index.d.ts +467 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +1720 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/node-views/NodeViewContent.d.ts +11 -0
  26. package/dist/node-views/NodeViewContent.d.ts.map +1 -0
  27. package/dist/node-views/NodeViewWrapper.d.ts +11 -0
  28. package/dist/node-views/NodeViewWrapper.d.ts.map +1 -0
  29. package/dist/node-views/ReactNodeViewContext.d.ts +12 -0
  30. package/dist/node-views/ReactNodeViewContext.d.ts.map +1 -0
  31. package/dist/node-views/ReactNodeViewRenderer.d.ts +101 -0
  32. package/dist/node-views/ReactNodeViewRenderer.d.ts.map +1 -0
  33. package/dist/toolbar/DomternalToolbar.d.ts +11 -0
  34. package/dist/toolbar/DomternalToolbar.d.ts.map +1 -0
  35. package/dist/toolbar/ToolbarButton.d.ts +14 -0
  36. package/dist/toolbar/ToolbarButton.d.ts.map +1 -0
  37. package/dist/toolbar/ToolbarDropdown.d.ts +16 -0
  38. package/dist/toolbar/ToolbarDropdown.d.ts.map +1 -0
  39. package/dist/toolbar/ToolbarDropdownPanel.d.ts +9 -0
  40. package/dist/toolbar/ToolbarDropdownPanel.d.ts.map +1 -0
  41. package/dist/toolbar/useComputedStyle.d.ts +12 -0
  42. package/dist/toolbar/useComputedStyle.d.ts.map +1 -0
  43. package/dist/toolbar/useKeyboardNav.d.ts +6 -0
  44. package/dist/toolbar/useKeyboardNav.d.ts.map +1 -0
  45. package/dist/toolbar/useToolbarController.d.ts +19 -0
  46. package/dist/toolbar/useToolbarController.d.ts.map +1 -0
  47. package/dist/toolbar/useToolbarIcons.d.ts +11 -0
  48. package/dist/toolbar/useToolbarIcons.d.ts.map +1 -0
  49. package/dist/toolbar/useTooltip.d.ts +5 -0
  50. package/dist/toolbar/useTooltip.d.ts.map +1 -0
  51. package/dist/useEditor.d.ts +79 -0
  52. package/dist/useEditor.d.ts.map +1 -0
  53. package/dist/useEditorState.d.ts +27 -0
  54. package/dist/useEditorState.d.ts.map +1 -0
  55. 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