@domternal/vue 0.6.1 → 0.7.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 (62) hide show
  1. package/README.md +12 -10
  2. package/dist/Domternal.d.ts +34 -0
  3. package/dist/Domternal.d.ts.map +1 -0
  4. package/dist/DomternalEditor.d.ts +224 -0
  5. package/dist/DomternalEditor.d.ts.map +1 -0
  6. package/dist/DomternalFloatingMenu.d.ts +94 -0
  7. package/dist/DomternalFloatingMenu.d.ts.map +1 -0
  8. package/dist/EditorContent.d.ts +44 -0
  9. package/dist/EditorContent.d.ts.map +1 -0
  10. package/dist/EditorContext.d.ts +38 -0
  11. package/dist/EditorContext.d.ts.map +1 -0
  12. package/dist/bubble-menu/DomternalBubbleMenu.d.ts +87 -0
  13. package/dist/bubble-menu/DomternalBubbleMenu.d.ts.map +1 -0
  14. package/dist/bubble-menu/useBubbleMenu.d.ts +56 -0
  15. package/dist/bubble-menu/useBubbleMenu.d.ts.map +1 -0
  16. package/dist/emoji-picker/DomternalEmojiPicker.d.ts +31 -0
  17. package/dist/emoji-picker/DomternalEmojiPicker.d.ts.map +1 -0
  18. package/dist/emoji-picker/useEmojiPicker.d.ts +24 -0
  19. package/dist/emoji-picker/useEmojiPicker.d.ts.map +1 -0
  20. package/dist/index.d.ts +152 -40
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +1178 -302
  23. package/dist/index.js.map +1 -1
  24. package/dist/node-views/NodeViewContent.d.ts +30 -0
  25. package/dist/node-views/NodeViewContent.d.ts.map +1 -0
  26. package/dist/node-views/NodeViewWrapper.d.ts +29 -0
  27. package/dist/node-views/NodeViewWrapper.d.ts.map +1 -0
  28. package/dist/node-views/VueNodeViewContext.d.ts +27 -0
  29. package/dist/node-views/VueNodeViewContext.d.ts.map +1 -0
  30. package/dist/node-views/VueNodeViewRenderer.d.ts +88 -0
  31. package/dist/node-views/VueNodeViewRenderer.d.ts.map +1 -0
  32. package/dist/notion-color-picker/DomternalNotionColorPicker.d.ts +22 -0
  33. package/dist/notion-color-picker/DomternalNotionColorPicker.d.ts.map +1 -0
  34. package/dist/notion-color-picker/index.d.ts +5 -0
  35. package/dist/notion-color-picker/index.d.ts.map +1 -0
  36. package/dist/notion-color-picker/useNotionColorPicker.d.ts +35 -0
  37. package/dist/notion-color-picker/useNotionColorPicker.d.ts.map +1 -0
  38. package/dist/toolbar/DomternalToolbar.d.ts +41 -0
  39. package/dist/toolbar/DomternalToolbar.d.ts.map +1 -0
  40. package/dist/toolbar/ToolbarButton.d.ts +72 -0
  41. package/dist/toolbar/ToolbarButton.d.ts.map +1 -0
  42. package/dist/toolbar/ToolbarDropdown.d.ts +76 -0
  43. package/dist/toolbar/ToolbarDropdown.d.ts.map +1 -0
  44. package/dist/toolbar/ToolbarDropdownPanel.d.ts +34 -0
  45. package/dist/toolbar/ToolbarDropdownPanel.d.ts.map +1 -0
  46. package/dist/toolbar/useComputedStyle.d.ts +12 -0
  47. package/dist/toolbar/useComputedStyle.d.ts.map +1 -0
  48. package/dist/toolbar/useKeyboardNav.d.ts +9 -0
  49. package/dist/toolbar/useKeyboardNav.d.ts.map +1 -0
  50. package/dist/toolbar/useToolbarController.d.ts +24 -0
  51. package/dist/toolbar/useToolbarController.d.ts.map +1 -0
  52. package/dist/toolbar/useToolbarIcons.d.ts +12 -0
  53. package/dist/toolbar/useToolbarIcons.d.ts.map +1 -0
  54. package/dist/toolbar/useTooltip.d.ts +5 -0
  55. package/dist/toolbar/useTooltip.d.ts.map +1 -0
  56. package/dist/useEditor.d.ts +63 -0
  57. package/dist/useEditor.d.ts.map +1 -0
  58. package/dist/useEditorState.d.ts +28 -0
  59. package/dist/useEditorState.d.ts.map +1 -0
  60. package/dist/utils.d.ts +39 -0
  61. package/dist/utils.d.ts.map +1 -0
  62. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { defineComponent, ref, watchEffect, h, watch, computed, Fragment, onMounted, onScopeDispose, inject, shallowRef, provide, getCurrentInstance, markRaw, customRef, shallowReactive, render } from 'vue';
2
- import { PluginKey, ToolbarController, createFloatingMenuPlugin, Editor, Document, Paragraph, Text, BaseKeymap, History, positionFloatingOnce, defaultIcons, createBubbleMenuPlugin } from '@domternal/core';
1
+ import { defineComponent, h, computed, Fragment, ref, watch, shallowRef, onMounted, nextTick, onScopeDispose, watchEffect, Teleport, inject, provide, getCurrentInstance, customRef, markRaw, shallowReactive, render } from 'vue';
2
+ import { positionFloatingOnce, PluginKey, positionFloating, ToolbarController, FloatingMenuController, defaultIcons, defaultBubbleContexts, createBubbleMenuPlugin, Editor, Document, Paragraph, Text, BaseKeymap, History } from '@domternal/core';
3
3
  export { Editor, generateHTML, generateJSON, generateText } from '@domternal/core';
4
+ import { createFloatingMenuPlugin } from '@domternal/extension-block-menu';
4
5
 
5
6
  // src/useEditor.ts
6
7
  var DEFAULT_EXTENSIONS = [Document, Paragraph, Text, BaseKeymap, History];
