@domternal/react 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/dist/index.d.ts +121 -30
- package/dist/index.js +961 -77
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/dist/Domternal.d.ts +0 -79
- package/dist/Domternal.d.ts.map +0 -1
- package/dist/DomternalEditor.d.ts +0 -48
- package/dist/DomternalEditor.d.ts.map +0 -1
- package/dist/DomternalFloatingMenu.d.ts +0 -13
- package/dist/DomternalFloatingMenu.d.ts.map +0 -1
- package/dist/EditorContent.d.ts +0 -22
- package/dist/EditorContent.d.ts.map +0 -1
- package/dist/EditorContext.d.ts +0 -38
- package/dist/EditorContext.d.ts.map +0 -1
- package/dist/bubble-menu/DomternalBubbleMenu.d.ts +0 -21
- package/dist/bubble-menu/DomternalBubbleMenu.d.ts.map +0 -1
- package/dist/bubble-menu/useBubbleMenu.d.ts +0 -26
- package/dist/bubble-menu/useBubbleMenu.d.ts.map +0 -1
- package/dist/emoji-picker/DomternalEmojiPicker.d.ts +0 -10
- package/dist/emoji-picker/DomternalEmojiPicker.d.ts.map +0 -1
- package/dist/emoji-picker/useEmojiPicker.d.ts +0 -22
- package/dist/emoji-picker/useEmojiPicker.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/node-views/NodeViewContent.d.ts +0 -11
- package/dist/node-views/NodeViewContent.d.ts.map +0 -1
- package/dist/node-views/NodeViewWrapper.d.ts +0 -11
- package/dist/node-views/NodeViewWrapper.d.ts.map +0 -1
- package/dist/node-views/ReactNodeViewContext.d.ts +0 -12
- package/dist/node-views/ReactNodeViewContext.d.ts.map +0 -1
- package/dist/node-views/ReactNodeViewRenderer.d.ts +0 -101
- package/dist/node-views/ReactNodeViewRenderer.d.ts.map +0 -1
- package/dist/toolbar/DomternalToolbar.d.ts +0 -11
- package/dist/toolbar/DomternalToolbar.d.ts.map +0 -1
- package/dist/toolbar/ToolbarButton.d.ts +0 -14
- package/dist/toolbar/ToolbarButton.d.ts.map +0 -1
- package/dist/toolbar/ToolbarDropdown.d.ts +0 -16
- package/dist/toolbar/ToolbarDropdown.d.ts.map +0 -1
- package/dist/toolbar/ToolbarDropdownPanel.d.ts +0 -9
- package/dist/toolbar/ToolbarDropdownPanel.d.ts.map +0 -1
- package/dist/toolbar/useComputedStyle.d.ts +0 -12
- package/dist/toolbar/useComputedStyle.d.ts.map +0 -1
- package/dist/toolbar/useKeyboardNav.d.ts +0 -6
- package/dist/toolbar/useKeyboardNav.d.ts.map +0 -1
- package/dist/toolbar/useToolbarController.d.ts +0 -19
- package/dist/toolbar/useToolbarController.d.ts.map +0 -1
- package/dist/toolbar/useToolbarIcons.d.ts +0 -11
- package/dist/toolbar/useToolbarIcons.d.ts.map +0 -1
- package/dist/toolbar/useTooltip.d.ts +0 -5
- package/dist/toolbar/useTooltip.d.ts.map +0 -1
- package/dist/useEditor.d.ts +0 -79
- package/dist/useEditor.d.ts.map +0 -1
- package/dist/useEditorState.d.ts +0 -27
- package/dist/useEditorState.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
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,
|
|
1
|
+
import { createContext, forwardRef, useImperativeHandle, useRef, useEffect, useState, useMemo, useCallback, useSyncExternalStore, useContext, Fragment, useLayoutEffect, createElement } from 'react';
|
|
2
|
+
import { Editor, Document, Paragraph, Text, BaseKeymap, History, positionFloatingOnce, PluginKey, FloatingMenuController, defaultIcons, positionFloating, ToolbarController, defaultBubbleContexts, createBubbleMenuPlugin } from '@domternal/core';
|
|
3
3
|
export { Editor, generateHTML, generateJSON, generateText } from '@domternal/core';
|
|
4
4
|
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
5
|
+
import { createFloatingMenuPlugin } from '@domternal/extension-block-menu';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
5
7
|
import { createRoot } from 'react-dom/client';
|
|
6
8
|
|
|
7
9
|
// src/useEditor.ts
|
|
@@ -95,7 +97,10 @@ function useEditor(options = {}, deps) {
|
|
|
95
97
|
useEffect(() => {
|
|
96
98
|
if (!deps || !instanceRef.current || instanceRef.current.isDestroyed) return;
|
|
97
99
|
if (depsRef.current === deps) return;
|
|
98
|
-
|
|
100
|
+
const prevDeps = depsRef.current;
|
|
101
|
+
if (prevDeps?.length === deps.length && deps.every((d, i) => d === prevDeps[i])) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
99
104
|
const element = instanceRef.current.view.dom.parentElement ?? document.createElement("div");
|
|
100
105
|
destroyCurrentEditor();
|
|
101
106
|
const initialContent = pendingContentRef.current ?? "";
|
|
@@ -119,7 +124,13 @@ function useEditor(options = {}, deps) {
|
|
|
119
124
|
return { editor, editorRef };
|
|
120
125
|
}
|
|
121
126
|
function useEditorState(editor, selector) {
|
|
122
|
-
|
|
127
|
+
const isSelectorMode = typeof selector === "function";
|
|
128
|
+
const modeRef = useRef(null);
|
|
129
|
+
modeRef.current ??= isSelectorMode;
|
|
130
|
+
if (modeRef.current !== isSelectorMode) {
|
|
131
|
+
throw new Error("useEditorState selector mode must remain stable for a component instance.");
|
|
132
|
+
}
|
|
133
|
+
if (typeof selector === "function") {
|
|
123
134
|
return useEditorStateSelector(editor, selector);
|
|
124
135
|
}
|
|
125
136
|
return useEditorStateFull(editor);
|
|
@@ -464,7 +475,7 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
|
|
|
464
475
|
const idx = controllerRef.current?.focusedIndex ?? 0;
|
|
465
476
|
const buttons = toolbarRef.current?.querySelectorAll(".dm-toolbar-button");
|
|
466
477
|
buttons?.[idx]?.focus();
|
|
467
|
-
}, []);
|
|
478
|
+
}, [controllerRef, toolbarRef]);
|
|
468
479
|
const focusDropdownItem = useCallback((direction, first) => {
|
|
469
480
|
const panel = toolbarRef.current?.querySelector(".dm-toolbar-dropdown-panel");
|
|
470
481
|
if (!panel) return;
|
|
@@ -478,7 +489,7 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
|
|
|
478
489
|
const idx = items.indexOf(current);
|
|
479
490
|
const next = idx === -1 ? direction > 0 ? 0 : items.length - 1 : (idx + direction + items.length) % items.length;
|
|
480
491
|
items[next]?.focus();
|
|
481
|
-
}, []);
|
|
492
|
+
}, [toolbarRef]);
|
|
482
493
|
const onKeyDown = useCallback((event) => {
|
|
483
494
|
const controller = controllerRef.current;
|
|
484
495
|
if (!controller) return;
|
|
@@ -501,7 +512,9 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
|
|
|
501
512
|
const btn = document.activeElement;
|
|
502
513
|
if (btn?.getAttribute("aria-haspopup") && btn.closest(".dm-toolbar")) {
|
|
503
514
|
btn.click();
|
|
504
|
-
requestAnimationFrame(() =>
|
|
515
|
+
requestAnimationFrame(() => {
|
|
516
|
+
focusDropdownItem(0, true);
|
|
517
|
+
});
|
|
505
518
|
}
|
|
506
519
|
}
|
|
507
520
|
break;
|
|
@@ -531,7 +544,7 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
|
|
|
531
544
|
}
|
|
532
545
|
break;
|
|
533
546
|
}
|
|
534
|
-
}, [closeDropdown, focusCurrentButton, focusDropdownItem]);
|
|
547
|
+
}, [closeDropdown, focusCurrentButton, focusDropdownItem, controllerRef]);
|
|
535
548
|
return { onKeyDown, focusCurrentButton };
|
|
536
549
|
}
|
|
537
550
|
|
|
@@ -581,9 +594,15 @@ function ToolbarButton({
|
|
|
581
594
|
tabIndex,
|
|
582
595
|
disabled: isDisabled,
|
|
583
596
|
dangerouslySetInnerHTML: { __html: iconHtml },
|
|
584
|
-
onMouseDown: (e) =>
|
|
585
|
-
|
|
586
|
-
|
|
597
|
+
onMouseDown: (e) => {
|
|
598
|
+
e.preventDefault();
|
|
599
|
+
},
|
|
600
|
+
onClick: (e) => {
|
|
601
|
+
onClick(item, e);
|
|
602
|
+
},
|
|
603
|
+
onFocus: () => {
|
|
604
|
+
onFocus(item.name);
|
|
605
|
+
}
|
|
587
606
|
}
|
|
588
607
|
);
|
|
589
608
|
}
|
|
@@ -611,8 +630,12 @@ function ToolbarDropdownPanel({
|
|
|
611
630
|
"aria-label": sub.label,
|
|
612
631
|
title: sub.label,
|
|
613
632
|
style: { backgroundColor: sub.color },
|
|
614
|
-
onMouseDown: (e) =>
|
|
615
|
-
|
|
633
|
+
onMouseDown: (e) => {
|
|
634
|
+
e.preventDefault();
|
|
635
|
+
},
|
|
636
|
+
onClick: (e) => {
|
|
637
|
+
onItemClick(sub, e);
|
|
638
|
+
}
|
|
616
639
|
},
|
|
617
640
|
sub.name
|
|
618
641
|
) : /* @__PURE__ */ jsx(
|
|
@@ -624,8 +647,12 @@ function ToolbarDropdownPanel({
|
|
|
624
647
|
tabIndex: -1,
|
|
625
648
|
"aria-label": sub.label,
|
|
626
649
|
dangerouslySetInnerHTML: { __html: getCachedItemContent(sub.icon, sub.label) },
|
|
627
|
-
onMouseDown: (e) =>
|
|
628
|
-
|
|
650
|
+
onMouseDown: (e) => {
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
},
|
|
653
|
+
onClick: (e) => {
|
|
654
|
+
onItemClick(sub, e);
|
|
655
|
+
}
|
|
629
656
|
},
|
|
630
657
|
sub.name
|
|
631
658
|
)
|
|
@@ -651,8 +678,12 @@ function ToolbarDropdownPanel({
|
|
|
651
678
|
if (el && sub.style) el.setAttribute("style", sub.style);
|
|
652
679
|
},
|
|
653
680
|
dangerouslySetInnerHTML: { __html: getCachedItemContent(sub.icon, sub.label, dropdown.displayMode) },
|
|
654
|
-
onMouseDown: (e) =>
|
|
655
|
-
|
|
681
|
+
onMouseDown: (e) => {
|
|
682
|
+
e.preventDefault();
|
|
683
|
+
},
|
|
684
|
+
onClick: (e) => {
|
|
685
|
+
onItemClick(sub, e);
|
|
686
|
+
}
|
|
656
687
|
},
|
|
657
688
|
sub.name
|
|
658
689
|
))
|
|
@@ -686,9 +717,15 @@ function ToolbarDropdown({
|
|
|
686
717
|
disabled: isDisabled,
|
|
687
718
|
"data-dropdown": dropdown.name,
|
|
688
719
|
dangerouslySetInnerHTML: { __html: triggerHtml },
|
|
689
|
-
onMouseDown: (e) =>
|
|
690
|
-
|
|
691
|
-
|
|
720
|
+
onMouseDown: (e) => {
|
|
721
|
+
e.preventDefault();
|
|
722
|
+
},
|
|
723
|
+
onClick: () => {
|
|
724
|
+
onToggle(dropdown);
|
|
725
|
+
},
|
|
726
|
+
onFocus: () => {
|
|
727
|
+
onFocus(dropdown.name);
|
|
728
|
+
}
|
|
692
729
|
}
|
|
693
730
|
),
|
|
694
731
|
isOpen && /* @__PURE__ */ jsx(
|
|
@@ -737,14 +774,16 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
|
|
|
737
774
|
return;
|
|
738
775
|
}
|
|
739
776
|
controllerRef.current?.executeCommand(item);
|
|
740
|
-
requestAnimationFrame(() =>
|
|
741
|
-
|
|
777
|
+
requestAnimationFrame(() => {
|
|
778
|
+
editor.view.focus();
|
|
779
|
+
});
|
|
780
|
+
}, [editor, closeDropdown, controllerRef]);
|
|
742
781
|
const onDropdownItemClick = useCallback((item, event) => {
|
|
743
782
|
if (!editor) return;
|
|
744
783
|
let anchor;
|
|
745
784
|
if (item.emitEvent) {
|
|
746
785
|
const wrapper = event.currentTarget.closest(".dm-toolbar-dropdown-wrapper");
|
|
747
|
-
anchor = wrapper?.querySelector(".dm-toolbar-dropdown-trigger");
|
|
786
|
+
anchor = wrapper?.querySelector(".dm-toolbar-dropdown-trigger") ?? void 0;
|
|
748
787
|
}
|
|
749
788
|
closeDropdown();
|
|
750
789
|
if (item.emitEvent) {
|
|
@@ -752,14 +791,16 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
|
|
|
752
791
|
} else {
|
|
753
792
|
controllerRef.current?.executeCommand(item);
|
|
754
793
|
}
|
|
755
|
-
requestAnimationFrame(() =>
|
|
756
|
-
|
|
794
|
+
requestAnimationFrame(() => {
|
|
795
|
+
editor.view.focus();
|
|
796
|
+
});
|
|
797
|
+
}, [editor, closeDropdown, controllerRef]);
|
|
757
798
|
const onButtonFocus = useCallback((name) => {
|
|
758
799
|
const index = controllerRef.current?.getFlatIndex(name) ?? -1;
|
|
759
800
|
if (index >= 0) {
|
|
760
801
|
controllerRef.current?.setFocusedIndex(index);
|
|
761
802
|
}
|
|
762
|
-
}, []);
|
|
803
|
+
}, [controllerRef]);
|
|
763
804
|
if (!editor) return null;
|
|
764
805
|
return /* @__PURE__ */ jsx(
|
|
765
806
|
"div",
|
|
@@ -801,7 +842,7 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
|
|
|
801
842
|
computed = getInlineStyleAtCursor(editor, dd.computedStyleProperty);
|
|
802
843
|
if (computed) {
|
|
803
844
|
const first = computed.split(",")[0]?.replace(/['"]+/g, "").trim();
|
|
804
|
-
computed = first
|
|
845
|
+
computed = first ?? null;
|
|
805
846
|
}
|
|
806
847
|
} else {
|
|
807
848
|
computed = getComputedStyleAtCursor(editor, dd.computedStyleProperty);
|
|
@@ -834,6 +875,15 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
|
|
|
834
875
|
}
|
|
835
876
|
);
|
|
836
877
|
}
|
|
878
|
+
var INITIAL_TRAILING_STATE = {
|
|
879
|
+
isNodeSelection: false,
|
|
880
|
+
showColorPickerButton: false,
|
|
881
|
+
showBlockMenuButton: false,
|
|
882
|
+
blockMenuButtonDisabled: false,
|
|
883
|
+
currentTextColorVar: null,
|
|
884
|
+
currentBgColorVar: null,
|
|
885
|
+
hasAnyColor: false
|
|
886
|
+
};
|
|
837
887
|
function isInsideTableCell($pos) {
|
|
838
888
|
for (let d = $pos.depth; d > 0; d--) {
|
|
839
889
|
const name = $pos.node(d).type.name;
|
|
@@ -849,23 +899,32 @@ function findCellNode(pos) {
|
|
|
849
899
|
return null;
|
|
850
900
|
}
|
|
851
901
|
function useBubbleMenu(options) {
|
|
852
|
-
const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts } = options;
|
|
902
|
+
const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts: explicitContexts, icons } = options;
|
|
903
|
+
const contexts = explicitContexts ?? (items ? void 0 : editor ? defaultBubbleContexts(editor) : void 0);
|
|
853
904
|
const menuRef = useRef(null);
|
|
854
|
-
const pluginKeyRef = useRef(new PluginKey("reactBubbleMenu-" + Math.random().toString(36).slice(2, 8)));
|
|
905
|
+
const pluginKeyRef = useRef(new PluginKey("reactBubbleMenu-" + (globalThis.crypto?.randomUUID?.().slice(0, 8) ?? Math.random().toString(36).slice(2, 8))));
|
|
855
906
|
const [resolvedItems, setResolvedItems] = useState([]);
|
|
856
907
|
const [activeVersion, setActiveVersion] = useState(0);
|
|
908
|
+
const [trailing, setTrailing] = useState(INITIAL_TRAILING_STATE);
|
|
857
909
|
const activeMapRef = useRef(/* @__PURE__ */ new Map());
|
|
858
910
|
const disabledMapRef = useRef(/* @__PURE__ */ new Map());
|
|
859
911
|
const itemMapRef = useRef(/* @__PURE__ */ new Map());
|
|
860
912
|
const bubbleDefaultsRef = useRef(/* @__PURE__ */ new Map());
|
|
861
913
|
const resolvedItemsRef = useRef([]);
|
|
914
|
+
const editorRef = useRef(editor);
|
|
915
|
+
editorRef.current = editor;
|
|
862
916
|
useEffect(() => {
|
|
863
917
|
if (!editor || editor.isDestroyed || !menuRef.current) return;
|
|
918
|
+
const exts = editor.extensionManager.extensions;
|
|
919
|
+
const hasNotionColorPicker = exts.some((e) => e.name === "notionColorPicker");
|
|
920
|
+
const hasBlockContextMenu = exts.some((e) => e.name === "blockContextMenu");
|
|
864
921
|
const itemMap = /* @__PURE__ */ new Map();
|
|
922
|
+
const dropdownMap = /* @__PURE__ */ new Map();
|
|
865
923
|
for (const item of editor.toolbarItems) {
|
|
866
924
|
if (item.type === "button") {
|
|
867
925
|
itemMap.set(item.name, item);
|
|
868
926
|
} else if (item.type === "dropdown") {
|
|
927
|
+
dropdownMap.set(item.name, item);
|
|
869
928
|
for (const sub of item.items) {
|
|
870
929
|
itemMap.set(sub.name, sub);
|
|
871
930
|
}
|
|
@@ -897,7 +956,7 @@ function useBubbleMenu(options) {
|
|
|
897
956
|
let sepIdx = 0;
|
|
898
957
|
for (const item of ctxItems) {
|
|
899
958
|
if (lastGroup !== void 0 && item.group !== lastGroup) {
|
|
900
|
-
result.push({ type: "separator", name: `bsep-${sepIdx++}` });
|
|
959
|
+
result.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
|
|
901
960
|
}
|
|
902
961
|
result.push(item);
|
|
903
962
|
lastGroup = item.group;
|
|
@@ -910,8 +969,13 @@ function useBubbleMenu(options) {
|
|
|
910
969
|
let sepIdx = 0;
|
|
911
970
|
for (const name of names) {
|
|
912
971
|
if (name === "|") {
|
|
913
|
-
result.push({ type: "separator", name: `sep-${sepIdx++}` });
|
|
972
|
+
result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
|
|
914
973
|
} else {
|
|
974
|
+
const dropdown = dropdownMap.get(name);
|
|
975
|
+
if (dropdown) {
|
|
976
|
+
result.push(dropdown);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
915
979
|
const item = itemMap.get(name);
|
|
916
980
|
if (item) result.push(item);
|
|
917
981
|
}
|
|
@@ -948,7 +1012,7 @@ function useBubbleMenu(options) {
|
|
|
948
1012
|
return schemaItems.filter((item) => {
|
|
949
1013
|
const markName = typeof item.isActive === "string" ? item.isActive : null;
|
|
950
1014
|
if (!markName) return true;
|
|
951
|
-
const markType = schema.marks
|
|
1015
|
+
const markType = schema.marks[markName];
|
|
952
1016
|
if (!markType) return true;
|
|
953
1017
|
return nodeType.allowsMarkType(markType);
|
|
954
1018
|
});
|
|
@@ -968,14 +1032,16 @@ function useBubbleMenu(options) {
|
|
|
968
1032
|
};
|
|
969
1033
|
} else {
|
|
970
1034
|
shouldShowFn = ({ state }) => {
|
|
971
|
-
if (state.selection.empty
|
|
1035
|
+
if (state.selection.empty) return false;
|
|
1036
|
+
if (state.selection.node) return bubbleDefaults.has(state.selection.node.type.name);
|
|
972
1037
|
if (isInsideTableCell(state.selection.$from)) return false;
|
|
973
1038
|
return state.selection.$from.parent.type.spec.marks !== "" || state.selection.$to.parent.type.spec.marks !== "";
|
|
974
1039
|
};
|
|
975
1040
|
}
|
|
976
1041
|
}
|
|
1042
|
+
const pluginKey = pluginKeyRef.current;
|
|
977
1043
|
const plugin = createBubbleMenuPlugin({
|
|
978
|
-
pluginKey
|
|
1044
|
+
pluginKey,
|
|
979
1045
|
editor,
|
|
980
1046
|
element: menuRef.current,
|
|
981
1047
|
shouldShow: shouldShowFn,
|
|
@@ -1001,30 +1067,81 @@ function useBubbleMenu(options) {
|
|
|
1001
1067
|
canProxy = ed.can();
|
|
1002
1068
|
} catch {
|
|
1003
1069
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
activeMapRef.current.set(item.name, ToolbarController.resolveActive(ed, item));
|
|
1070
|
+
const trackButton = (btn) => {
|
|
1071
|
+
activeMapRef.current.set(btn.name, ToolbarController.resolveActive(ed, btn));
|
|
1007
1072
|
try {
|
|
1008
|
-
const canCmd = canProxy?.[
|
|
1009
|
-
disabledMapRef.current.set(
|
|
1073
|
+
const canCmd = typeof btn.command === "string" ? canProxy?.[btn.command] : void 0;
|
|
1074
|
+
disabledMapRef.current.set(btn.name, canCmd ? !(btn.commandArgs?.length ? canCmd(...btn.commandArgs) : canCmd()) : false);
|
|
1010
1075
|
} catch {
|
|
1011
|
-
disabledMapRef.current.set(
|
|
1076
|
+
disabledMapRef.current.set(btn.name, false);
|
|
1012
1077
|
}
|
|
1078
|
+
};
|
|
1079
|
+
for (const item of resolvedItemsRef.current) {
|
|
1080
|
+
if (item.type === "separator") continue;
|
|
1081
|
+
if (item.type === "dropdown") {
|
|
1082
|
+
for (const sub of item.items) trackButton(sub);
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
trackButton(item);
|
|
1013
1086
|
}
|
|
1014
1087
|
};
|
|
1088
|
+
const defaultItems = items ? resolveNames(items) : resolveNames(["bold", "italic", "underline"]);
|
|
1089
|
+
const syncTrailingState = (ed) => {
|
|
1090
|
+
const sel = ed.state.selection;
|
|
1091
|
+
const isNode = !!sel.node;
|
|
1092
|
+
let blockMenuDisabled = false;
|
|
1093
|
+
if (hasBlockContextMenu) {
|
|
1094
|
+
const { $from, $to } = ed.state.selection;
|
|
1095
|
+
if ($from.depth < 1 || $to.depth < 1) {
|
|
1096
|
+
blockMenuDisabled = true;
|
|
1097
|
+
} else {
|
|
1098
|
+
blockMenuDisabled = $from.before(1) !== $to.before(1);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
let textVar = null;
|
|
1102
|
+
let bgVar = null;
|
|
1103
|
+
let hasAny = false;
|
|
1104
|
+
if (hasNotionColorPicker) {
|
|
1105
|
+
const mark = ed.state.selection.$from.marks().find((m) => m.type.name === "textStyle");
|
|
1106
|
+
const attrs = mark?.attrs ?? {};
|
|
1107
|
+
const tToken = attrs.colorToken ?? null;
|
|
1108
|
+
const bToken = attrs.backgroundColorToken ?? null;
|
|
1109
|
+
textVar = tToken ? `var(--dm-block-text-${tToken})` : null;
|
|
1110
|
+
bgVar = bToken ? `var(--dm-block-bg-${bToken})` : null;
|
|
1111
|
+
hasAny = tToken !== null || bToken !== null;
|
|
1112
|
+
}
|
|
1113
|
+
setTrailing({
|
|
1114
|
+
isNodeSelection: isNode,
|
|
1115
|
+
showColorPickerButton: hasNotionColorPicker,
|
|
1116
|
+
showBlockMenuButton: hasBlockContextMenu,
|
|
1117
|
+
blockMenuButtonDisabled: blockMenuDisabled,
|
|
1118
|
+
currentTextColorVar: textVar,
|
|
1119
|
+
currentBgColorVar: bgVar,
|
|
1120
|
+
hasAnyColor: hasAny
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1015
1123
|
const transactionHandler = () => {
|
|
1016
1124
|
if (contexts) {
|
|
1017
1125
|
updateContextItems(editor, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
|
|
1126
|
+
} else {
|
|
1127
|
+
const sel = editor.state.selection;
|
|
1128
|
+
if (sel.node && bubbleDefaults.has(sel.node.type.name)) {
|
|
1129
|
+
setItems(bubbleDefaults.get(sel.node.type.name) ?? []);
|
|
1130
|
+
} else {
|
|
1131
|
+
setItems(defaultItems);
|
|
1132
|
+
}
|
|
1018
1133
|
}
|
|
1019
1134
|
updateStates(editor);
|
|
1135
|
+
syncTrailingState(editor);
|
|
1020
1136
|
setActiveVersion((v) => v + 1);
|
|
1021
1137
|
};
|
|
1022
1138
|
editor.on("transaction", transactionHandler);
|
|
1023
1139
|
updateStates(editor);
|
|
1140
|
+
syncTrailingState(editor);
|
|
1024
1141
|
return () => {
|
|
1025
1142
|
editor.off("transaction", transactionHandler);
|
|
1026
1143
|
if (!editor.isDestroyed) {
|
|
1027
|
-
editor.unregisterPlugin(
|
|
1144
|
+
editor.unregisterPlugin(pluginKey);
|
|
1028
1145
|
}
|
|
1029
1146
|
};
|
|
1030
1147
|
}, [editor]);
|
|
@@ -1058,14 +1175,36 @@ function useBubbleMenu(options) {
|
|
|
1058
1175
|
const isItemDisabled = (item) => {
|
|
1059
1176
|
return disabledMapRef.current.get(item.name) ?? false;
|
|
1060
1177
|
};
|
|
1061
|
-
const executeCommand = (item) => {
|
|
1178
|
+
const executeCommand = (item, event) => {
|
|
1062
1179
|
if (!editor) return;
|
|
1063
1180
|
if (item.emitEvent) {
|
|
1064
|
-
|
|
1181
|
+
const anchor = event?.currentTarget ?? event?.target ?? null;
|
|
1182
|
+
editor.emit(item.emitEvent, { anchorElement: anchor });
|
|
1065
1183
|
return;
|
|
1066
1184
|
}
|
|
1067
1185
|
ToolbarController.executeItem(editor, item);
|
|
1068
1186
|
};
|
|
1187
|
+
const openColorPicker = (anchor) => {
|
|
1188
|
+
const ed = editorRef.current;
|
|
1189
|
+
if (!ed) return;
|
|
1190
|
+
ed.emit(
|
|
1191
|
+
"notionColorOpen",
|
|
1192
|
+
{ anchorElement: anchor }
|
|
1193
|
+
);
|
|
1194
|
+
};
|
|
1195
|
+
const openBlockContextMenu = (anchor) => {
|
|
1196
|
+
const ed = editorRef.current;
|
|
1197
|
+
if (!ed) return;
|
|
1198
|
+
const $from = ed.state.selection.$from;
|
|
1199
|
+
if ($from.depth < 1) return;
|
|
1200
|
+
const depth = $from.depth > 1 && $from.node($from.depth - 1).type.name !== "doc" ? $from.depth - 1 : $from.depth;
|
|
1201
|
+
const blockPos = $from.before(depth);
|
|
1202
|
+
const editorEl = ed.view.dom.closest(".dm-editor");
|
|
1203
|
+
editorEl?.dispatchEvent(new CustomEvent("dm:block-context-menu-open", {
|
|
1204
|
+
bubbles: false,
|
|
1205
|
+
detail: { blockPos, anchorElement: anchor }
|
|
1206
|
+
}));
|
|
1207
|
+
};
|
|
1069
1208
|
return {
|
|
1070
1209
|
menuRef,
|
|
1071
1210
|
resolvedItems,
|
|
@@ -1073,9 +1212,13 @@ function useBubbleMenu(options) {
|
|
|
1073
1212
|
isItemDisabled,
|
|
1074
1213
|
executeCommand,
|
|
1075
1214
|
activeVersion,
|
|
1076
|
-
getCachedIcon: (name) => defaultIcons[name] ?? ""
|
|
1215
|
+
getCachedIcon: (name) => icons?.[name] ?? defaultIcons[name] ?? "",
|
|
1216
|
+
trailing,
|
|
1217
|
+
openColorPicker,
|
|
1218
|
+
openBlockContextMenu
|
|
1077
1219
|
};
|
|
1078
1220
|
}
|
|
1221
|
+
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>';
|
|
1079
1222
|
function DomternalBubbleMenu({
|
|
1080
1223
|
editor: editorProp,
|
|
1081
1224
|
shouldShow,
|
|
@@ -1084,18 +1227,22 @@ function DomternalBubbleMenu({
|
|
|
1084
1227
|
updateDelay,
|
|
1085
1228
|
items,
|
|
1086
1229
|
contexts,
|
|
1230
|
+
icons,
|
|
1087
1231
|
children
|
|
1088
1232
|
}) {
|
|
1089
1233
|
const { editor: contextEditor } = useCurrentEditor();
|
|
1090
1234
|
const editor = editorProp ?? contextEditor;
|
|
1091
|
-
const htmlCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
1092
1235
|
const {
|
|
1093
1236
|
menuRef,
|
|
1094
1237
|
resolvedItems,
|
|
1095
1238
|
isItemActive,
|
|
1096
1239
|
isItemDisabled,
|
|
1097
1240
|
executeCommand,
|
|
1098
|
-
|
|
1241
|
+
activeVersion,
|
|
1242
|
+
getCachedIcon,
|
|
1243
|
+
trailing,
|
|
1244
|
+
openColorPicker,
|
|
1245
|
+
openBlockContextMenu
|
|
1099
1246
|
} = useBubbleMenu({
|
|
1100
1247
|
editor,
|
|
1101
1248
|
shouldShow,
|
|
@@ -1103,21 +1250,54 @@ function DomternalBubbleMenu({
|
|
|
1103
1250
|
offset,
|
|
1104
1251
|
updateDelay,
|
|
1105
1252
|
items,
|
|
1106
|
-
contexts
|
|
1253
|
+
contexts,
|
|
1254
|
+
icons
|
|
1107
1255
|
});
|
|
1108
|
-
const getCachedHtml = (
|
|
1109
|
-
const cache =
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1256
|
+
const getCachedHtml = useMemo(() => {
|
|
1257
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1258
|
+
return (name) => {
|
|
1259
|
+
const cached = cache.get(name);
|
|
1260
|
+
if (cached !== void 0) return cached;
|
|
1261
|
+
const html = getCachedIcon(name);
|
|
1262
|
+
cache.set(name, html);
|
|
1263
|
+
return html;
|
|
1264
|
+
};
|
|
1265
|
+
}, [getCachedIcon]);
|
|
1266
|
+
const [openDropdown, setOpenDropdown] = useState(null);
|
|
1267
|
+
const closeDropdown = useCallback(() => {
|
|
1268
|
+
setOpenDropdown(null);
|
|
1269
|
+
}, []);
|
|
1270
|
+
const colorBtnRef = useRef(null);
|
|
1271
|
+
const blockMenuBtnRef = useRef(null);
|
|
1116
1272
|
return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "dm-bubble-menu", role: "toolbar", "aria-label": "Text formatting", children: [
|
|
1117
1273
|
resolvedItems.map((item) => {
|
|
1118
1274
|
if (item.type === "separator") {
|
|
1119
1275
|
return /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }, item.name);
|
|
1120
1276
|
}
|
|
1277
|
+
if (item.type === "dropdown") {
|
|
1278
|
+
return /* @__PURE__ */ jsx(
|
|
1279
|
+
BubbleDropdown,
|
|
1280
|
+
{
|
|
1281
|
+
dropdown: item,
|
|
1282
|
+
isOpen: openDropdown === item.name,
|
|
1283
|
+
onToggle: () => {
|
|
1284
|
+
setOpenDropdown(openDropdown === item.name ? null : item.name);
|
|
1285
|
+
},
|
|
1286
|
+
onClose: closeDropdown,
|
|
1287
|
+
isItemActive,
|
|
1288
|
+
getCachedHtml,
|
|
1289
|
+
activeVersion,
|
|
1290
|
+
executeSubItem: (sub) => {
|
|
1291
|
+
closeDropdown();
|
|
1292
|
+
executeCommand(sub);
|
|
1293
|
+
requestAnimationFrame(() => {
|
|
1294
|
+
editor?.view.focus();
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
item.name
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1121
1301
|
const btn = item;
|
|
1122
1302
|
const active = isItemActive(btn);
|
|
1123
1303
|
return /* @__PURE__ */ jsx(
|
|
@@ -1130,48 +1310,388 @@ function DomternalBubbleMenu({
|
|
|
1130
1310
|
"aria-label": btn.label,
|
|
1131
1311
|
"aria-pressed": active,
|
|
1132
1312
|
dangerouslySetInnerHTML: { __html: getCachedHtml(btn.icon) },
|
|
1133
|
-
onMouseDown: (e) =>
|
|
1134
|
-
|
|
1313
|
+
onMouseDown: (e) => {
|
|
1314
|
+
e.preventDefault();
|
|
1315
|
+
},
|
|
1316
|
+
onClick: (e) => {
|
|
1317
|
+
executeCommand(btn, e);
|
|
1318
|
+
}
|
|
1135
1319
|
},
|
|
1136
1320
|
btn.name
|
|
1137
1321
|
);
|
|
1138
1322
|
}),
|
|
1323
|
+
trailing.showColorPickerButton && !trailing.isNodeSelection && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1324
|
+
/* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
|
|
1325
|
+
/* @__PURE__ */ jsxs(
|
|
1326
|
+
"button",
|
|
1327
|
+
{
|
|
1328
|
+
ref: colorBtnRef,
|
|
1329
|
+
type: "button",
|
|
1330
|
+
className: `dm-toolbar-button dm-ncp-trigger${trailing.hasAnyColor ? " dm-toolbar-button--active" : ""}`,
|
|
1331
|
+
title: "Text and background color",
|
|
1332
|
+
"aria-label": "Text and background color",
|
|
1333
|
+
"aria-haspopup": "dialog",
|
|
1334
|
+
onMouseDown: (e) => {
|
|
1335
|
+
e.preventDefault();
|
|
1336
|
+
},
|
|
1337
|
+
onClick: () => {
|
|
1338
|
+
if (colorBtnRef.current) openColorPicker(colorBtnRef.current);
|
|
1339
|
+
},
|
|
1340
|
+
children: [
|
|
1341
|
+
/* @__PURE__ */ jsx(
|
|
1342
|
+
"span",
|
|
1343
|
+
{
|
|
1344
|
+
className: "dm-ncp-trigger-glyph",
|
|
1345
|
+
style: trailing.currentTextColorVar ? { color: trailing.currentTextColorVar } : void 0,
|
|
1346
|
+
children: "A"
|
|
1347
|
+
}
|
|
1348
|
+
),
|
|
1349
|
+
/* @__PURE__ */ jsx(
|
|
1350
|
+
"span",
|
|
1351
|
+
{
|
|
1352
|
+
className: "dm-ncp-trigger-underline",
|
|
1353
|
+
style: trailing.currentBgColorVar ? { backgroundColor: trailing.currentBgColorVar } : void 0
|
|
1354
|
+
}
|
|
1355
|
+
)
|
|
1356
|
+
]
|
|
1357
|
+
}
|
|
1358
|
+
)
|
|
1359
|
+
] }),
|
|
1360
|
+
trailing.showBlockMenuButton && !trailing.isNodeSelection && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1361
|
+
/* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
|
|
1362
|
+
/* @__PURE__ */ jsx(
|
|
1363
|
+
"button",
|
|
1364
|
+
{
|
|
1365
|
+
ref: blockMenuBtnRef,
|
|
1366
|
+
type: "button",
|
|
1367
|
+
className: "dm-toolbar-button",
|
|
1368
|
+
disabled: trailing.blockMenuButtonDisabled,
|
|
1369
|
+
title: trailing.blockMenuButtonDisabled ? "Block actions (select within a single block)" : "More options",
|
|
1370
|
+
"aria-label": "More options",
|
|
1371
|
+
"aria-haspopup": "menu",
|
|
1372
|
+
dangerouslySetInnerHTML: { __html: getCachedHtml("dotsThree") },
|
|
1373
|
+
onMouseDown: (e) => {
|
|
1374
|
+
e.preventDefault();
|
|
1375
|
+
},
|
|
1376
|
+
onClick: () => {
|
|
1377
|
+
if (blockMenuBtnRef.current) openBlockContextMenu(blockMenuBtnRef.current);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
)
|
|
1381
|
+
] }),
|
|
1139
1382
|
children
|
|
1140
1383
|
] });
|
|
1141
1384
|
}
|
|
1385
|
+
function BubbleDropdown({
|
|
1386
|
+
dropdown,
|
|
1387
|
+
isOpen,
|
|
1388
|
+
onToggle,
|
|
1389
|
+
onClose,
|
|
1390
|
+
isItemActive,
|
|
1391
|
+
getCachedHtml,
|
|
1392
|
+
activeVersion,
|
|
1393
|
+
executeSubItem
|
|
1394
|
+
}) {
|
|
1395
|
+
const triggerRef = useRef(null);
|
|
1396
|
+
const panelRef = useRef(null);
|
|
1397
|
+
const dropdownActive = dropdown.items.some((sub) => isItemActive(sub));
|
|
1398
|
+
const activeChild = dropdown.dynamicIcon ? dropdown.items.find((sub) => isItemActive(sub)) : void 0;
|
|
1399
|
+
const triggerIcon = activeChild?.icon ?? dropdown.icon;
|
|
1400
|
+
const triggerHtml = getCachedHtml(triggerIcon) + DROPDOWN_CARET2;
|
|
1401
|
+
useLayoutEffect(() => {
|
|
1402
|
+
if (!isOpen) return;
|
|
1403
|
+
const trigger = triggerRef.current;
|
|
1404
|
+
const panel = panelRef.current;
|
|
1405
|
+
if (!trigger || !panel) return;
|
|
1406
|
+
const cleanupFloating = positionFloatingOnce(trigger, panel, {
|
|
1407
|
+
placement: "bottom-start",
|
|
1408
|
+
offsetValue: 4
|
|
1409
|
+
});
|
|
1410
|
+
const controller = new AbortController();
|
|
1411
|
+
const { signal } = controller;
|
|
1412
|
+
document.addEventListener("mousedown", (e) => {
|
|
1413
|
+
const target = e.target;
|
|
1414
|
+
if (!target) return;
|
|
1415
|
+
if (panel.contains(target)) return;
|
|
1416
|
+
if (trigger.contains(target)) return;
|
|
1417
|
+
onClose();
|
|
1418
|
+
}, { signal });
|
|
1419
|
+
document.addEventListener("keydown", (e) => {
|
|
1420
|
+
if (e.key === "Escape") {
|
|
1421
|
+
e.preventDefault();
|
|
1422
|
+
onClose();
|
|
1423
|
+
}
|
|
1424
|
+
}, { signal });
|
|
1425
|
+
const editorEl = trigger.closest(".dm-editor");
|
|
1426
|
+
if (editorEl) {
|
|
1427
|
+
editorEl.addEventListener("dm:dismiss-overlays", () => {
|
|
1428
|
+
onClose();
|
|
1429
|
+
}, { signal });
|
|
1430
|
+
}
|
|
1431
|
+
return () => {
|
|
1432
|
+
controller.abort();
|
|
1433
|
+
cleanupFloating();
|
|
1434
|
+
};
|
|
1435
|
+
}, [isOpen, onClose]);
|
|
1436
|
+
return /* @__PURE__ */ jsxs("div", { className: "dm-toolbar-dropdown-wrapper", "data-dropdown-wrapper": dropdown.name, children: [
|
|
1437
|
+
/* @__PURE__ */ jsx(
|
|
1438
|
+
"button",
|
|
1439
|
+
{
|
|
1440
|
+
ref: triggerRef,
|
|
1441
|
+
type: "button",
|
|
1442
|
+
className: `dm-toolbar-button dm-toolbar-dropdown-trigger${dropdownActive ? " dm-toolbar-button--active" : ""}`,
|
|
1443
|
+
"aria-expanded": isOpen,
|
|
1444
|
+
"aria-haspopup": "true",
|
|
1445
|
+
"aria-label": dropdown.label,
|
|
1446
|
+
title: dropdown.label,
|
|
1447
|
+
"data-dropdown": dropdown.name,
|
|
1448
|
+
dangerouslySetInnerHTML: { __html: triggerHtml },
|
|
1449
|
+
onMouseDown: (e) => {
|
|
1450
|
+
e.preventDefault();
|
|
1451
|
+
},
|
|
1452
|
+
onClick: onToggle
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
1456
|
+
"div",
|
|
1457
|
+
{
|
|
1458
|
+
ref: panelRef,
|
|
1459
|
+
className: "dm-toolbar-dropdown-panel",
|
|
1460
|
+
role: "menu",
|
|
1461
|
+
"data-dm-editor-ui": true,
|
|
1462
|
+
"data-dropdown-panel": dropdown.name,
|
|
1463
|
+
children: dropdown.items.map((sub) => {
|
|
1464
|
+
const subActive = isItemActive(sub);
|
|
1465
|
+
const subHtml = `${getCachedHtml(sub.icon)} ${sub.label}`;
|
|
1466
|
+
return /* @__PURE__ */ jsx(
|
|
1467
|
+
"button",
|
|
1468
|
+
{
|
|
1469
|
+
type: "button",
|
|
1470
|
+
className: `dm-toolbar-dropdown-item${subActive ? " dm-toolbar-dropdown-item--active" : ""}`,
|
|
1471
|
+
role: "menuitem",
|
|
1472
|
+
"aria-label": sub.label,
|
|
1473
|
+
dangerouslySetInnerHTML: { __html: subHtml },
|
|
1474
|
+
onMouseDown: (e) => {
|
|
1475
|
+
e.preventDefault();
|
|
1476
|
+
},
|
|
1477
|
+
onClick: () => {
|
|
1478
|
+
executeSubItem(sub);
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
sub.name
|
|
1482
|
+
);
|
|
1483
|
+
})
|
|
1484
|
+
}
|
|
1485
|
+
)
|
|
1486
|
+
] });
|
|
1487
|
+
}
|
|
1142
1488
|
function DomternalFloatingMenu({
|
|
1143
1489
|
editor: editorProp,
|
|
1144
1490
|
shouldShow,
|
|
1145
1491
|
offset = 0,
|
|
1492
|
+
items,
|
|
1493
|
+
keymap,
|
|
1494
|
+
icons,
|
|
1495
|
+
requireExplicitTrigger = false,
|
|
1146
1496
|
children
|
|
1147
1497
|
}) {
|
|
1148
1498
|
const { editor: contextEditor } = useCurrentEditor();
|
|
1149
1499
|
const editor = editorProp ?? contextEditor;
|
|
1150
1500
|
const menuRef = useRef(null);
|
|
1151
1501
|
const pluginKeyRef = useRef(
|
|
1152
|
-
new PluginKey("reactFloatingMenu-" + Math.random().toString(36).slice(2, 8))
|
|
1502
|
+
new PluginKey("reactFloatingMenu-" + (globalThis.crypto?.randomUUID?.().slice(0, 8) ?? Math.random().toString(36).slice(2, 8)))
|
|
1153
1503
|
);
|
|
1154
1504
|
const shouldShowRef = useRef(shouldShow);
|
|
1155
1505
|
shouldShowRef.current = shouldShow;
|
|
1156
1506
|
const offsetRef = useRef(offset);
|
|
1157
1507
|
offsetRef.current = offset;
|
|
1508
|
+
const keymapRef = useRef(keymap);
|
|
1509
|
+
keymapRef.current = keymap;
|
|
1510
|
+
const requireExplicitTriggerRef = useRef(requireExplicitTrigger);
|
|
1511
|
+
requireExplicitTriggerRef.current = requireExplicitTrigger;
|
|
1158
1512
|
useEffect(() => {
|
|
1159
1513
|
if (!editor || editor.isDestroyed || !menuRef.current) return;
|
|
1514
|
+
const pluginKey = pluginKeyRef.current;
|
|
1160
1515
|
const plugin = createFloatingMenuPlugin({
|
|
1161
|
-
pluginKey
|
|
1516
|
+
pluginKey,
|
|
1162
1517
|
editor,
|
|
1163
1518
|
element: menuRef.current,
|
|
1164
1519
|
...shouldShowRef.current && { shouldShow: shouldShowRef.current },
|
|
1165
|
-
offset: offsetRef.current
|
|
1520
|
+
offset: offsetRef.current,
|
|
1521
|
+
...keymapRef.current && { keymap: keymapRef.current },
|
|
1522
|
+
requireExplicitTrigger: requireExplicitTriggerRef.current
|
|
1166
1523
|
});
|
|
1167
1524
|
editor.registerPlugin(plugin);
|
|
1168
1525
|
return () => {
|
|
1169
|
-
if (!editor.isDestroyed)
|
|
1170
|
-
|
|
1171
|
-
|
|
1526
|
+
if (!editor.isDestroyed) editor.unregisterPlugin(pluginKey);
|
|
1527
|
+
};
|
|
1528
|
+
}, [editor]);
|
|
1529
|
+
const useDefaultRender = !children;
|
|
1530
|
+
const [, bump] = useState(0);
|
|
1531
|
+
const forceRender = useCallback(() => {
|
|
1532
|
+
bump((v) => v + 1);
|
|
1533
|
+
}, []);
|
|
1534
|
+
const controllerRef = useRef(null);
|
|
1535
|
+
useEffect(() => {
|
|
1536
|
+
if (!useDefaultRender || !editor || editor.isDestroyed) {
|
|
1537
|
+
controllerRef.current = null;
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const controller2 = new FloatingMenuController(editor, forceRender, items);
|
|
1541
|
+
controller2.subscribe();
|
|
1542
|
+
controllerRef.current = controller2;
|
|
1543
|
+
forceRender();
|
|
1544
|
+
return () => {
|
|
1545
|
+
controller2.destroy();
|
|
1546
|
+
controllerRef.current = null;
|
|
1172
1547
|
};
|
|
1548
|
+
}, [editor, useDefaultRender, items, forceRender]);
|
|
1549
|
+
const controller = controllerRef.current;
|
|
1550
|
+
const groups = useMemo(() => controller?.groups ?? [], [controller]);
|
|
1551
|
+
const focusedIndex = controller?.focusedIndex ?? -1;
|
|
1552
|
+
useLayoutEffect(() => {
|
|
1553
|
+
if (focusedIndex < 0 || !menuRef.current) return;
|
|
1554
|
+
const target = menuRef.current.querySelector(
|
|
1555
|
+
`[data-floating-menu-index="${String(focusedIndex)}"]`
|
|
1556
|
+
);
|
|
1557
|
+
target?.focus();
|
|
1558
|
+
}, [focusedIndex]);
|
|
1559
|
+
const resolveIcon = useCallback((name) => {
|
|
1560
|
+
if (!name) return "";
|
|
1561
|
+
return icons?.[name] ?? defaultIcons[name] ?? "";
|
|
1562
|
+
}, [icons]);
|
|
1563
|
+
const onItemClick = useCallback((item) => {
|
|
1564
|
+
if (!editor || !controllerRef.current) return;
|
|
1565
|
+
controllerRef.current.execute(item);
|
|
1566
|
+
requestAnimationFrame(() => {
|
|
1567
|
+
editor.view.focus();
|
|
1568
|
+
});
|
|
1173
1569
|
}, [editor]);
|
|
1174
|
-
|
|
1570
|
+
const onMenuKeyDown = useCallback((e) => {
|
|
1571
|
+
const ctl = controllerRef.current;
|
|
1572
|
+
if (!ctl) return;
|
|
1573
|
+
const focused = ctl.focusedItem();
|
|
1574
|
+
switch (e.key) {
|
|
1575
|
+
case "ArrowDown":
|
|
1576
|
+
e.preventDefault();
|
|
1577
|
+
ctl.next();
|
|
1578
|
+
return;
|
|
1579
|
+
case "ArrowUp":
|
|
1580
|
+
e.preventDefault();
|
|
1581
|
+
ctl.prev();
|
|
1582
|
+
return;
|
|
1583
|
+
case "Home":
|
|
1584
|
+
e.preventDefault();
|
|
1585
|
+
ctl.first();
|
|
1586
|
+
return;
|
|
1587
|
+
case "End":
|
|
1588
|
+
e.preventDefault();
|
|
1589
|
+
ctl.last();
|
|
1590
|
+
return;
|
|
1591
|
+
case "Escape":
|
|
1592
|
+
e.preventDefault();
|
|
1593
|
+
e.stopPropagation();
|
|
1594
|
+
ctl.leaveMenu();
|
|
1595
|
+
if (editor) editor.view.focus();
|
|
1596
|
+
return;
|
|
1597
|
+
case "Enter":
|
|
1598
|
+
case " ":
|
|
1599
|
+
if (focused) {
|
|
1600
|
+
e.preventDefault();
|
|
1601
|
+
onItemClick(focused);
|
|
1602
|
+
}
|
|
1603
|
+
return;
|
|
1604
|
+
default:
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
}, [editor, onItemClick]);
|
|
1608
|
+
const flatNames = useMemo(
|
|
1609
|
+
() => groups.flatMap((g) => g.items.map((i) => i.name)),
|
|
1610
|
+
[groups]
|
|
1611
|
+
);
|
|
1612
|
+
if (!editor && !useDefaultRender) return null;
|
|
1613
|
+
return /* @__PURE__ */ jsx(
|
|
1614
|
+
"div",
|
|
1615
|
+
{
|
|
1616
|
+
ref: menuRef,
|
|
1617
|
+
className: "dm-floating-menu",
|
|
1618
|
+
role: "menu",
|
|
1619
|
+
"aria-label": "Insert block",
|
|
1620
|
+
"data-dm-editor-ui": "",
|
|
1621
|
+
onKeyDown: useDefaultRender ? onMenuKeyDown : void 0,
|
|
1622
|
+
children: useDefaultRender ? groups.map((group, gi) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1623
|
+
group.name && /* @__PURE__ */ jsx("div", { className: "dm-floating-menu-group-label", id: `dm-fm-g${String(gi)}`, children: group.name }),
|
|
1624
|
+
/* @__PURE__ */ jsx(
|
|
1625
|
+
"div",
|
|
1626
|
+
{
|
|
1627
|
+
className: "dm-floating-menu-group",
|
|
1628
|
+
role: "group",
|
|
1629
|
+
...group.name && { "aria-labelledby": `dm-fm-g${String(gi)}` },
|
|
1630
|
+
children: group.items.map((item) => {
|
|
1631
|
+
const flatIndex = flatNames.indexOf(item.name);
|
|
1632
|
+
const isFocused = flatIndex === focusedIndex;
|
|
1633
|
+
const disabled = controller?.isDisabled(item) ?? false;
|
|
1634
|
+
return /* @__PURE__ */ jsx(
|
|
1635
|
+
FloatingMenuItemButton,
|
|
1636
|
+
{
|
|
1637
|
+
item,
|
|
1638
|
+
flatIndex,
|
|
1639
|
+
tabIndex: isFocused || focusedIndex < 0 && flatIndex === 0 ? 0 : -1,
|
|
1640
|
+
disabled,
|
|
1641
|
+
iconHtml: resolveIcon(item.icon),
|
|
1642
|
+
onClick: onItemClick
|
|
1643
|
+
},
|
|
1644
|
+
item.name
|
|
1645
|
+
);
|
|
1646
|
+
})
|
|
1647
|
+
}
|
|
1648
|
+
)
|
|
1649
|
+
] }, group.name || `__group-${String(gi)}`)) : children
|
|
1650
|
+
}
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
function FloatingMenuItemButton({
|
|
1654
|
+
item,
|
|
1655
|
+
flatIndex,
|
|
1656
|
+
tabIndex,
|
|
1657
|
+
disabled,
|
|
1658
|
+
iconHtml,
|
|
1659
|
+
onClick
|
|
1660
|
+
}) {
|
|
1661
|
+
const handleClick = useCallback(() => {
|
|
1662
|
+
onClick(item);
|
|
1663
|
+
}, [item, onClick]);
|
|
1664
|
+
const onMouseDown = useCallback((e) => {
|
|
1665
|
+
e.preventDefault();
|
|
1666
|
+
}, []);
|
|
1667
|
+
return /* @__PURE__ */ jsxs(
|
|
1668
|
+
"button",
|
|
1669
|
+
{
|
|
1670
|
+
type: "button",
|
|
1671
|
+
role: "menuitem",
|
|
1672
|
+
className: "dm-floating-menu-item",
|
|
1673
|
+
"data-floating-menu-item": item.name,
|
|
1674
|
+
"data-floating-menu-index": String(flatIndex),
|
|
1675
|
+
tabIndex,
|
|
1676
|
+
"aria-disabled": disabled || void 0,
|
|
1677
|
+
"aria-keyshortcuts": item.shortcut,
|
|
1678
|
+
disabled,
|
|
1679
|
+
onClick: handleClick,
|
|
1680
|
+
onMouseDown,
|
|
1681
|
+
children: [
|
|
1682
|
+
iconHtml && /* @__PURE__ */ jsx(
|
|
1683
|
+
"span",
|
|
1684
|
+
{
|
|
1685
|
+
className: "dm-floating-menu-item-icon",
|
|
1686
|
+
"aria-hidden": "true",
|
|
1687
|
+
dangerouslySetInnerHTML: { __html: iconHtml }
|
|
1688
|
+
}
|
|
1689
|
+
),
|
|
1690
|
+
/* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-label", children: item.label }),
|
|
1691
|
+
item.shortcut && /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-shortcut", "aria-hidden": "true", children: item.shortcut })
|
|
1692
|
+
]
|
|
1693
|
+
}
|
|
1694
|
+
);
|
|
1175
1695
|
}
|
|
1176
1696
|
function useEmojiPicker(editor, emojis) {
|
|
1177
1697
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -1209,7 +1729,8 @@ function useEmojiPicker(editor, emojis) {
|
|
|
1209
1729
|
const frequentlyUsed = useMemo(() => {
|
|
1210
1730
|
if (!isOpen) return [];
|
|
1211
1731
|
const storage = getEmojiStorage(editor);
|
|
1212
|
-
|
|
1732
|
+
if (!storage) return [];
|
|
1733
|
+
const getFreq = storage["getFrequentlyUsed"];
|
|
1213
1734
|
if (!getFreq) return [];
|
|
1214
1735
|
const names = getFreq();
|
|
1215
1736
|
if (!names.length) return [];
|
|
@@ -1242,7 +1763,9 @@ function useEmojiPicker(editor, emojis) {
|
|
|
1242
1763
|
clickOutsideRef.current = (e) => {
|
|
1243
1764
|
const target = e.target;
|
|
1244
1765
|
if (pickerRef.current && !pickerRef.current.contains(target) && target !== anchorRef.current && !anchorRef.current?.contains(target)) {
|
|
1245
|
-
requestAnimationFrame(() =>
|
|
1766
|
+
requestAnimationFrame(() => {
|
|
1767
|
+
close();
|
|
1768
|
+
});
|
|
1246
1769
|
}
|
|
1247
1770
|
};
|
|
1248
1771
|
document.addEventListener("mousedown", clickOutsideRef.current);
|
|
@@ -1405,7 +1928,7 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
|
1405
1928
|
const swatches = Array.from(grid.querySelectorAll(".dm-emoji-swatch"));
|
|
1406
1929
|
if (!swatches.length) return;
|
|
1407
1930
|
const current = document.activeElement;
|
|
1408
|
-
|
|
1931
|
+
const idx = swatches.indexOf(current);
|
|
1409
1932
|
if (idx === -1) {
|
|
1410
1933
|
if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
|
|
1411
1934
|
event.preventDefault();
|
|
@@ -1466,8 +1989,12 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
|
1466
1989
|
"aria-selected": activeCategory === cat,
|
|
1467
1990
|
title: cat,
|
|
1468
1991
|
"aria-label": cat,
|
|
1469
|
-
onMouseDown: (e) =>
|
|
1470
|
-
|
|
1992
|
+
onMouseDown: (e) => {
|
|
1993
|
+
e.preventDefault();
|
|
1994
|
+
},
|
|
1995
|
+
onClick: () => {
|
|
1996
|
+
scrollToCategory(cat);
|
|
1997
|
+
},
|
|
1471
1998
|
children: categoryIcon(cat)
|
|
1472
1999
|
},
|
|
1473
2000
|
cat
|
|
@@ -1480,8 +2007,12 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
|
1480
2007
|
tabIndex: -1,
|
|
1481
2008
|
title: formatName(item.name),
|
|
1482
2009
|
"aria-label": formatName(item.name),
|
|
1483
|
-
onMouseDown: (e) =>
|
|
1484
|
-
|
|
2010
|
+
onMouseDown: (e) => {
|
|
2011
|
+
e.preventDefault();
|
|
2012
|
+
},
|
|
2013
|
+
onClick: () => {
|
|
2014
|
+
selectEmoji(item);
|
|
2015
|
+
},
|
|
1485
2016
|
children: item.emoji
|
|
1486
2017
|
},
|
|
1487
2018
|
item.name
|
|
@@ -1496,8 +2027,12 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
|
1496
2027
|
tabIndex: -1,
|
|
1497
2028
|
title: formatName(item.name),
|
|
1498
2029
|
"aria-label": formatName(item.name),
|
|
1499
|
-
onMouseDown: (e) =>
|
|
1500
|
-
|
|
2030
|
+
onMouseDown: (e) => {
|
|
2031
|
+
e.preventDefault();
|
|
2032
|
+
},
|
|
2033
|
+
onClick: () => {
|
|
2034
|
+
selectEmoji(item);
|
|
2035
|
+
},
|
|
1501
2036
|
children: item.emoji
|
|
1502
2037
|
},
|
|
1503
2038
|
item.name
|
|
@@ -1513,8 +2048,12 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
|
|
|
1513
2048
|
tabIndex: -1,
|
|
1514
2049
|
title: formatName(item.name),
|
|
1515
2050
|
"aria-label": formatName(item.name),
|
|
1516
|
-
onMouseDown: (e) =>
|
|
1517
|
-
|
|
2051
|
+
onMouseDown: (e) => {
|
|
2052
|
+
e.preventDefault();
|
|
2053
|
+
},
|
|
2054
|
+
onClick: () => {
|
|
2055
|
+
selectEmoji(item);
|
|
2056
|
+
},
|
|
1518
2057
|
children: item.emoji
|
|
1519
2058
|
},
|
|
1520
2059
|
item.name
|
|
@@ -1657,6 +2196,349 @@ function EditorContent({ editor, innerRef, ...htmlProps }) {
|
|
|
1657
2196
|
}
|
|
1658
2197
|
);
|
|
1659
2198
|
}
|
|
2199
|
+
var TOKEN_LABELS = {
|
|
2200
|
+
gray: "Gray",
|
|
2201
|
+
brown: "Brown",
|
|
2202
|
+
orange: "Orange",
|
|
2203
|
+
yellow: "Yellow",
|
|
2204
|
+
green: "Green",
|
|
2205
|
+
blue: "Blue",
|
|
2206
|
+
purple: "Purple",
|
|
2207
|
+
pink: "Pink",
|
|
2208
|
+
red: "Red"
|
|
2209
|
+
};
|
|
2210
|
+
function useNotionColorPicker(options) {
|
|
2211
|
+
const { editor } = options;
|
|
2212
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2213
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
2214
|
+
const [hostEl, setHostEl] = useState(null);
|
|
2215
|
+
const [currentTextToken, setCurrentTextToken] = useState(null);
|
|
2216
|
+
const [currentBgToken, setCurrentBgToken] = useState(null);
|
|
2217
|
+
const [palette, setPalette] = useState([]);
|
|
2218
|
+
const panelRef = useRef(null);
|
|
2219
|
+
const isOpenRef = useRef(false);
|
|
2220
|
+
isOpenRef.current = isOpen;
|
|
2221
|
+
const editorRef = useRef(editor);
|
|
2222
|
+
useEffect(() => {
|
|
2223
|
+
editorRef.current = editor;
|
|
2224
|
+
}, [editor]);
|
|
2225
|
+
const anchorRef = useRef(null);
|
|
2226
|
+
anchorRef.current = anchorEl;
|
|
2227
|
+
const setStorageOpen2 = useCallback((open) => {
|
|
2228
|
+
const ed = editorRef.current;
|
|
2229
|
+
if (!ed) return;
|
|
2230
|
+
const slot = ed.storage["notionColorPicker"];
|
|
2231
|
+
if (slot && typeof slot === "object") {
|
|
2232
|
+
slot.isOpen = open;
|
|
2233
|
+
}
|
|
2234
|
+
}, []);
|
|
2235
|
+
const syncFromSelection = useCallback(() => {
|
|
2236
|
+
const ed = editorRef.current;
|
|
2237
|
+
if (!ed) return;
|
|
2238
|
+
const { selection } = ed.state;
|
|
2239
|
+
let mark = null;
|
|
2240
|
+
if (selection.empty) {
|
|
2241
|
+
mark = selection.$from.marks().find((m) => m.type.name === "textStyle") ?? null;
|
|
2242
|
+
} else {
|
|
2243
|
+
ed.state.doc.nodesBetween(selection.from, selection.to, (node) => {
|
|
2244
|
+
if (mark) return false;
|
|
2245
|
+
if (node.isText) {
|
|
2246
|
+
const found = node.marks.find((m) => m.type.name === "textStyle");
|
|
2247
|
+
if (found) mark = found;
|
|
2248
|
+
}
|
|
2249
|
+
return true;
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
const attrs = mark?.attrs ?? {};
|
|
2253
|
+
setCurrentTextToken(attrs.colorToken ?? null);
|
|
2254
|
+
setCurrentBgToken(attrs.backgroundColorToken ?? null);
|
|
2255
|
+
}, []);
|
|
2256
|
+
const close = useCallback((opts = {}) => {
|
|
2257
|
+
if (!isOpenRef.current) return;
|
|
2258
|
+
setIsOpen(false);
|
|
2259
|
+
setStorageOpen2(false);
|
|
2260
|
+
if (opts.refocus) {
|
|
2261
|
+
editorRef.current?.view.focus();
|
|
2262
|
+
}
|
|
2263
|
+
setAnchorEl(null);
|
|
2264
|
+
}, [setStorageOpen2]);
|
|
2265
|
+
useEffect(() => {
|
|
2266
|
+
if (!editor || editor.isDestroyed) return;
|
|
2267
|
+
const host = editor.view.dom.closest(".dm-editor") ?? null;
|
|
2268
|
+
setHostEl(host);
|
|
2269
|
+
const ext = editor.extensionManager.extensions.find((e) => e.name === "notionColorPicker");
|
|
2270
|
+
const extOptions = ext?.options ?? null;
|
|
2271
|
+
setPalette(extOptions?.palette ? [...extOptions.palette] : []);
|
|
2272
|
+
const onOpen = (...args) => {
|
|
2273
|
+
const detail = args[0];
|
|
2274
|
+
const incomingAnchor = detail?.anchorElement;
|
|
2275
|
+
if (!incomingAnchor) return;
|
|
2276
|
+
if (isOpenRef.current && anchorRef.current === incomingAnchor) {
|
|
2277
|
+
close({ refocus: true });
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
setAnchorEl(incomingAnchor);
|
|
2281
|
+
syncFromSelection();
|
|
2282
|
+
setIsOpen(true);
|
|
2283
|
+
setStorageOpen2(true);
|
|
2284
|
+
};
|
|
2285
|
+
const onSelectionUpdate = () => {
|
|
2286
|
+
if (!isOpenRef.current) return;
|
|
2287
|
+
if (!anchorRef.current?.isConnected) {
|
|
2288
|
+
close();
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
if (editor.state.selection.empty) {
|
|
2292
|
+
close();
|
|
2293
|
+
} else {
|
|
2294
|
+
syncFromSelection();
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
editor.on("notionColorOpen", onOpen);
|
|
2298
|
+
editor.on("selectionUpdate", onSelectionUpdate);
|
|
2299
|
+
return () => {
|
|
2300
|
+
editor.off("notionColorOpen", onOpen);
|
|
2301
|
+
editor.off("selectionUpdate", onSelectionUpdate);
|
|
2302
|
+
if (isOpenRef.current) setStorageOpen2(false);
|
|
2303
|
+
};
|
|
2304
|
+
}, [editor, close, syncFromSelection, setStorageOpen2]);
|
|
2305
|
+
useEffect(() => {
|
|
2306
|
+
if (!isOpen) return;
|
|
2307
|
+
const controller = new AbortController();
|
|
2308
|
+
const { signal } = controller;
|
|
2309
|
+
document.addEventListener("mousedown", (e) => {
|
|
2310
|
+
const target = e.target;
|
|
2311
|
+
if (!target) return;
|
|
2312
|
+
if (panelRef.current?.contains(target)) return;
|
|
2313
|
+
if (anchorRef.current?.contains(target)) return;
|
|
2314
|
+
close({ refocus: false });
|
|
2315
|
+
}, { signal });
|
|
2316
|
+
document.addEventListener("keydown", (e) => {
|
|
2317
|
+
if (e.key === "Escape" && isOpenRef.current) {
|
|
2318
|
+
e.preventDefault();
|
|
2319
|
+
close({ refocus: true });
|
|
2320
|
+
}
|
|
2321
|
+
}, { signal });
|
|
2322
|
+
return () => {
|
|
2323
|
+
controller.abort();
|
|
2324
|
+
};
|
|
2325
|
+
}, [isOpen, close]);
|
|
2326
|
+
const applyText = useCallback((token) => {
|
|
2327
|
+
const ed = editorRef.current;
|
|
2328
|
+
if (!ed) return;
|
|
2329
|
+
ed.commands.setTextColorToken(token);
|
|
2330
|
+
syncFromSelection();
|
|
2331
|
+
}, [syncFromSelection]);
|
|
2332
|
+
const applyBg = useCallback((token) => {
|
|
2333
|
+
const ed = editorRef.current;
|
|
2334
|
+
if (!ed) return;
|
|
2335
|
+
ed.commands.setBackgroundColorToken(token);
|
|
2336
|
+
syncFromSelection();
|
|
2337
|
+
}, [syncFromSelection]);
|
|
2338
|
+
const tokenLabel = useCallback((token) => {
|
|
2339
|
+
return TOKEN_LABELS[token] ?? token.charAt(0).toUpperCase() + token.slice(1);
|
|
2340
|
+
}, []);
|
|
2341
|
+
const onPanelKeydown = useCallback((event) => {
|
|
2342
|
+
const cols = 5;
|
|
2343
|
+
const root = panelRef.current;
|
|
2344
|
+
if (!root) return;
|
|
2345
|
+
const swatches = Array.from(
|
|
2346
|
+
root.querySelectorAll(".dm-ncp-swatch")
|
|
2347
|
+
);
|
|
2348
|
+
if (!swatches.length) return;
|
|
2349
|
+
const active = document.activeElement;
|
|
2350
|
+
const idx = active ? swatches.indexOf(active) : -1;
|
|
2351
|
+
if (idx === -1) return;
|
|
2352
|
+
let next = idx;
|
|
2353
|
+
switch (event.key) {
|
|
2354
|
+
case "ArrowRight":
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
next = Math.min(idx + 1, swatches.length - 1);
|
|
2357
|
+
break;
|
|
2358
|
+
case "ArrowLeft":
|
|
2359
|
+
event.preventDefault();
|
|
2360
|
+
next = Math.max(idx - 1, 0);
|
|
2361
|
+
break;
|
|
2362
|
+
case "ArrowDown":
|
|
2363
|
+
event.preventDefault();
|
|
2364
|
+
next = Math.min(idx + cols, swatches.length - 1);
|
|
2365
|
+
break;
|
|
2366
|
+
case "ArrowUp":
|
|
2367
|
+
event.preventDefault();
|
|
2368
|
+
next = Math.max(idx - cols, 0);
|
|
2369
|
+
break;
|
|
2370
|
+
case "Home":
|
|
2371
|
+
event.preventDefault();
|
|
2372
|
+
next = 0;
|
|
2373
|
+
break;
|
|
2374
|
+
case "End":
|
|
2375
|
+
event.preventDefault();
|
|
2376
|
+
next = swatches.length - 1;
|
|
2377
|
+
break;
|
|
2378
|
+
default:
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
swatches[next]?.focus();
|
|
2382
|
+
}, []);
|
|
2383
|
+
return {
|
|
2384
|
+
isOpen,
|
|
2385
|
+
hostEl,
|
|
2386
|
+
anchorEl,
|
|
2387
|
+
panelRef,
|
|
2388
|
+
currentTextToken,
|
|
2389
|
+
currentBgToken,
|
|
2390
|
+
palette,
|
|
2391
|
+
applyText,
|
|
2392
|
+
applyBg,
|
|
2393
|
+
close,
|
|
2394
|
+
tokenLabel,
|
|
2395
|
+
onPanelKeydown
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function DomternalNotionColorPicker({
|
|
2399
|
+
editor: editorProp,
|
|
2400
|
+
children
|
|
2401
|
+
}) {
|
|
2402
|
+
const { editor: contextEditor } = useCurrentEditor();
|
|
2403
|
+
const editor = editorProp ?? contextEditor;
|
|
2404
|
+
const api = useNotionColorPicker({ editor });
|
|
2405
|
+
const {
|
|
2406
|
+
isOpen,
|
|
2407
|
+
hostEl,
|
|
2408
|
+
anchorEl,
|
|
2409
|
+
panelRef,
|
|
2410
|
+
currentTextToken,
|
|
2411
|
+
currentBgToken,
|
|
2412
|
+
palette,
|
|
2413
|
+
applyText,
|
|
2414
|
+
applyBg,
|
|
2415
|
+
tokenLabel,
|
|
2416
|
+
onPanelKeydown
|
|
2417
|
+
} = api;
|
|
2418
|
+
useLayoutEffect(() => {
|
|
2419
|
+
if (!isOpen || !anchorEl || !panelRef.current) return;
|
|
2420
|
+
const panel = panelRef.current;
|
|
2421
|
+
const cleanupFloating = positionFloating(anchorEl, panel, {
|
|
2422
|
+
placement: "bottom-start",
|
|
2423
|
+
offsetValue: 4
|
|
2424
|
+
});
|
|
2425
|
+
let id2 = 0;
|
|
2426
|
+
const id1 = requestAnimationFrame(() => {
|
|
2427
|
+
id2 = requestAnimationFrame(() => {
|
|
2428
|
+
if (!panel.isConnected) return;
|
|
2429
|
+
const active = panel.querySelector(".dm-ncp-swatch.dm-ncp-active");
|
|
2430
|
+
const fallback = panel.querySelector('.dm-ncp-swatch--text[data-color="null"]');
|
|
2431
|
+
(active ?? fallback)?.focus({ preventScroll: true });
|
|
2432
|
+
});
|
|
2433
|
+
});
|
|
2434
|
+
return () => {
|
|
2435
|
+
cancelAnimationFrame(id1);
|
|
2436
|
+
if (id2) cancelAnimationFrame(id2);
|
|
2437
|
+
cleanupFloating();
|
|
2438
|
+
};
|
|
2439
|
+
}, [isOpen, anchorEl, panelRef]);
|
|
2440
|
+
if (!isOpen || !hostEl) return null;
|
|
2441
|
+
const defaultContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2442
|
+
/* @__PURE__ */ jsxs("div", { className: "dm-ncp-section", children: [
|
|
2443
|
+
/* @__PURE__ */ jsx("div", { className: "dm-ncp-label", children: "Text color" }),
|
|
2444
|
+
/* @__PURE__ */ jsxs("div", { className: "dm-ncp-grid", children: [
|
|
2445
|
+
/* @__PURE__ */ jsx(
|
|
2446
|
+
"button",
|
|
2447
|
+
{
|
|
2448
|
+
type: "button",
|
|
2449
|
+
className: `dm-ncp-swatch dm-ncp-swatch--text${currentTextToken === null ? " dm-ncp-active" : ""}`,
|
|
2450
|
+
"aria-pressed": currentTextToken === null,
|
|
2451
|
+
"data-color": "null",
|
|
2452
|
+
title: "Default text color",
|
|
2453
|
+
"aria-label": "Default text color",
|
|
2454
|
+
onMouseDown: (e) => {
|
|
2455
|
+
e.preventDefault();
|
|
2456
|
+
},
|
|
2457
|
+
onClick: () => {
|
|
2458
|
+
applyText(null);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
),
|
|
2462
|
+
palette.map((t) => /* @__PURE__ */ jsx(
|
|
2463
|
+
"button",
|
|
2464
|
+
{
|
|
2465
|
+
type: "button",
|
|
2466
|
+
className: `dm-ncp-swatch dm-ncp-swatch--text${currentTextToken === t ? " dm-ncp-active" : ""}`,
|
|
2467
|
+
"aria-pressed": currentTextToken === t,
|
|
2468
|
+
"data-color": t,
|
|
2469
|
+
title: tokenLabel(t),
|
|
2470
|
+
"aria-label": `${tokenLabel(t)} text`,
|
|
2471
|
+
onMouseDown: (e) => {
|
|
2472
|
+
e.preventDefault();
|
|
2473
|
+
},
|
|
2474
|
+
onClick: () => {
|
|
2475
|
+
applyText(t);
|
|
2476
|
+
}
|
|
2477
|
+
},
|
|
2478
|
+
t
|
|
2479
|
+
))
|
|
2480
|
+
] })
|
|
2481
|
+
] }),
|
|
2482
|
+
/* @__PURE__ */ jsxs("div", { className: "dm-ncp-section", children: [
|
|
2483
|
+
/* @__PURE__ */ jsx("div", { className: "dm-ncp-label", children: "Background color" }),
|
|
2484
|
+
/* @__PURE__ */ jsxs("div", { className: "dm-ncp-grid", children: [
|
|
2485
|
+
/* @__PURE__ */ jsx(
|
|
2486
|
+
"button",
|
|
2487
|
+
{
|
|
2488
|
+
type: "button",
|
|
2489
|
+
className: `dm-ncp-swatch dm-ncp-swatch--bg${currentBgToken === null ? " dm-ncp-active" : ""}`,
|
|
2490
|
+
"aria-pressed": currentBgToken === null,
|
|
2491
|
+
"data-color": "null",
|
|
2492
|
+
title: "Default background",
|
|
2493
|
+
"aria-label": "Default background",
|
|
2494
|
+
onMouseDown: (e) => {
|
|
2495
|
+
e.preventDefault();
|
|
2496
|
+
},
|
|
2497
|
+
onClick: () => {
|
|
2498
|
+
applyBg(null);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
),
|
|
2502
|
+
palette.map((t) => /* @__PURE__ */ jsx(
|
|
2503
|
+
"button",
|
|
2504
|
+
{
|
|
2505
|
+
type: "button",
|
|
2506
|
+
className: `dm-ncp-swatch dm-ncp-swatch--bg${currentBgToken === t ? " dm-ncp-active" : ""}`,
|
|
2507
|
+
"aria-pressed": currentBgToken === t,
|
|
2508
|
+
"data-color": t,
|
|
2509
|
+
title: `${tokenLabel(t)} background`,
|
|
2510
|
+
"aria-label": `${tokenLabel(t)} background`,
|
|
2511
|
+
onMouseDown: (e) => {
|
|
2512
|
+
e.preventDefault();
|
|
2513
|
+
},
|
|
2514
|
+
onClick: () => {
|
|
2515
|
+
applyBg(t);
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
t
|
|
2519
|
+
))
|
|
2520
|
+
] })
|
|
2521
|
+
] })
|
|
2522
|
+
] });
|
|
2523
|
+
const content = typeof children === "function" ? children(api) : children ?? defaultContent;
|
|
2524
|
+
return createPortal(
|
|
2525
|
+
/* @__PURE__ */ jsx(
|
|
2526
|
+
"div",
|
|
2527
|
+
{
|
|
2528
|
+
ref: panelRef,
|
|
2529
|
+
className: "dm-notion-color-picker",
|
|
2530
|
+
"data-show": true,
|
|
2531
|
+
"data-dm-editor-ui": true,
|
|
2532
|
+
role: "dialog",
|
|
2533
|
+
"aria-label": "Text and background color",
|
|
2534
|
+
"aria-modal": "false",
|
|
2535
|
+
onKeyDown: onPanelKeydown,
|
|
2536
|
+
children: content
|
|
2537
|
+
}
|
|
2538
|
+
),
|
|
2539
|
+
hostEl
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
1660
2542
|
var ReactNodeViewContext = createContext(null);
|
|
1661
2543
|
var ReactNodeViewProvider = ReactNodeViewContext.Provider;
|
|
1662
2544
|
function useReactNodeView() {
|
|
@@ -1775,7 +2657,9 @@ var ReactNodeView = class {
|
|
|
1775
2657
|
}
|
|
1776
2658
|
destroy() {
|
|
1777
2659
|
const root = this.root;
|
|
1778
|
-
setTimeout(() =>
|
|
2660
|
+
setTimeout(() => {
|
|
2661
|
+
root.unmount();
|
|
2662
|
+
}, 0);
|
|
1779
2663
|
}
|
|
1780
2664
|
ignoreMutation(mutation) {
|
|
1781
2665
|
if (!this.contentDOM) return true;
|
|
@@ -1810,6 +2694,6 @@ function NodeViewContent({ as: Tag = "div", style, ...props }) {
|
|
|
1810
2694
|
);
|
|
1811
2695
|
}
|
|
1812
2696
|
|
|
1813
|
-
export { DEFAULT_EXTENSIONS, Domternal, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalToolbar, EditorContent, EditorProvider, NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useCurrentEditor, useEditor, useEditorState, useReactNodeView };
|
|
2697
|
+
export { DEFAULT_EXTENSIONS, Domternal, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalNotionColorPicker, DomternalToolbar, EditorContent, EditorProvider, NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useCurrentEditor, useEditor, useEditorState, useNotionColorPicker, useReactNodeView };
|
|
1814
2698
|
//# sourceMappingURL=index.js.map
|
|
1815
2699
|
//# sourceMappingURL=index.js.map
|