@@ -46,7 +47,7 @@ function useEditor(options = {}) {
46
47
  pendingContent = current.getJSON();
47
48
  options.onDestroy?.();
48
49
  const dom = current.view.dom;
49
- const parent = dom?.parentNode;
50
+ const parent = dom.parentNode;
50
51
  if (parent) {
51
52
  const clone = dom.cloneNode(true);
52
53
  clone.style.pointerEvents = "none";
@@ -242,7 +243,8 @@ function provideEditor(editor) {
242
243
  if (instance) {
243
244
  const buildCtx = () => {
244
245
  const ctx = Object.create(instance.appContext);
245
- ctx.provides = instance.provides;
246
+ const instanceWithProvides = instance;
247
+ ctx.provides = instanceWithProvides.provides;
246
248
  return ctx;
247
249
  };
248
250
  pendingAppContextStore.value = buildCtx();
@@ -259,189 +261,6 @@ function useCurrentEditor() {
259
261
  const editor = inject(EDITOR_KEY, shallowRef(null));
260
262
  return { editor };
261
263
  }
262
- var Domternal = defineComponent({
263
- name: "Domternal",
264
- props: {
265
- extensions: { type: Array, default: void 0 },
266
- content: { type: [String, Object], default: "" },
267
- editable: { type: Boolean, default: true },
268
- autofocus: { type: [Boolean, String, Number], default: false },
269
- outputFormat: { type: String, default: "html" },
270
- immediatelyRender: { type: Boolean, default: false },
271
- onCreate: { type: Function, default: void 0 },
272
- onUpdate: { type: Function, default: void 0 },
273
- onSelectionChange: { type: Function, default: void 0 },
274
- onFocus: { type: Function, default: void 0 },
275
- onBlur: { type: Function, default: void 0 },
276
- onDestroy: { type: Function, default: void 0 }
277
- },
278
- setup(props, { slots }) {
279
- const { editor } = useEditor({
280
- ...props.extensions && { extensions: props.extensions },
281
- content: props.content,
282
- editable: props.editable,
283
- autofocus: props.autofocus,
284
- outputFormat: props.outputFormat,
285
- immediatelyRender: props.immediatelyRender,
286
- ...props.onCreate && { onCreate: props.onCreate },
287
- ...props.onUpdate && { onUpdate: props.onUpdate },
288
- ...props.onSelectionChange && { onSelectionChange: props.onSelectionChange },
289
- ...props.onFocus && { onFocus: props.onFocus },
290
- ...props.onBlur && { onBlur: props.onBlur },
291
- ...props.onDestroy && { onDestroy: props.onDestroy }
292
- });
293
- provideEditor(editor);
294
- return () => slots["default"]?.();
295
- }
296
- });
297
- var DomternalContent = defineComponent({
298
- name: "DomternalContent",
299
- props: {
300
- class: { type: String, default: void 0 }
301
- },
302
- setup(props) {
303
- const { editor } = useCurrentEditor();
304
- const containerRef = ref();
305
- watchEffect(() => {
306
- const container = containerRef.value;
307
- const ed = editor.value;
308
- if (!container || !ed || ed.isDestroyed) return;
309
- const editorDom = ed.view.dom;
310
- if (editorDom.parentElement !== container) {
311
- container.appendChild(editorDom);
312
- }
313
- });
314
- return () => {
315
- const classes = props.class ? `dm-editor ${props.class}` : "dm-editor";
316
- return h("div", { class: classes, "data-dm-editor-ui": "" }, [h("div", { ref: containerRef })]);
317
- };
318
- }
319
- });
320
- var DomternalLoading = defineComponent({
321
- name: "DomternalLoading",
322
- setup(_props, { slots }) {
323
- const { editor } = useCurrentEditor();
324
- return () => editor.value ? null : slots["default"]?.();
325
- }
326
- });
327
- Domternal.Content = DomternalContent;
328
- Domternal.Loading = DomternalLoading;
329
- var DomternalEditor = defineComponent({
330
- name: "DomternalEditor",
331
- props: {
332
- extensions: { type: Array, default: void 0 },
333
- content: { type: [String, Object], default: void 0 },
334
- editable: { type: Boolean, default: true },
335
- autofocus: { type: [Boolean, String, Number], default: false },
336
- immediatelyRender: { type: Boolean, default: false },
337
- outputFormat: { type: String, default: "html" },
338
- modelValue: { type: [String, Object], default: void 0 },
339
- class: { type: String, default: void 0 },
340
- onCreate: { type: Function, default: void 0 },
341
- onUpdate: { type: Function, default: void 0 },
342
- onSelectionChange: { type: Function, default: void 0 },
343
- onFocus: { type: Function, default: void 0 },
344
- onBlur: { type: Function, default: void 0 },
345
- onDestroy: { type: Function, default: void 0 }
346
- },
347
- emits: {
348
- "update:modelValue": (_value) => true
349
- },
350
- setup(props, { slots, emit, expose }) {
351
- const { editor, editorRef } = useEditor({
352
- ...props.extensions && { extensions: props.extensions },
353
- content: props.modelValue ?? props.content ?? "",
354
- editable: props.editable,
355
- autofocus: props.autofocus,
356
- immediatelyRender: props.immediatelyRender,
357
- outputFormat: props.outputFormat,
358
- ...props.onCreate && { onCreate: props.onCreate },
359
- ...props.onUpdate && { onUpdate: props.onUpdate },
360
- ...props.onSelectionChange && { onSelectionChange: props.onSelectionChange },
361
- ...props.onFocus && { onFocus: props.onFocus },
362
- ...props.onBlur && { onBlur: props.onBlur },
363
- ...props.onDestroy && { onDestroy: props.onDestroy }
364
- });
365
- const state = useEditorState(editor);
366
- expose({
367
- editor,
368
- htmlContent: state.htmlContent,
369
- jsonContent: state.jsonContent,
370
- isEmpty: state.isEmpty,
371
- isFocused: state.isFocused
372
- });
373
- provideEditor(editor);
374
- const prevModelValue = ref(props.modelValue);
375
- watch(
376
- () => props.modelValue,
377
- (newValue) => {
378
- if (newValue === void 0) return;
379
- const ed = editor.value;
380
- if (!ed || ed.isDestroyed) return;
381
- if (newValue === prevModelValue.value) return;
382
- prevModelValue.value = newValue;
383
- if (props.outputFormat === "html") {
384
- if (newValue !== ed.getHTML()) {
385
- ed.setContent(newValue, false);
386
- }
387
- } else {
388
- if (JSON.stringify(newValue) !== JSON.stringify(ed.getJSON())) {
389
- ed.setContent(newValue, false);
390
- }
391
- }
392
- },
393
- { flush: "post" }
394
- );
395
- watch(editor, (ed, _oldEd, onCleanup) => {
396
- if (!ed || ed.isDestroyed) return;
397
- const handler = () => {
398
- const val = props.outputFormat === "html" ? ed.getHTML() : ed.getJSON();
399
- prevModelValue.value = val;
400
- emit("update:modelValue", val);
401
- };
402
- ed.on("update", handler);
403
- onCleanup(() => {
404
- ed.off("update", handler);
405
- });
406
- }, { immediate: true });
407
- return () => {
408
- const classes = props.class ? `dm-editor ${props.class}` : "dm-editor";
409
- return [
410
- h("div", { class: classes, "data-dm-editor-ui": "" }, [h("div", { ref: editorRef })]),
411
- slots["default"]?.()
412
- ];
413
- };
414
- }
415
- });
416
- var EditorContent = defineComponent({
417
- name: "EditorContent",
418
- props: {
419
- editor: {
420
- type: Object,
421
- default: null
422
- },
423
- class: {
424
- type: String,
425
- default: void 0
426
- }
427
- },
428
- setup(props) {
429
- const containerRef = ref();
430
- watchEffect(() => {
431
- const container = containerRef.value;
432
- const editor = props.editor;
433
- if (!container || !editor || editor.isDestroyed) return;
434
- const editorDom = editor.view.dom;
435
- if (editorDom.parentElement !== container) {
436
- container.appendChild(editorDom);
437
- }
438
- });
439
- return () => h("div", {
440
- ref: containerRef,
441
- class: props.class
442
- });
443
- }
444
- });
445
264
  function useToolbarController(editor, layout) {
446
265
  const groups = shallowRef([]);
447
266
  const focusedIndex = ref(0);
@@ -527,8 +346,9 @@ function useToolbarController(editor, layout) {
527
346
  function isDropdownActive(dropdown) {
528
347
  if (dropdown.layout === "grid") return false;
529
348
  if (dropdown.dynamicLabel) return false;
530
- if (!controller) return false;
531
- return dropdown.items.some((item) => controller.activeMap.get(item.name) ?? false);
349
+ const ctl = controller;
350
+ if (!ctl) return false;
351
+ return dropdown.items.some((item) => ctl.activeMap.get(item.name) ?? false);
532
352
  }
533
353
  function getAriaExpanded(item) {
534
354
  if (!item.emitEvent) return null;
@@ -742,7 +562,9 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
742
562
  const btn = document.activeElement;
743
563
  if (btn?.getAttribute("aria-haspopup") && btn.closest(".dm-toolbar")) {
744
564
  btn.click();
745
- requestAnimationFrame(() => focusDropdownItem(0, true));
565
+ requestAnimationFrame(() => {
566
+ focusDropdownItem(0, true);
567
+ });
746
568
  }
747
569
  }
748
570
  break;
@@ -777,31 +599,25 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
777
599
  }
778
600
 
779
601
  // src/toolbar/useComputedStyle.ts
602
+ function resolveElementAtCursor(editor) {
603
+ const { from } = editor.state.selection;
604
+ const { node } = editor.view.domAtPos(from);
605
+ return node instanceof HTMLElement ? node : node.parentElement;
606
+ }
780
607
  function getComputedStyleAtCursor(editor, prop) {
781
608
  try {
782
- const { from } = editor.state.selection;
783
- const domAtPos = editor.view.domAtPos(from);
784
- let node = domAtPos.node;
785
- if (!(node instanceof HTMLElement)) {
786
- node = node.parentElement;
787
- }
609
+ const node = resolveElementAtCursor(editor);
788
610
  if (!node) return null;
789
- const el = node;
790
- const inline = el.style.getPropertyValue(prop);
611
+ const inline = node.style.getPropertyValue(prop);
791
612
  if (inline) return inline;
792
- return window.getComputedStyle(el).getPropertyValue(prop) || null;
613
+ return window.getComputedStyle(node).getPropertyValue(prop) || null;
793
614
  } catch {
794
615
  return null;
795
616
  }
796
617
  }
797
618
  function getInlineStyleAtCursor(editor, prop) {
798
619
  try {
799
- const { from } = editor.state.selection;
800
- const domAtPos = editor.view.domAtPos(from);
801
- let node = domAtPos.node;
802
- if (!(node instanceof HTMLElement)) {
803
- node = node.parentElement;
804
- }
620
+ const node = resolveElementAtCursor(editor);
805
621
  if (!node) return null;
806
622
  return node.style.getPropertyValue(prop) || null;
807
623
  } catch {
@@ -834,9 +650,15 @@ var ToolbarButton = defineComponent({
834
650
  "aria-expanded": props.ariaExpanded === "true" ? true : void 0,
835
651
  "aria-label": props.item.label,
836
652
  title: props.tooltip,
837
- onMousedown: (e) => e.preventDefault(),
838
- onClick: (e) => emit("click", props.item, e),
839
- onFocus: () => emit("focus", props.item.name)
653
+ onMousedown: (e) => {
654
+ e.preventDefault();
655
+ },
656
+ onClick: (e) => {
657
+ emit("click", props.item, e);
658
+ },
659
+ onFocus: () => {
660
+ emit("focus", props.item.name);
661
+ }
840
662
  });
841
663
  }
842
664
  });
@@ -872,8 +694,12 @@ var ToolbarDropdownPanel = defineComponent({
872
694
  "aria-label": sub.label,
873
695
  title: sub.label,
874
696
  style: { backgroundColor: sub.color },
875
- onMousedown: (e) => e.preventDefault(),
876
- onClick: (e) => emit("itemClick", sub, e)
697
+ onMousedown: (e) => {
698
+ e.preventDefault();
699
+ },
700
+ onClick: (e) => {
701
+ emit("itemClick", sub, e);
702
+ }
877
703
  }) : h("button", {
878
704
  key: sub.name,
879
705
  type: "button",
@@ -882,8 +708,12 @@ var ToolbarDropdownPanel = defineComponent({
882
708
  tabindex: -1,
883
709
  "aria-label": sub.label,
884
710
  innerHTML: getCachedItemContent(sub.icon, sub.label),
885
- onMousedown: (e) => e.preventDefault(),
886
- onClick: (e) => emit("itemClick", sub, e)
711
+ onMousedown: (e) => {
712
+ e.preventDefault();
713
+ },
714
+ onClick: (e) => {
715
+ emit("itemClick", sub, e);
716
+ }
887
717
  })
888
718
  )
889
719
  );
@@ -908,8 +738,12 @@ var ToolbarDropdownPanel = defineComponent({
908
738
  onVnodeMounted: (vnode) => {
909
739
  if (sub.style && vnode.el) vnode.el.setAttribute("style", sub.style);
910
740
  },
911
- onMousedown: (e) => e.preventDefault(),
912
- onClick: (e) => emit("itemClick", sub, e)
741
+ onMousedown: (e) => {
742
+ e.preventDefault();
743
+ },
744
+ onClick: (e) => {
745
+ emit("itemClick", sub, e);
746
+ }
913
747
  })
914
748
  )
915
749
  );
@@ -948,9 +782,15 @@ var ToolbarDropdown = defineComponent({
948
782
  disabled: props.isDisabled,
949
783
  "data-dropdown": props.dropdown.name,
950
784
  innerHTML: props.triggerHtml,
951
- onMousedown: (e) => e.preventDefault(),
952
- onClick: () => emit("toggle", props.dropdown),
953
- onFocus: () => emit("focus", props.dropdown.name)
785
+ onMousedown: (e) => {
786
+ e.preventDefault();
787
+ },
788
+ onClick: () => {
789
+ emit("toggle", props.dropdown);
790
+ },
791
+ onFocus: () => {
792
+ emit("focus", props.dropdown.name);
793
+ }
954
794
  })
955
795
  ];
956
796
  if (props.isOpen) {
@@ -959,7 +799,9 @@ var ToolbarDropdown = defineComponent({
959
799
  dropdown: props.dropdown,
960
800
  isActive: props.isActive,
961
801
  getCachedItemContent: props.getCachedItemContent,
962
- onItemClick: (item, event) => emit("itemClick", item, event)
802
+ onItemClick: (item, event) => {
803
+ emit("itemClick", item, event);
804
+ }
963
805
  })
964
806
  );
965
807
  }
@@ -1011,20 +853,23 @@ var DomternalToolbar = defineComponent({
1011
853
  closeDropdown();
1012
854
  }
1013
855
  if (item.emitEvent) {
1014
- const anchor = event?.target?.closest?.(".dm-toolbar-button") ?? event?.target;
856
+ const target = event?.target;
857
+ const anchor = target?.closest(".dm-toolbar-button") ?? target;
1015
858
  editor.emit(item.emitEvent, { anchorElement: anchor });
1016
859
  return;
1017
860
  }
1018
861
  executeCommand(item);
1019
- requestAnimationFrame(() => editor.view.focus());
862
+ requestAnimationFrame(() => {
863
+ editor.view.focus();
864
+ });
1020
865
  }
1021
866
  function onDropdownItemClick(item, event) {
1022
867
  const editor = props.editor ?? contextEditor.value;
1023
868
  if (!editor) return;
1024
869
  let anchor;
1025
870
  if (item.emitEvent) {
1026
- const wrapper = event.target?.closest?.(".dm-toolbar-dropdown-wrapper");
1027
- anchor = wrapper?.querySelector(".dm-toolbar-dropdown-trigger");
871
+ const wrapper = event.target.closest(".dm-toolbar-dropdown-wrapper");
872
+ anchor = wrapper?.querySelector(".dm-toolbar-dropdown-trigger") ?? void 0;
1028
873
  }
1029
874
  closeDropdown();
1030
875
  if (item.emitEvent) {
@@ -1032,7 +877,9 @@ var DomternalToolbar = defineComponent({
1032
877
  } else {
1033
878
  executeCommand(item);
1034
879
  }
1035
- requestAnimationFrame(() => editor.view.focus());
880
+ requestAnimationFrame(() => {
881
+ editor.view.focus();
882
+ });
1036
883
  }
1037
884
  function onButtonFocus(name) {
1038
885
  const index = controllerRef.current?.getFlatIndex(name) ?? -1;
@@ -1072,7 +919,9 @@ var DomternalToolbar = defineComponent({
1072
919
  tooltip: getTooltip(btn),
1073
920
  iconHtml: getCachedIcon(btn.icon),
1074
921
  ariaExpanded: getAriaExpanded(btn),
1075
- onClick: (clickedItem, event) => onButtonClick(clickedItem, event),
922
+ onClick: (clickedItem, event) => {
923
+ onButtonClick(clickedItem, event);
924
+ },
1076
925
  onFocus: onButtonFocus
1077
926
  });
1078
927
  }
@@ -1081,18 +930,18 @@ var DomternalToolbar = defineComponent({
1081
930
  const activeItem = dd.items.find((sub) => controllerRef.current?.activeMap.get(sub.name));
1082
931
  let triggerHtml = getDropdownTriggerHtml(dd, activeItem);
1083
932
  if (dd.dynamicLabel && !activeItem && dd.computedStyleProperty) {
1084
- let computed6;
933
+ let computed7;
1085
934
  if (dd.computedStyleProperty === "font-family") {
1086
- computed6 = getInlineStyleAtCursor(editor, dd.computedStyleProperty);
1087
- if (computed6) {
1088
- const first = computed6.split(",")[0]?.replace(/['"]+/g, "").trim();
1089
- computed6 = first || null;
935
+ computed7 = getInlineStyleAtCursor(editor, dd.computedStyleProperty);
936
+ if (computed7) {
937
+ const first = computed7.split(",")[0]?.replace(/['"]+/g, "").trim();
938
+ computed7 = first ?? null;
1090
939
  }
1091
940
  } else {
1092
- computed6 = getComputedStyleAtCursor(editor, dd.computedStyleProperty);
941
+ computed7 = getComputedStyleAtCursor(editor, dd.computedStyleProperty);
1093
942
  }
1094
- if (computed6) {
1095
- triggerHtml = `<span class="dm-toolbar-trigger-label">${computed6}</span>${DROPDOWN_CARET}`;
943
+ if (computed7) {
944
+ triggerHtml = `<span class="dm-toolbar-trigger-label">${computed7}</span>${DROPDOWN_CARET}`;
1096
945
  }
1097
946
  }
1098
947
  return h(ToolbarDropdown, {
@@ -1119,6 +968,15 @@ var DomternalToolbar = defineComponent({
1119
968
  };
1120
969
  }
1121
970
  });
971
+ var INITIAL_TRAILING_STATE = {
972
+ isNodeSelection: false,
973
+ showColorPickerButton: false,
974
+ showBlockMenuButton: false,
975
+ blockMenuButtonDisabled: false,
976
+ currentTextColorVar: null,
977
+ currentBgColorVar: null,
978
+ hasAnyColor: false
979
+ };
1122
980
  function isInsideTableCell($pos) {
1123
981
  for (let d = $pos.depth; d > 0; d--) {
1124
982
  const name = $pos.node(d).type.name;
@@ -1134,26 +992,37 @@ function findCellNode(pos) {
1134
992
  return null;
1135
993
  }
1136
994
  function useBubbleMenu(options) {
1137
- const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts } = options;
995
+ const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts: explicitContexts, icons: iconsRef } = options;
1138
996
  const menuRef = ref();
1139
- const pluginKey = new PluginKey("vueBubbleMenu-" + Math.random().toString(36).slice(2, 8));
997
+ const cryptoRef = globalThis.crypto;
998
+ const pluginKey = new PluginKey(
999
+ "vueBubbleMenu-" + (cryptoRef?.randomUUID?.().slice(0, 8) ?? Math.random().toString(36).slice(2, 8))
1000
+ );
1140
1001
  const resolvedItems = shallowRef([]);
1141
1002
  const activeVersion = useDebouncedRef(0);
1003
+ const trailing = shallowRef(INITIAL_TRAILING_STATE);
1142
1004
  const activeMapRef = /* @__PURE__ */ new Map();
1143
1005
  const disabledMapRef = /* @__PURE__ */ new Map();
1144
1006
  let itemMap;
1007
+ let dropdownMap;
1145
1008
  let bubbleDefaults;
1146
1009
  let currentResolvedItems = [];
1147
1010
  let initialized = false;
1148
1011
  let stopEditorWatch = null;
1149
1012
  const doInit = (ed) => {
1150
- if (initialized || !ed || ed.isDestroyed || !menuRef.value) return;
1013
+ if (initialized || ed.isDestroyed || !menuRef.value) return;
1151
1014
  initialized = true;
1015
+ const contexts = explicitContexts ?? (items ? void 0 : defaultBubbleContexts(ed));
1016
+ const exts = ed.extensionManager.extensions;
1017
+ const hasNotionColorPicker = exts.some((e) => e.name === "notionColorPicker");
1018
+ const hasBlockContextMenu = exts.some((e) => e.name === "blockContextMenu");
1152
1019
  itemMap = /* @__PURE__ */ new Map();
1020
+ dropdownMap = /* @__PURE__ */ new Map();
1153
1021
  for (const item of ed.toolbarItems) {
1154
1022
  if (item.type === "button") {
1155
1023
  itemMap.set(item.name, item);
1156
1024
  } else if (item.type === "dropdown") {
1025
+ dropdownMap.set(item.name, item);
1157
1026
  for (const sub of item.items) {
1158
1027
  itemMap.set(sub.name, sub);
1159
1028
  }
@@ -1184,7 +1053,7 @@ function useBubbleMenu(options) {
1184
1053
  let sepIdx = 0;
1185
1054
  for (const item of ctxItems) {
1186
1055
  if (lastGroup !== void 0 && item.group !== lastGroup) {
1187
- result.push({ type: "separator", name: `bsep-${sepIdx++}` });
1056
+ result.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
1188
1057
  }
1189
1058
  result.push(item);
1190
1059
  lastGroup = item.group;
@@ -1196,8 +1065,13 @@ function useBubbleMenu(options) {
1196
1065
  let sepIdx = 0;
1197
1066
  for (const name of names) {
1198
1067
  if (name === "|") {
1199
- result.push({ type: "separator", name: `sep-${sepIdx++}` });
1068
+ result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
1200
1069
  } else {
1070
+ const dropdown = dropdownMap.get(name);
1071
+ if (dropdown) {
1072
+ result.push(dropdown);
1073
+ continue;
1074
+ }
1201
1075
  const item = itemMap.get(name);
1202
1076
  if (item) result.push(item);
1203
1077
  }
@@ -1234,7 +1108,7 @@ function useBubbleMenu(options) {
1234
1108
  return schemaItems.filter((item) => {
1235
1109
  const markName = typeof item.isActive === "string" ? item.isActive : null;
1236
1110
  if (!markName) return true;
1237
- const markType = schema.marks?.[markName];
1111
+ const markType = schema.marks[markName];
1238
1112
  if (!markType) return true;
1239
1113
  return nodeType.allowsMarkType(markType);
1240
1114
  });
@@ -1254,7 +1128,8 @@ function useBubbleMenu(options) {
1254
1128
  };
1255
1129
  } else {
1256
1130
  shouldShowFn = ({ state }) => {
1257
- if (state.selection.empty || state.selection.node) return false;
1131
+ if (state.selection.empty) return false;
1132
+ if (state.selection.node) return bubbleDefaults.has(state.selection.node.type.name);
1258
1133
  if (isInsideTableCell(state.selection.$from)) return false;
1259
1134
  return state.selection.$from.parent.type.spec.marks !== "" || state.selection.$to.parent.type.spec.marks !== "";
1260
1135
  };
@@ -1287,26 +1162,77 @@ function useBubbleMenu(options) {
1287
1162
  canProxy = currentEd.can();
1288
1163
  } catch {
1289
1164
  }
1290
- for (const item of currentResolvedItems) {
1291
- if (item.type === "separator") continue;
1292
- activeMapRef.set(item.name, ToolbarController.resolveActive(currentEd, item));
1165
+ const trackButton = (btn) => {
1166
+ activeMapRef.set(btn.name, ToolbarController.resolveActive(currentEd, btn));
1293
1167
  try {
1294
- const canCmd = canProxy?.[item.command];
1295
- disabledMapRef.set(item.name, canCmd ? !(item.commandArgs?.length ? canCmd(...item.commandArgs) : canCmd()) : false);
1168
+ const canCmd = typeof btn.command === "string" ? canProxy?.[btn.command] : void 0;
1169
+ disabledMapRef.set(btn.name, canCmd ? !(btn.commandArgs?.length ? canCmd(...btn.commandArgs) : canCmd()) : false);
1296
1170
  } catch {
1297
- disabledMapRef.set(item.name, false);
1171
+ disabledMapRef.set(btn.name, false);
1172
+ }
1173
+ };
1174
+ for (const item of currentResolvedItems) {
1175
+ if (item.type === "separator") continue;
1176
+ if (item.type === "dropdown") {
1177
+ for (const sub of item.items) trackButton(sub);
1178
+ continue;
1179
+ }
1180
+ trackButton(item);
1181
+ }
1182
+ };
1183
+ const defaultItems = items ? resolveNames(items) : resolveNames(["bold", "italic", "underline"]);
1184
+ const syncTrailingState = (currentEd) => {
1185
+ const sel = currentEd.state.selection;
1186
+ const isNode = !!sel.node;
1187
+ let blockMenuDisabled = false;
1188
+ if (hasBlockContextMenu) {
1189
+ const { $from, $to } = currentEd.state.selection;
1190
+ if ($from.depth < 1 || $to.depth < 1) {
1191
+ blockMenuDisabled = true;
1192
+ } else {
1193
+ blockMenuDisabled = $from.before(1) !== $to.before(1);
1298
1194
  }
1299
1195
  }
1196
+ let textVar = null;
1197
+ let bgVar = null;
1198
+ let hasAny = false;
1199
+ if (hasNotionColorPicker) {
1200
+ const mark = currentEd.state.selection.$from.marks().find((m) => m.type.name === "textStyle");
1201
+ const attrs = mark?.attrs ?? {};
1202
+ const tToken = attrs.colorToken ?? null;
1203
+ const bToken = attrs.backgroundColorToken ?? null;
1204
+ textVar = tToken ? `var(--dm-block-text-${tToken})` : null;
1205
+ bgVar = bToken ? `var(--dm-block-bg-${bToken})` : null;
1206
+ hasAny = tToken !== null || bToken !== null;
1207
+ }
1208
+ trailing.value = {
1209
+ isNodeSelection: isNode,
1210
+ showColorPickerButton: hasNotionColorPicker,
1211
+ showBlockMenuButton: hasBlockContextMenu,
1212
+ blockMenuButtonDisabled: blockMenuDisabled,
1213
+ currentTextColorVar: textVar,
1214
+ currentBgColorVar: bgVar,
1215
+ hasAnyColor: hasAny
1216
+ };
1300
1217
  };
1301
1218
  const transactionHandler = () => {
1302
1219
  if (contexts) {
1303
1220
  updateContextItems(ed, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
1221
+ } else {
1222
+ const sel = ed.state.selection;
1223
+ if (sel.node && bubbleDefaults.has(sel.node.type.name)) {
1224
+ setItems(bubbleDefaults.get(sel.node.type.name) ?? []);
1225
+ } else {
1226
+ setItems(defaultItems);
1227
+ }
1304
1228
  }
1305
1229
  updateStates(ed);
1230
+ syncTrailingState(ed);
1306
1231
  activeVersion.value++;
1307
1232
  };
1308
1233
  ed.on("transaction", transactionHandler);
1309
1234
  updateStates(ed);
1235
+ syncTrailingState(ed);
1310
1236
  initializedEditor = ed;
1311
1237
  initializedHandler = transactionHandler;
1312
1238
  };
@@ -1364,17 +1290,39 @@ function useBubbleMenu(options) {
1364
1290
  const isItemDisabled = (item) => {
1365
1291
  return disabledMapRef.get(item.name) ?? false;
1366
1292
  };
1367
- const executeCommand = (item) => {
1293
+ const executeCommand = (item, event) => {
1368
1294
  const ed = editor.value;
1369
1295
  if (!ed) return;
1370
1296
  if (item.emitEvent) {
1371
- ed.emit(item.emitEvent, {});
1297
+ const anchor = event?.currentTarget ?? event?.target ?? null;
1298
+ ed.emit(item.emitEvent, { anchorElement: anchor });
1372
1299
  return;
1373
1300
  }
1374
1301
  ToolbarController.executeItem(ed, item);
1375
1302
  };
1376
1303
  const getCachedIcon = (name) => {
1377
- return defaultIcons[name] ?? "";
1304
+ return iconsRef?.value?.[name] ?? defaultIcons[name] ?? "";
1305
+ };
1306
+ const openColorPicker = (anchor) => {
1307
+ const ed = editor.value;
1308
+ if (!ed) return;
1309
+ ed.emit(
1310
+ "notionColorOpen",
1311
+ { anchorElement: anchor }
1312
+ );
1313
+ };
1314
+ const openBlockContextMenu = (anchor) => {
1315
+ const ed = editor.value;
1316
+ if (!ed) return;
1317
+ const $from = ed.state.selection.$from;
1318
+ if ($from.depth < 1) return;
1319
+ const depth = $from.depth > 1 && $from.node($from.depth - 1).type.name !== "doc" ? $from.depth - 1 : $from.depth;
1320
+ const blockPos = $from.before(depth);
1321
+ const editorEl = ed.view.dom.closest(".dm-editor");
1322
+ editorEl?.dispatchEvent(new CustomEvent("dm:block-context-menu-open", {
1323
+ bubbles: false,
1324
+ detail: { blockPos, anchorElement: anchor }
1325
+ }));
1378
1326
  };
1379
1327
  return {
1380
1328
  menuRef,
@@ -1383,11 +1331,15 @@ function useBubbleMenu(options) {
1383
1331
  isItemDisabled,
1384
1332
  executeCommand,
1385
1333
  activeVersion,
1386
- getCachedIcon
1334
+ getCachedIcon,
1335
+ trailing,
1336
+ openColorPicker,
1337
+ openBlockContextMenu
1387
1338
  };
1388
1339
  }
1389
1340
 
1390
1341
  // src/bubble-menu/DomternalBubbleMenu.ts
1342
+ var DROPDOWN_CARET2 = '<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>';
1391
1343
  var DomternalBubbleMenu = defineComponent({
1392
1344
  name: "DomternalBubbleMenu",
1393
1345
  props: {
@@ -1397,10 +1349,13 @@ var DomternalBubbleMenu = defineComponent({
1397
1349
  offset: { type: Number, default: 8 },
1398
1350
  updateDelay: { type: Number, default: 0 },
1399
1351
  items: { type: Array, default: void 0 },
1400
- contexts: { type: Object, default: void 0 }
1352
+ contexts: { type: Object, default: void 0 },
1353
+ icons: { type: Object, default: void 0 }
1401
1354
  },
1402
1355
  setup(props, { slots }) {
1403
1356
  const { editor: contextEditor } = useCurrentEditor();
1357
+ const editorRef = computed(() => props.editor ?? contextEditor.value);
1358
+ const iconsRef = computed(() => props.icons);
1404
1359
  const {
1405
1360
  menuRef,
1406
1361
  resolvedItems,
@@ -1408,67 +1363,300 @@ var DomternalBubbleMenu = defineComponent({
1408
1363
  isItemDisabled,
1409
1364
  executeCommand,
1410
1365
  activeVersion,
1411
- getCachedIcon
1366
+ getCachedIcon,
1367
+ trailing,
1368
+ openColorPicker,
1369
+ openBlockContextMenu
1412
1370
  } = useBubbleMenu({
1413
- editor: computed(() => props.editor ?? contextEditor.value),
1371
+ editor: editorRef,
1414
1372
  shouldShow: props.shouldShow,
1415
1373
  placement: props.placement,
1416
1374
  offset: props.offset,
1417
1375
  updateDelay: props.updateDelay,
1418
1376
  items: props.items,
1419
- contexts: props.contexts
1377
+ contexts: props.contexts,
1378
+ icons: iconsRef
1420
1379
  });
1380
+ const openDropdown = ref(null);
1381
+ const closeDropdown = () => {
1382
+ openDropdown.value = null;
1383
+ };
1384
+ const executeSubItem = (sub) => {
1385
+ closeDropdown();
1386
+ const ed = editorRef.value;
1387
+ if (!ed) return;
1388
+ ToolbarController.executeItem(ed, sub);
1389
+ requestAnimationFrame(() => {
1390
+ ed.view.focus();
1391
+ });
1392
+ };
1393
+ const colorBtnRef = ref();
1394
+ const blockMenuBtnRef = ref();
1421
1395
  return () => {
1422
1396
  void activeVersion.value;
1423
- return h("div", { ref: menuRef, class: "dm-bubble-menu", role: "toolbar", "aria-label": "Text formatting" }, [
1424
- ...resolvedItems.value.map((item) => {
1425
- if (item.type === "separator") {
1426
- return h("span", { key: item.name, class: "dm-toolbar-separator", role: "separator" });
1397
+ const t = trailing.value;
1398
+ const children = [];
1399
+ for (const item of resolvedItems.value) {
1400
+ if (item.type === "separator") {
1401
+ children.push(h("span", { key: item.name, class: "dm-toolbar-separator", role: "separator" }));
1402
+ continue;
1403
+ }
1404
+ if (item.type === "dropdown") {
1405
+ children.push(
1406
+ h(BubbleDropdown, {
1407
+ key: item.name,
1408
+ dropdown: item,
1409
+ isOpen: openDropdown.value === item.name,
1410
+ isItemActive,
1411
+ getCachedIcon,
1412
+ activeVersion: activeVersion.value,
1413
+ executeSubItem,
1414
+ onToggle: () => {
1415
+ openDropdown.value = openDropdown.value === item.name ? null : item.name;
1416
+ },
1417
+ onClose: closeDropdown
1418
+ })
1419
+ );
1420
+ continue;
1421
+ }
1422
+ const btn = item;
1423
+ const active = isItemActive(btn);
1424
+ children.push(h("button", {
1425
+ key: btn.name,
1426
+ type: "button",
1427
+ class: ["dm-toolbar-button", active && "dm-toolbar-button--active"],
1428
+ disabled: isItemDisabled(btn),
1429
+ "aria-label": btn.label,
1430
+ "aria-pressed": active,
1431
+ title: btn.label,
1432
+ innerHTML: getCachedIcon(btn.icon),
1433
+ onMousedown: (e) => {
1434
+ e.preventDefault();
1435
+ },
1436
+ onClick: (e) => {
1437
+ executeCommand(btn, e);
1427
1438
  }
1428
- const btn = item;
1429
- const active = isItemActive(btn);
1430
- return h("button", {
1431
- key: btn.name,
1439
+ }));
1440
+ }
1441
+ if (t.showColorPickerButton && !t.isNodeSelection) {
1442
+ children.push(
1443
+ h("span", { class: "dm-toolbar-separator", role: "separator" }),
1444
+ h("button", {
1445
+ ref: colorBtnRef,
1432
1446
  type: "button",
1433
- class: ["dm-toolbar-button", active && "dm-toolbar-button--active"],
1434
- disabled: isItemDisabled(btn),
1435
- "aria-label": btn.label,
1436
- "aria-pressed": active,
1437
- title: btn.label,
1438
- innerHTML: getCachedIcon(btn.icon),
1439
- onMousedown: (e) => e.preventDefault(),
1440
- onClick: () => executeCommand(btn)
1441
- });
1442
- }),
1443
- slots["default"]?.()
1444
- ]);
1445
- };
1446
- }
1447
- });
1448
- var DomternalFloatingMenu = defineComponent({
1449
- name: "DomternalFloatingMenu",
1450
- props: {
1451
- editor: { type: Object, default: void 0 },
1452
- shouldShow: { type: Function, default: void 0 },
1453
- offset: { type: Number, default: 0 }
1454
- },
1447
+ class: ["dm-toolbar-button", "dm-ncp-trigger", t.hasAnyColor && "dm-toolbar-button--active"],
1448
+ title: "Text and background color",
1449
+ "aria-label": "Text and background color",
1450
+ "aria-haspopup": "dialog",
1451
+ onMousedown: (e) => {
1452
+ e.preventDefault();
1453
+ },
1454
+ onClick: () => {
1455
+ if (colorBtnRef.value) openColorPicker(colorBtnRef.value);
1456
+ }
1457
+ }, [
1458
+ h("span", {
1459
+ class: "dm-ncp-trigger-glyph",
1460
+ style: t.currentTextColorVar ? { color: t.currentTextColorVar } : void 0
1461
+ }, "A"),
1462
+ h("span", {
1463
+ class: "dm-ncp-trigger-underline",
1464
+ style: t.currentBgColorVar ? { backgroundColor: t.currentBgColorVar } : void 0
1465
+ })
1466
+ ])
1467
+ );
1468
+ }
1469
+ if (t.showBlockMenuButton && !t.isNodeSelection) {
1470
+ children.push(
1471
+ h("span", { class: "dm-toolbar-separator", role: "separator" }),
1472
+ h("button", {
1473
+ ref: blockMenuBtnRef,
1474
+ type: "button",
1475
+ class: "dm-toolbar-button",
1476
+ disabled: t.blockMenuButtonDisabled,
1477
+ title: t.blockMenuButtonDisabled ? "Block actions (select within a single block)" : "More options",
1478
+ "aria-label": "More options",
1479
+ "aria-haspopup": "menu",
1480
+ innerHTML: getCachedIcon("dotsThree"),
1481
+ onMousedown: (e) => {
1482
+ e.preventDefault();
1483
+ },
1484
+ onClick: () => {
1485
+ if (blockMenuBtnRef.value) openBlockContextMenu(blockMenuBtnRef.value);
1486
+ }
1487
+ })
1488
+ );
1489
+ }
1490
+ const slotContent = slots["default"]?.();
1491
+ if (slotContent) children.push(...slotContent);
1492
+ return h("div", {
1493
+ ref: menuRef,
1494
+ class: "dm-bubble-menu",
1495
+ role: "toolbar",
1496
+ "aria-label": "Text formatting"
1497
+ }, children);
1498
+ };
1499
+ }
1500
+ });
1501
+ var BubbleDropdown = defineComponent({
1502
+ name: "BubbleDropdown",
1503
+ props: {
1504
+ dropdown: { type: Object, required: true },
1505
+ isOpen: { type: Boolean, required: true },
1506
+ isItemActive: { type: Function, required: true },
1507
+ getCachedIcon: { type: Function, required: true },
1508
+ activeVersion: { type: Number, required: true },
1509
+ executeSubItem: { type: Function, required: true }
1510
+ },
1511
+ emits: ["toggle", "close"],
1512
+ setup(props, { emit }) {
1513
+ const triggerRef = ref();
1514
+ const panelRef = ref();
1515
+ watch(
1516
+ () => props.isOpen,
1517
+ (open, _old, onCleanup) => {
1518
+ if (!open) return;
1519
+ const trigger = triggerRef.value;
1520
+ const panel = panelRef.value;
1521
+ if (!trigger || !panel) return;
1522
+ const cleanupFloating = positionFloatingOnce(trigger, panel, {
1523
+ placement: "bottom-start",
1524
+ offsetValue: 4
1525
+ });
1526
+ const controller = new AbortController();
1527
+ const { signal } = controller;
1528
+ document.addEventListener("mousedown", (e) => {
1529
+ const target = e.target;
1530
+ if (!target) return;
1531
+ if (panel.contains(target)) return;
1532
+ if (trigger.contains(target)) return;
1533
+ emit("close");
1534
+ }, { signal });
1535
+ document.addEventListener("keydown", (e) => {
1536
+ if (e.key === "Escape") {
1537
+ e.preventDefault();
1538
+ emit("close");
1539
+ }
1540
+ }, { signal });
1541
+ const editorEl = trigger.closest(".dm-editor");
1542
+ editorEl?.addEventListener("dm:dismiss-overlays", () => {
1543
+ emit("close");
1544
+ }, { signal });
1545
+ onCleanup(() => {
1546
+ controller.abort();
1547
+ cleanupFloating();
1548
+ });
1549
+ },
1550
+ { flush: "post" }
1551
+ );
1552
+ return () => {
1553
+ void props.activeVersion;
1554
+ const dropdown = props.dropdown;
1555
+ const dropdownActive = dropdown.items.some((sub) => props.isItemActive(sub));
1556
+ const activeChild = dropdown.dynamicIcon ? dropdown.items.find((sub) => props.isItemActive(sub)) : void 0;
1557
+ const triggerIcon = activeChild?.icon ?? dropdown.icon;
1558
+ const triggerHtml = props.getCachedIcon(triggerIcon) + DROPDOWN_CARET2;
1559
+ return h("div", {
1560
+ class: "dm-toolbar-dropdown-wrapper",
1561
+ "data-dropdown-wrapper": dropdown.name
1562
+ }, [
1563
+ h("button", {
1564
+ ref: triggerRef,
1565
+ type: "button",
1566
+ class: ["dm-toolbar-button", "dm-toolbar-dropdown-trigger", dropdownActive && "dm-toolbar-button--active"],
1567
+ "aria-expanded": props.isOpen,
1568
+ "aria-haspopup": "true",
1569
+ "aria-label": dropdown.label,
1570
+ title: dropdown.label,
1571
+ "data-dropdown": dropdown.name,
1572
+ innerHTML: triggerHtml,
1573
+ onMousedown: (e) => {
1574
+ e.preventDefault();
1575
+ },
1576
+ onClick: () => {
1577
+ emit("toggle");
1578
+ }
1579
+ }),
1580
+ props.isOpen ? h("div", {
1581
+ ref: panelRef,
1582
+ class: "dm-toolbar-dropdown-panel",
1583
+ role: "menu",
1584
+ "data-dm-editor-ui": "",
1585
+ "data-dropdown-panel": dropdown.name
1586
+ }, dropdown.items.map((sub) => {
1587
+ const subActive = props.isItemActive(sub);
1588
+ const subHtml = `${props.getCachedIcon(sub.icon)} ${sub.label}`;
1589
+ return h("button", {
1590
+ key: sub.name,
1591
+ type: "button",
1592
+ class: ["dm-toolbar-dropdown-item", subActive && "dm-toolbar-dropdown-item--active"],
1593
+ role: "menuitem",
1594
+ "aria-label": sub.label,
1595
+ innerHTML: subHtml,
1596
+ onMousedown: (e) => {
1597
+ e.preventDefault();
1598
+ },
1599
+ onClick: () => {
1600
+ props.executeSubItem(sub);
1601
+ }
1602
+ });
1603
+ })) : null
1604
+ ]);
1605
+ };
1606
+ }
1607
+ });
1608
+ var DomternalFloatingMenu = defineComponent({
1609
+ name: "DomternalFloatingMenu",
1610
+ props: {
1611
+ editor: { type: Object, default: void 0 },
1612
+ shouldShow: { type: Function, default: void 0 },
1613
+ offset: { type: Number, default: 0 },
1614
+ items: {
1615
+ type: [Array, Function],
1616
+ default: void 0
1617
+ },
1618
+ keymap: { type: Object, default: void 0 },
1619
+ icons: { type: Object, default: void 0 },
1620
+ requireExplicitTrigger: { type: Boolean, default: false }
1621
+ },
1455
1622
  setup(props, { slots }) {
1456
1623
  const { editor: contextEditor } = useCurrentEditor();
1457
1624
  const menuRef = ref();
1458
- const pluginKey = new PluginKey("vueFloatingMenu-" + Math.random().toString(36).slice(2, 8));
1625
+ const cryptoRef = globalThis.crypto;
1626
+ const pluginKey = new PluginKey(
1627
+ "vueFloatingMenu-" + (cryptoRef?.randomUUID?.().slice(0, 8) ?? Math.random().toString(36).slice(2, 8))
1628
+ );
1629
+ const controller = shallowRef(null);
1630
+ const version = ref(0);
1459
1631
  let registered = false;
1460
1632
  let stopWatch = null;
1633
+ let currentEditor = null;
1634
+ const resolveIcon = (name) => {
1635
+ if (!name) return "";
1636
+ return props.icons?.[name] ?? defaultIcons[name] ?? "";
1637
+ };
1461
1638
  const doRegister = (editor) => {
1462
1639
  if (registered || editor.isDestroyed || !menuRef.value) return;
1463
1640
  registered = true;
1641
+ currentEditor = editor;
1464
1642
  const plugin = createFloatingMenuPlugin({
1465
1643
  pluginKey,
1466
1644
  editor,
1467
1645
  element: menuRef.value,
1468
1646
  ...props.shouldShow && { shouldShow: props.shouldShow },
1469
- offset: props.offset
1647
+ offset: props.offset,
1648
+ ...props.keymap && { keymap: props.keymap },
1649
+ requireExplicitTrigger: props.requireExplicitTrigger
1470
1650
  });
1471
1651
  editor.registerPlugin(plugin);
1652
+ const hasCustomSlot = Boolean(slots["default"]);
1653
+ if (!hasCustomSlot) {
1654
+ const ctl = new FloatingMenuController(editor, () => {
1655
+ version.value++;
1656
+ }, props.items);
1657
+ ctl.subscribe();
1658
+ controller.value = ctl;
1659
+ }
1472
1660
  };
1473
1661
  onMounted(() => {
1474
1662
  const ed = props.editor ?? contextEditor.value;
@@ -1487,14 +1675,149 @@ var DomternalFloatingMenu = defineComponent({
1487
1675
  );
1488
1676
  }
1489
1677
  });
1678
+ watch(
1679
+ () => [controller.value?.focusedIndex ?? -1, version.value],
1680
+ ([focusedIndex]) => {
1681
+ if (focusedIndex < 0 || !menuRef.value) return;
1682
+ void nextTick(() => {
1683
+ const target = menuRef.value?.querySelector(
1684
+ `[data-floating-menu-index="${String(focusedIndex)}"]`
1685
+ );
1686
+ target?.focus();
1687
+ });
1688
+ }
1689
+ );
1490
1690
  onScopeDispose(() => {
1491
1691
  stopWatch?.();
1492
- const editor = props.editor ?? contextEditor.value;
1692
+ controller.value?.destroy();
1693
+ controller.value = null;
1694
+ const editor = currentEditor ?? props.editor ?? contextEditor.value;
1493
1695
  if (editor && !editor.isDestroyed) {
1494
1696
  editor.unregisterPlugin(pluginKey);
1495
1697
  }
1496
1698
  });
1497
- return () => h("div", { ref: menuRef, class: "dm-floating-menu" }, slots["default"]?.());
1699
+ const onItemClick = (item) => {
1700
+ const ctl = controller.value;
1701
+ const ed = currentEditor;
1702
+ if (!ctl || !ed) return;
1703
+ ctl.execute(item);
1704
+ requestAnimationFrame(() => {
1705
+ ed.view.focus();
1706
+ });
1707
+ };
1708
+ const onMenuKeyDown = (e) => {
1709
+ const ctl = controller.value;
1710
+ if (!ctl) return;
1711
+ const focused = ctl.focusedItem();
1712
+ switch (e.key) {
1713
+ case "ArrowDown":
1714
+ e.preventDefault();
1715
+ ctl.next();
1716
+ return;
1717
+ case "ArrowUp":
1718
+ e.preventDefault();
1719
+ ctl.prev();
1720
+ return;
1721
+ case "Home":
1722
+ e.preventDefault();
1723
+ ctl.first();
1724
+ return;
1725
+ case "End":
1726
+ e.preventDefault();
1727
+ ctl.last();
1728
+ return;
1729
+ case "Escape":
1730
+ e.preventDefault();
1731
+ e.stopPropagation();
1732
+ ctl.leaveMenu();
1733
+ currentEditor?.view.focus();
1734
+ return;
1735
+ case "Enter":
1736
+ case " ":
1737
+ if (focused) {
1738
+ e.preventDefault();
1739
+ onItemClick(focused);
1740
+ }
1741
+ return;
1742
+ default:
1743
+ return;
1744
+ }
1745
+ };
1746
+ return () => {
1747
+ if (slots["default"]) {
1748
+ return h(
1749
+ "div",
1750
+ { ref: menuRef, class: "dm-floating-menu", "data-dm-editor-ui": "" },
1751
+ slots["default"]()
1752
+ );
1753
+ }
1754
+ const ctl = controller.value;
1755
+ void version.value;
1756
+ const groups = ctl?.groups ?? [];
1757
+ const focusedIndex = ctl?.focusedIndex ?? -1;
1758
+ const flatNames = groups.flatMap((g) => g.items.map((i) => i.name));
1759
+ return h(
1760
+ "div",
1761
+ {
1762
+ ref: menuRef,
1763
+ class: "dm-floating-menu",
1764
+ role: "menu",
1765
+ "aria-label": "Insert block",
1766
+ "data-dm-editor-ui": "",
1767
+ onKeydown: onMenuKeyDown
1768
+ },
1769
+ groups.map((group, gi) => {
1770
+ const groupId = `dm-fm-g${String(gi)}`;
1771
+ return h("div", { key: group.name || `__group-${String(gi)}`, class: "dm-floating-menu-group-wrapper" }, [
1772
+ group.name ? h("div", { class: "dm-floating-menu-group-label", id: groupId }, group.name) : null,
1773
+ h(
1774
+ "div",
1775
+ {
1776
+ class: "dm-floating-menu-group",
1777
+ role: "group",
1778
+ ...group.name ? { "aria-labelledby": groupId } : {}
1779
+ },
1780
+ group.items.map((item) => {
1781
+ const flatIndex = flatNames.indexOf(item.name);
1782
+ const isFocused = flatIndex === focusedIndex;
1783
+ const disabled = ctl?.isDisabled(item) ?? false;
1784
+ const iconHtml = resolveIcon(item.icon);
1785
+ return h(
1786
+ "button",
1787
+ {
1788
+ key: item.name,
1789
+ type: "button",
1790
+ role: "menuitem",
1791
+ class: "dm-floating-menu-item",
1792
+ "data-floating-menu-item": item.name,
1793
+ "data-floating-menu-index": String(flatIndex),
1794
+ tabindex: isFocused || focusedIndex < 0 && flatIndex === 0 ? 0 : -1,
1795
+ "aria-disabled": disabled ? "true" : void 0,
1796
+ "aria-keyshortcuts": item.shortcut,
1797
+ disabled,
1798
+ onMousedown: (e) => {
1799
+ e.preventDefault();
1800
+ },
1801
+ onClick: () => {
1802
+ onItemClick(item);
1803
+ }
1804
+ },
1805
+ [
1806
+ iconHtml ? h("span", {
1807
+ class: "dm-floating-menu-item-icon",
1808
+ "aria-hidden": "true",
1809
+ innerHTML: iconHtml
1810
+ }) : null,
1811
+ h("span", { class: "dm-floating-menu-item-label" }, item.label),
1812
+ item.shortcut ? h("span", { class: "dm-floating-menu-item-shortcut", "aria-hidden": "true" }, item.shortcut) : null
1813
+ ]
1814
+ );
1815
+ })
1816
+ )
1817
+ ]);
1818
+ })
1819
+ );
1820
+ };
1498
1821
  }
1499
1822
  });
1500
1823
  var SCROLL_SETTLE_MS = 50;
@@ -1533,7 +1856,8 @@ function useEmojiPicker(editor, emojis) {
1533
1856
  const frequentlyUsed = computed(() => {
1534
1857
  if (!isOpen.value) return [];
1535
1858
  const storage = getEmojiStorage(editor.value);
1536
- const getFreq = storage?.["getFrequentlyUsed"];
1859
+ if (!storage) return [];
1860
+ const getFreq = storage["getFrequentlyUsed"];
1537
1861
  if (!getFreq) return [];
1538
1862
  const names = getFreq();
1539
1863
  if (!names.length) return [];
@@ -1565,7 +1889,9 @@ function useEmojiPicker(editor, emojis) {
1565
1889
  clickOutsideHandler = (e) => {
1566
1890
  const target = e.target;
1567
1891
  if (pickerRef.value && !pickerRef.value.contains(target) && target !== anchorEl && !anchorEl?.contains(target)) {
1568
- requestAnimationFrame(() => close());
1892
+ requestAnimationFrame(() => {
1893
+ close();
1894
+ });
1569
1895
  }
1570
1896
  };
1571
1897
  document.addEventListener("mousedown", clickOutsideHandler);
@@ -1743,7 +2069,7 @@ var DomternalEmojiPicker = defineComponent({
1743
2069
  const swatches = Array.from(grid.querySelectorAll(".dm-emoji-swatch"));
1744
2070
  if (!swatches.length) return;
1745
2071
  const current = document.activeElement;
1746
- let idx = swatches.indexOf(current);
2072
+ const idx = swatches.indexOf(current);
1747
2073
  if (idx === -1) {
1748
2074
  if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
1749
2075
  event.preventDefault();
@@ -1788,8 +2114,12 @@ var DomternalEmojiPicker = defineComponent({
1788
2114
  tabindex: -1,
1789
2115
  title: formatName(item.name),
1790
2116
  "aria-label": formatName(item.name),
1791
- onMousedown: (e) => e.preventDefault(),
1792
- onClick: () => selectEmoji(item)
2117
+ onMousedown: (e) => {
2118
+ e.preventDefault();
2119
+ },
2120
+ onClick: () => {
2121
+ selectEmoji(item);
2122
+ }
1793
2123
  }, item.emoji);
1794
2124
  }
1795
2125
  return () => {
@@ -1824,8 +2154,12 @@ var DomternalEmojiPicker = defineComponent({
1824
2154
  "aria-selected": activeCategory.value === cat,
1825
2155
  title: cat,
1826
2156
  "aria-label": cat,
1827
- onMousedown: (e) => e.preventDefault(),
1828
- onClick: () => scrollToCategory(cat)
2157
+ onMousedown: (e) => {
2158
+ e.preventDefault();
2159
+ },
2160
+ onClick: () => {
2161
+ scrollToCategory(cat);
2162
+ }
1829
2163
  }, categoryIcon(cat))
1830
2164
  )
1831
2165
  ),
@@ -1855,6 +2189,552 @@ var DomternalEmojiPicker = defineComponent({
1855
2189
  };
1856
2190
  }
1857
2191
  });
2192
+
2193
+ // src/Domternal.ts
2194
+ var Domternal = defineComponent({
2195
+ name: "Domternal",
2196
+ props: {
2197
+ extensions: { type: Array, default: void 0 },
2198
+ content: { type: [String, Object], default: "" },
2199
+ editable: { type: Boolean, default: true },
2200
+ autofocus: { type: [Boolean, String, Number], default: false },
2201
+ outputFormat: { type: String, default: "html" },
2202
+ immediatelyRender: { type: Boolean, default: false },
2203
+ onCreate: { type: Function, default: void 0 },
2204
+ onUpdate: { type: Function, default: void 0 },
2205
+ onSelectionChange: { type: Function, default: void 0 },
2206
+ onFocus: { type: Function, default: void 0 },
2207
+ onBlur: { type: Function, default: void 0 },
2208
+ onDestroy: { type: Function, default: void 0 }
2209
+ },
2210
+ setup(props, { slots }) {
2211
+ const { editor } = useEditor({
2212
+ ...props.extensions && { extensions: props.extensions },
2213
+ content: props.content,
2214
+ editable: props.editable,
2215
+ autofocus: props.autofocus,
2216
+ outputFormat: props.outputFormat,
2217
+ immediatelyRender: props.immediatelyRender,
2218
+ ...props.onCreate && { onCreate: props.onCreate },
2219
+ ...props.onUpdate && { onUpdate: props.onUpdate },
2220
+ ...props.onSelectionChange && { onSelectionChange: props.onSelectionChange },
2221
+ ...props.onFocus && { onFocus: props.onFocus },
2222
+ ...props.onBlur && { onBlur: props.onBlur },
2223
+ ...props.onDestroy && { onDestroy: props.onDestroy }
2224
+ });
2225
+ provideEditor(editor);
2226
+ watch(
2227
+ () => props.editable,
2228
+ (newEditable) => {
2229
+ const ed = editor.value;
2230
+ if (ed && !ed.isDestroyed) ed.setEditable(newEditable);
2231
+ }
2232
+ );
2233
+ return () => slots["default"]?.();
2234
+ }
2235
+ });
2236
+ var DomternalContent = defineComponent({
2237
+ name: "DomternalContent",
2238
+ props: {
2239
+ class: { type: String, default: void 0 }
2240
+ },
2241
+ setup(props) {
2242
+ const { editor } = useCurrentEditor();
2243
+ const containerRef = ref();
2244
+ watchEffect(() => {
2245
+ const container = containerRef.value;
2246
+ const ed = editor.value;
2247
+ if (!container || !ed || ed.isDestroyed) return;
2248
+ const editorDom = ed.view.dom;
2249
+ if (editorDom.parentElement !== container) {
2250
+ container.appendChild(editorDom);
2251
+ }
2252
+ });
2253
+ return () => {
2254
+ const classes = props.class ? `dm-editor ${props.class}` : "dm-editor";
2255
+ return h("div", { class: classes, "data-dm-editor-ui": "" }, [h("div", { ref: containerRef })]);
2256
+ };
2257
+ }
2258
+ });
2259
+ var DomternalLoading = defineComponent({
2260
+ name: "DomternalLoading",
2261
+ setup(_props, { slots }) {
2262
+ const { editor } = useCurrentEditor();
2263
+ return () => editor.value ? null : slots["default"]?.();
2264
+ }
2265
+ });
2266
+ Domternal.Content = DomternalContent;
2267
+ Domternal.Loading = DomternalLoading;
2268
+ Domternal.Toolbar = DomternalToolbar;
2269
+ Domternal.BubbleMenu = DomternalBubbleMenu;
2270
+ Domternal.FloatingMenu = DomternalFloatingMenu;
2271
+ Domternal.EmojiPicker = DomternalEmojiPicker;
2272
+ var DomternalEditor = defineComponent({
2273
+ name: "DomternalEditor",
2274
+ props: {
2275
+ extensions: { type: Array, default: void 0 },
2276
+ content: { type: [String, Object], default: void 0 },
2277
+ editable: { type: Boolean, default: true },
2278
+ autofocus: { type: [Boolean, String, Number], default: false },
2279
+ immediatelyRender: { type: Boolean, default: false },
2280
+ outputFormat: { type: String, default: "html" },
2281
+ modelValue: { type: [String, Object], default: void 0 },
2282
+ class: { type: String, default: void 0 },
2283
+ onCreate: { type: Function, default: void 0 },
2284
+ onUpdate: { type: Function, default: void 0 },
2285
+ onSelectionChange: { type: Function, default: void 0 },
2286
+ onFocus: { type: Function, default: void 0 },
2287
+ onBlur: { type: Function, default: void 0 },
2288
+ onDestroy: { type: Function, default: void 0 }
2289
+ },
2290
+ emits: {
2291
+ "update:modelValue": (_value) => true
2292
+ },
2293
+ setup(props, { slots, emit, expose }) {
2294
+ const { editor, editorRef } = useEditor({
2295
+ ...props.extensions && { extensions: props.extensions },
2296
+ content: props.modelValue ?? props.content ?? "",
2297
+ editable: props.editable,
2298
+ autofocus: props.autofocus,
2299
+ immediatelyRender: props.immediatelyRender,
2300
+ outputFormat: props.outputFormat,
2301
+ ...props.onCreate && { onCreate: props.onCreate },
2302
+ ...props.onUpdate && { onUpdate: props.onUpdate },
2303
+ ...props.onSelectionChange && { onSelectionChange: props.onSelectionChange },
2304
+ ...props.onFocus && { onFocus: props.onFocus },
2305
+ ...props.onBlur && { onBlur: props.onBlur },
2306
+ ...props.onDestroy && { onDestroy: props.onDestroy }
2307
+ });
2308
+ const state = useEditorState(editor);
2309
+ expose({
2310
+ editor,
2311
+ htmlContent: state.htmlContent,
2312
+ jsonContent: state.jsonContent,
2313
+ isEmpty: state.isEmpty,
2314
+ isFocused: state.isFocused
2315
+ });
2316
+ provideEditor(editor);
2317
+ watch(
2318
+ () => props.editable,
2319
+ (newEditable) => {
2320
+ const ed = editor.value;
2321
+ if (ed && !ed.isDestroyed) ed.setEditable(newEditable);
2322
+ }
2323
+ );
2324
+ const prevModelValue = ref(props.modelValue);
2325
+ watch(
2326
+ () => props.modelValue,
2327
+ (newValue) => {
2328
+ if (newValue === void 0) return;
2329
+ const ed = editor.value;
2330
+ if (!ed || ed.isDestroyed) return;
2331
+ if (newValue === prevModelValue.value) return;
2332
+ prevModelValue.value = newValue;
2333
+ if (props.outputFormat === "html") {
2334
+ if (newValue !== ed.getHTML()) {
2335
+ ed.setContent(newValue, false);
2336
+ }
2337
+ } else {
2338
+ if (JSON.stringify(newValue) !== JSON.stringify(ed.getJSON())) {
2339
+ ed.setContent(newValue, false);
2340
+ }
2341
+ }
2342
+ },
2343
+ { flush: "post" }
2344
+ );
2345
+ watch(editor, (ed, _oldEd, onCleanup) => {
2346
+ if (!ed || ed.isDestroyed) return;
2347
+ const handler = () => {
2348
+ const val = props.outputFormat === "html" ? ed.getHTML() : ed.getJSON();
2349
+ prevModelValue.value = val;
2350
+ emit("update:modelValue", val);
2351
+ };
2352
+ ed.on("update", handler);
2353
+ onCleanup(() => {
2354
+ ed.off("update", handler);
2355
+ });
2356
+ }, { immediate: true });
2357
+ return () => {
2358
+ const classes = props.class ? `dm-editor ${props.class}` : "dm-editor";
2359
+ return [
2360
+ h("div", { class: classes, "data-dm-editor-ui": "" }, [h("div", { ref: editorRef })]),
2361
+ slots["default"]?.()
2362
+ ];
2363
+ };
2364
+ }
2365
+ });
2366
+ var EditorContent = defineComponent({
2367
+ name: "EditorContent",
2368
+ props: {
2369
+ editor: {
2370
+ type: Object,
2371
+ default: null
2372
+ },
2373
+ class: {
2374
+ type: String,
2375
+ default: void 0
2376
+ }
2377
+ },
2378
+ setup(props) {
2379
+ const containerRef = ref();
2380
+ watchEffect(() => {
2381
+ const container = containerRef.value;
2382
+ const editor = props.editor;
2383
+ if (!container || !editor || editor.isDestroyed) return;
2384
+ const editorDom = editor.view.dom;
2385
+ if (editorDom.parentElement !== container) {
2386
+ container.appendChild(editorDom);
2387
+ }
2388
+ });
2389
+ return () => h("div", {
2390
+ ref: containerRef,
2391
+ class: props.class,
2392
+ "data-dm-editor-ui": ""
2393
+ });
2394
+ }
2395
+ });
2396
+ var TOKEN_LABELS = {
2397
+ gray: "Gray",
2398
+ brown: "Brown",
2399
+ orange: "Orange",
2400
+ yellow: "Yellow",
2401
+ green: "Green",
2402
+ blue: "Blue",
2403
+ purple: "Purple",
2404
+ pink: "Pink",
2405
+ red: "Red"
2406
+ };
2407
+ function useNotionColorPicker(options) {
2408
+ const { editor } = options;
2409
+ const isOpen = ref(false);
2410
+ const anchorEl = shallowRef(null);
2411
+ const hostEl = shallowRef(null);
2412
+ const panelRef = ref();
2413
+ const currentTextToken = ref(null);
2414
+ const currentBgToken = ref(null);
2415
+ const palette = shallowRef([]);
2416
+ const setStorageOpen2 = (open) => {
2417
+ const ed = editor.value;
2418
+ if (!ed) return;
2419
+ const slot = ed.storage["notionColorPicker"];
2420
+ if (slot && typeof slot === "object") {
2421
+ slot.isOpen = open;
2422
+ }
2423
+ };
2424
+ const syncFromSelection = () => {
2425
+ const ed = editor.value;
2426
+ if (!ed) return;
2427
+ const { selection } = ed.state;
2428
+ let mark = null;
2429
+ if (selection.empty) {
2430
+ mark = selection.$from.marks().find((m) => m.type.name === "textStyle") ?? null;
2431
+ } else {
2432
+ ed.state.doc.nodesBetween(selection.from, selection.to, (node) => {
2433
+ if (mark) return false;
2434
+ if (node.isText) {
2435
+ const found = node.marks.find((m) => m.type.name === "textStyle");
2436
+ if (found) mark = found;
2437
+ }
2438
+ return true;
2439
+ });
2440
+ }
2441
+ const attrs = mark?.attrs ?? {};
2442
+ currentTextToken.value = attrs.colorToken ?? null;
2443
+ currentBgToken.value = attrs.backgroundColorToken ?? null;
2444
+ };
2445
+ const close = (opts = {}) => {
2446
+ if (!isOpen.value) return;
2447
+ isOpen.value = false;
2448
+ setStorageOpen2(false);
2449
+ if (opts.refocus) {
2450
+ editor.value?.view.focus();
2451
+ }
2452
+ anchorEl.value = null;
2453
+ };
2454
+ watch(
2455
+ editor,
2456
+ (ed, _oldEd, onCleanup) => {
2457
+ if (!ed || ed.isDestroyed) return;
2458
+ hostEl.value = ed.view.dom.closest(".dm-editor");
2459
+ const ext = ed.extensionManager.extensions.find((e) => e.name === "notionColorPicker");
2460
+ const extOptions = ext?.options ?? null;
2461
+ palette.value = extOptions?.palette ? [...extOptions.palette] : [];
2462
+ const onOpen = (...args) => {
2463
+ const detail = args[0];
2464
+ const incomingAnchor = detail?.anchorElement;
2465
+ if (!incomingAnchor) return;
2466
+ if (isOpen.value && anchorEl.value === incomingAnchor) {
2467
+ close({ refocus: true });
2468
+ return;
2469
+ }
2470
+ anchorEl.value = incomingAnchor;
2471
+ syncFromSelection();
2472
+ isOpen.value = true;
2473
+ setStorageOpen2(true);
2474
+ };
2475
+ const onSelectionUpdate = () => {
2476
+ if (!isOpen.value) return;
2477
+ if (!anchorEl.value?.isConnected) {
2478
+ close();
2479
+ return;
2480
+ }
2481
+ if (ed.state.selection.empty) {
2482
+ close();
2483
+ } else {
2484
+ syncFromSelection();
2485
+ }
2486
+ };
2487
+ ed.on("notionColorOpen", onOpen);
2488
+ ed.on("selectionUpdate", onSelectionUpdate);
2489
+ onCleanup(() => {
2490
+ ed.off("notionColorOpen", onOpen);
2491
+ ed.off("selectionUpdate", onSelectionUpdate);
2492
+ if (isOpen.value) setStorageOpen2(false);
2493
+ });
2494
+ },
2495
+ { immediate: true }
2496
+ );
2497
+ watch(isOpen, (open, _old, onCleanup) => {
2498
+ if (!open) return;
2499
+ const controller = new AbortController();
2500
+ const { signal } = controller;
2501
+ document.addEventListener("mousedown", (e) => {
2502
+ const target = e.target;
2503
+ if (!target) return;
2504
+ if (panelRef.value?.contains(target)) return;
2505
+ if (anchorEl.value?.contains(target)) return;
2506
+ close({ refocus: false });
2507
+ }, { signal });
2508
+ document.addEventListener("keydown", (e) => {
2509
+ if (e.key === "Escape" && isOpen.value) {
2510
+ e.preventDefault();
2511
+ close({ refocus: true });
2512
+ }
2513
+ }, { signal });
2514
+ onCleanup(() => {
2515
+ controller.abort();
2516
+ });
2517
+ });
2518
+ const applyText = (token) => {
2519
+ const ed = editor.value;
2520
+ if (!ed) return;
2521
+ ed.commands.setTextColorToken(token);
2522
+ syncFromSelection();
2523
+ };
2524
+ const applyBg = (token) => {
2525
+ const ed = editor.value;
2526
+ if (!ed) return;
2527
+ ed.commands.setBackgroundColorToken(token);
2528
+ syncFromSelection();
2529
+ };
2530
+ const tokenLabel = (token) => {
2531
+ return TOKEN_LABELS[token] ?? token.charAt(0).toUpperCase() + token.slice(1);
2532
+ };
2533
+ const onPanelKeydown = (event) => {
2534
+ const cols = 5;
2535
+ const root = panelRef.value;
2536
+ if (!root) return;
2537
+ const swatches = Array.from(
2538
+ root.querySelectorAll(".dm-ncp-swatch")
2539
+ );
2540
+ if (!swatches.length) return;
2541
+ const active = document.activeElement;
2542
+ const idx = active ? swatches.indexOf(active) : -1;
2543
+ if (idx === -1) return;
2544
+ let next = idx;
2545
+ switch (event.key) {
2546
+ case "ArrowRight":
2547
+ event.preventDefault();
2548
+ next = Math.min(idx + 1, swatches.length - 1);
2549
+ break;
2550
+ case "ArrowLeft":
2551
+ event.preventDefault();
2552
+ next = Math.max(idx - 1, 0);
2553
+ break;
2554
+ case "ArrowDown":
2555
+ event.preventDefault();
2556
+ next = Math.min(idx + cols, swatches.length - 1);
2557
+ break;
2558
+ case "ArrowUp":
2559
+ event.preventDefault();
2560
+ next = Math.max(idx - cols, 0);
2561
+ break;
2562
+ case "Home":
2563
+ event.preventDefault();
2564
+ next = 0;
2565
+ break;
2566
+ case "End":
2567
+ event.preventDefault();
2568
+ next = swatches.length - 1;
2569
+ break;
2570
+ default:
2571
+ return;
2572
+ }
2573
+ swatches[next]?.focus();
2574
+ };
2575
+ return {
2576
+ isOpen,
2577
+ hostEl,
2578
+ anchorEl,
2579
+ panelRef,
2580
+ currentTextToken,
2581
+ currentBgToken,
2582
+ palette,
2583
+ applyText,
2584
+ applyBg,
2585
+ close,
2586
+ tokenLabel,
2587
+ onPanelKeydown
2588
+ };
2589
+ }
2590
+
2591
+ // src/notion-color-picker/DomternalNotionColorPicker.ts
2592
+ var DomternalNotionColorPicker = defineComponent({
2593
+ name: "DomternalNotionColorPicker",
2594
+ props: {
2595
+ editor: { type: Object, default: void 0 }
2596
+ },
2597
+ setup(props, { slots }) {
2598
+ const { editor: contextEditor } = useCurrentEditor();
2599
+ const editorRef = computed(() => props.editor ?? contextEditor.value);
2600
+ const api = useNotionColorPicker({ editor: editorRef });
2601
+ const {
2602
+ isOpen,
2603
+ hostEl,
2604
+ anchorEl,
2605
+ panelRef,
2606
+ currentTextToken,
2607
+ currentBgToken,
2608
+ palette,
2609
+ applyText,
2610
+ applyBg,
2611
+ tokenLabel,
2612
+ onPanelKeydown
2613
+ } = api;
2614
+ watch(
2615
+ [isOpen, anchorEl],
2616
+ ([open, anchor], _old, onCleanup) => {
2617
+ if (!open || !anchor || !panelRef.value) return;
2618
+ const panel = panelRef.value;
2619
+ const cleanupFloating = positionFloating(anchor, panel, {
2620
+ placement: "bottom-start",
2621
+ offsetValue: 4
2622
+ });
2623
+ let id2 = 0;
2624
+ const id1 = requestAnimationFrame(() => {
2625
+ id2 = requestAnimationFrame(() => {
2626
+ if (!panel.isConnected) return;
2627
+ const active = panel.querySelector(".dm-ncp-swatch.dm-ncp-active");
2628
+ const fallback = panel.querySelector('.dm-ncp-swatch--text[data-color="null"]');
2629
+ (active ?? fallback)?.focus({ preventScroll: true });
2630
+ });
2631
+ });
2632
+ onCleanup(() => {
2633
+ cancelAnimationFrame(id1);
2634
+ if (id2) cancelAnimationFrame(id2);
2635
+ cleanupFloating();
2636
+ });
2637
+ },
2638
+ { flush: "post" }
2639
+ );
2640
+ const renderDefaultPanel = () => {
2641
+ const tToken = currentTextToken.value;
2642
+ const bToken = currentBgToken.value;
2643
+ const pal = palette.value;
2644
+ return [
2645
+ h("div", { class: "dm-ncp-section" }, [
2646
+ h("div", { class: "dm-ncp-label" }, "Text color"),
2647
+ h("div", { class: "dm-ncp-grid" }, [
2648
+ h("button", {
2649
+ type: "button",
2650
+ class: ["dm-ncp-swatch", "dm-ncp-swatch--text", tToken === null && "dm-ncp-active"],
2651
+ "aria-pressed": tToken === null,
2652
+ "data-color": "null",
2653
+ title: "Default text color",
2654
+ "aria-label": "Default text color",
2655
+ onMousedown: (e) => {
2656
+ e.preventDefault();
2657
+ },
2658
+ onClick: () => {
2659
+ applyText(null);
2660
+ }
2661
+ }),
2662
+ ...pal.map((t) => h("button", {
2663
+ key: t,
2664
+ type: "button",
2665
+ class: ["dm-ncp-swatch", "dm-ncp-swatch--text", tToken === t && "dm-ncp-active"],
2666
+ "aria-pressed": tToken === t,
2667
+ "data-color": t,
2668
+ title: tokenLabel(t),
2669
+ "aria-label": `${tokenLabel(t)} text`,
2670
+ onMousedown: (e) => {
2671
+ e.preventDefault();
2672
+ },
2673
+ onClick: () => {
2674
+ applyText(t);
2675
+ }
2676
+ }))
2677
+ ])
2678
+ ]),
2679
+ h("div", { class: "dm-ncp-section" }, [
2680
+ h("div", { class: "dm-ncp-label" }, "Background color"),
2681
+ h("div", { class: "dm-ncp-grid" }, [
2682
+ h("button", {
2683
+ type: "button",
2684
+ class: ["dm-ncp-swatch", "dm-ncp-swatch--bg", bToken === null && "dm-ncp-active"],
2685
+ "aria-pressed": bToken === null,
2686
+ "data-color": "null",
2687
+ title: "Default background",
2688
+ "aria-label": "Default background",
2689
+ onMousedown: (e) => {
2690
+ e.preventDefault();
2691
+ },
2692
+ onClick: () => {
2693
+ applyBg(null);
2694
+ }
2695
+ }),
2696
+ ...pal.map((t) => h("button", {
2697
+ key: t,
2698
+ type: "button",
2699
+ class: ["dm-ncp-swatch", "dm-ncp-swatch--bg", bToken === t && "dm-ncp-active"],
2700
+ "aria-pressed": bToken === t,
2701
+ "data-color": t,
2702
+ title: `${tokenLabel(t)} background`,
2703
+ "aria-label": `${tokenLabel(t)} background`,
2704
+ onMousedown: (e) => {
2705
+ e.preventDefault();
2706
+ },
2707
+ onClick: () => {
2708
+ applyBg(t);
2709
+ }
2710
+ }))
2711
+ ])
2712
+ ])
2713
+ ];
2714
+ };
2715
+ return () => {
2716
+ if (!isOpen.value || !hostEl.value) return null;
2717
+ const slot = slots["default"];
2718
+ const slotChildren = slot ? slot({ api }) : void 0;
2719
+ const panelChildren = slotChildren ?? renderDefaultPanel();
2720
+ return h(Teleport, { to: hostEl.value }, [
2721
+ h("div", {
2722
+ ref: panelRef,
2723
+ class: "dm-notion-color-picker",
2724
+ "data-show": "",
2725
+ "data-dm-editor-ui": "",
2726
+ role: "dialog",
2727
+ "aria-label": "Text and background color",
2728
+ // Intentional non-modal: the picker doesn't trap focus (outside-click
2729
+ // closes, editor stays interactive). `role="dialog"` defaults to
2730
+ // modal=true for screen readers, so we explicitly opt out.
2731
+ "aria-modal": "false",
2732
+ onKeydown: onPanelKeydown
2733
+ }, panelChildren)
2734
+ ]);
2735
+ };
2736
+ }
2737
+ });
1858
2738
  var NODE_VIEW_ON_DRAG_START = /* @__PURE__ */ Symbol("domternal-node-view-drag");
1859
2739
  var NODE_VIEW_CONTENT_REF = /* @__PURE__ */ Symbol("domternal-node-view-content");
1860
2740
  function useVueNodeView() {
@@ -1865,7 +2745,7 @@ function useVueNodeView() {
1865
2745
 
1866
2746
  // src/node-views/VueNodeViewRenderer.ts
1867
2747
  function VueNodeViewRenderer(component, options = {}) {
1868
- const normalizedComponent = typeof component === "function" && "__vccOpts" in component ? component["__vccOpts"] ?? component : component;
2748
+ const normalizedComponent = typeof component === "function" ? component["__vccOpts"] ?? component : component;
1869
2749
  markRaw(normalizedComponent);
1870
2750
  const constructor = (node, _view, getPos, decorations) => {
1871
2751
  const ctx = constructor.__domternalContext;
@@ -1878,8 +2758,8 @@ function VueNodeViewRenderer(component, options = {}) {
1878
2758
  appContextStore.set(editor, appContext);
1879
2759
  }
1880
2760
  }
1881
- if (!appContext) {
1882
- if (typeof globalThis !== "undefined" && globalThis.__DEV__ !== false) {
2761
+ if (!appContext || !editor) {
2762
+ if (globalThis.__DEV__ !== false) {
1883
2763
  console.warn(
1884
2764
  "[VueNodeViewRenderer] appContext not found for editor. Custom Vue node views require provideEditor(editor) to be called, either manually after useEditor() or automatically via <Domternal> root."
1885
2765
  );
@@ -2036,11 +2916,7 @@ var NodeViewContent = defineComponent({
2036
2916
  };
2037
2917
  }
2038
2918
  });
2039
- Domternal.Toolbar = DomternalToolbar;
2040
- Domternal.BubbleMenu = DomternalBubbleMenu;
2041
- Domternal.FloatingMenu = DomternalFloatingMenu;
2042
- Domternal.EmojiPicker = DomternalEmojiPicker;
2043
2919
 
2044
- export { DEFAULT_EXTENSIONS, Domternal, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalToolbar, EDITOR_KEY, EditorContent, NodeViewContent, NodeViewWrapper, VueNodeViewRenderer, provideEditor, useCurrentEditor, useEditor, useEditorState, useVueNodeView };
2920
+ export { DEFAULT_EXTENSIONS, Domternal, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalNotionColorPicker, DomternalToolbar, EDITOR_KEY, EditorContent, NodeViewContent, NodeViewWrapper, VueNodeViewRenderer, provideEditor, useCurrentEditor, useEditor, useEditorState, useNotionColorPicker, useVueNodeView };
2045
2921
  //# sourceMappingURL=index.js.map
2046
2922
  //# sourceMappingURL=index.js.map