@react-email/editor 0.0.0-experimental.37 → 0.0.0-experimental.39

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 (41) hide show
  1. package/dist/{extension-XZBBjuHO.mjs → extension-CnC8y63H.mjs} +3 -7
  2. package/dist/extension-CnC8y63H.mjs.map +1 -0
  3. package/dist/{extension-B8yvCdun.cjs → extension-dGpPpEvD.cjs} +2 -6
  4. package/dist/index-C4KcMQ0R.d.cts.map +1 -1
  5. package/dist/index-CxX7W63O.d.mts.map +1 -1
  6. package/dist/index.cjs +7 -7
  7. package/dist/index.css +27 -54
  8. package/dist/index.css.map +1 -1
  9. package/dist/index.d.cts +4 -4
  10. package/dist/index.d.cts.map +1 -1
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.d.mts.map +1 -1
  13. package/dist/index.mjs +7 -7
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/plugins/index.cjs +1 -1
  16. package/dist/plugins/index.d.cts.map +1 -1
  17. package/dist/plugins/index.d.mts.map +1 -1
  18. package/dist/plugins/index.mjs +1 -1
  19. package/dist/{root-BMxsq1NF.mjs → root-CkYaJZpj.mjs} +645 -730
  20. package/dist/root-CkYaJZpj.mjs.map +1 -0
  21. package/dist/{root-CNVO39XG.cjs → root-Gu08xybW.cjs} +723 -832
  22. package/dist/{set-text-alignment-GMXOPMlJ.mjs → set-text-alignment-OA8IMWmO.mjs} +1 -1
  23. package/dist/{set-text-alignment-GMXOPMlJ.mjs.map → set-text-alignment-OA8IMWmO.mjs.map} +1 -1
  24. package/dist/ui/bubble-menu/bubble-menu.css +139 -0
  25. package/dist/ui/index.cjs +32 -57
  26. package/dist/ui/index.d.cts +280 -364
  27. package/dist/ui/index.d.cts.map +1 -1
  28. package/dist/ui/index.d.mts +280 -364
  29. package/dist/ui/index.d.mts.map +1 -1
  30. package/dist/ui/index.mjs +17 -35
  31. package/dist/ui/index.mjs.map +1 -1
  32. package/dist/ui/themes/default.css +27 -54
  33. package/dist/utils/index.cjs +1 -1
  34. package/dist/utils/index.mjs +1 -1
  35. package/package.json +1 -1
  36. package/dist/extension-XZBBjuHO.mjs.map +0 -1
  37. package/dist/root-BMxsq1NF.mjs.map +0 -1
  38. package/dist/ui/button-bubble-menu/button-bubble-menu.css +0 -63
  39. package/dist/ui/image-bubble-menu/image-bubble-menu.css +0 -29
  40. package/dist/ui/link-bubble-menu/link-bubble-menu.css +0 -68
  41. /package/dist/{set-text-alignment-aNb7Ml9N.cjs → set-text-alignment-Cv72txmv.cjs} +0 -0
@@ -1,12 +1,12 @@
1
1
  import { a as MAX_COLUMNS_DEPTH, c as getColumnsDepth, t as editorEventBus } from "./event-bus-CHEzOS_O.mjs";
2
- import { t as setTextAlignment } from "./set-text-alignment-GMXOPMlJ.mjs";
2
+ import { t as setTextAlignment } from "./set-text-alignment-OA8IMWmO.mjs";
3
3
  import { useCurrentEditor, useEditorState } from "@tiptap/react";
4
4
  import * as React from "react";
5
5
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { PluginKey } from "@tiptap/pm/state";
8
- import * as Popover from "@radix-ui/react-popover";
9
8
  import { BubbleMenu } from "@tiptap/react/menus";
9
+ import * as Popover from "@radix-ui/react-popover";
10
10
  import { autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react-dom";
11
11
  import Suggestion from "@tiptap/suggestion";
12
12
  import { createPortal } from "react-dom";
@@ -744,137 +744,28 @@ function useBubbleMenuContext() {
744
744
  }
745
745
 
746
746
  //#endregion
747
- //#region src/ui/bubble-menu/item.tsx
748
- function BubbleMenuItem({ name, isActive, onCommand, className, children, ...rest }) {
747
+ //#region src/ui/bubble-menu/button-edit-link.tsx
748
+ function BubbleMenuButtonEditLink({ className, children, onClick, onMouseDown, ...rest }) {
749
+ const { setIsEditing } = useBubbleMenuContext();
749
750
  return /* @__PURE__ */ jsx("button", {
750
- type: "button",
751
- "aria-label": name,
752
- "aria-pressed": isActive,
753
- className,
754
- "data-re-bubble-menu-item": "",
755
- "data-item": name,
756
- ...isActive ? { "data-active": "" } : {},
757
- onMouseDown: (e) => e.preventDefault(),
758
- onClick: onCommand,
759
751
  ...rest,
760
- children
761
- });
762
- }
763
-
764
- //#endregion
765
- //#region src/ui/bubble-menu/align-center.tsx
766
- function BubbleMenuAlignCenter({ className, children }) {
767
- const { editor } = useBubbleMenuContext();
768
- return /* @__PURE__ */ jsx(BubbleMenuItem, {
769
- name: "align-center",
770
- isActive: useEditorState({
771
- editor,
772
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
773
- }),
774
- onCommand: () => setTextAlignment(editor, "center"),
775
- className,
776
- children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
777
- });
778
- }
779
-
780
- //#endregion
781
- //#region src/ui/bubble-menu/align-left.tsx
782
- function BubbleMenuAlignLeft({ className, children }) {
783
- const { editor } = useBubbleMenuContext();
784
- return /* @__PURE__ */ jsx(BubbleMenuItem, {
785
- name: "align-left",
786
- isActive: useEditorState({
787
- editor,
788
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
789
- }),
790
- onCommand: () => setTextAlignment(editor, "left"),
791
- className,
792
- children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
793
- });
794
- }
795
-
796
- //#endregion
797
- //#region src/ui/bubble-menu/align-right.tsx
798
- function BubbleMenuAlignRight({ className, children }) {
799
- const { editor } = useBubbleMenuContext();
800
- return /* @__PURE__ */ jsx(BubbleMenuItem, {
801
- name: "align-right",
802
- isActive: useEditorState({
803
- editor,
804
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
805
- }),
806
- onCommand: () => setTextAlignment(editor, "right"),
807
- className,
808
- children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
809
- });
810
- }
811
-
812
- //#endregion
813
- //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
814
- function createMarkBubbleItem(config) {
815
- function MarkBubbleItem({ className, children }) {
816
- const { editor } = useBubbleMenuContext();
817
- const isActive = useEditorState({
818
- editor,
819
- selector: ({ editor: editor$1 }) => {
820
- if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
821
- return editor$1?.isActive(config.activeName) ?? false;
822
- }
823
- });
824
- const handleCommand = () => {
825
- const chain = editor.chain().focus();
826
- const method = chain[config.command];
827
- if (method) method.call(chain).run();
828
- };
829
- return /* @__PURE__ */ jsx(BubbleMenuItem, {
830
- name: config.name,
831
- isActive,
832
- onCommand: handleCommand,
833
- className,
834
- children: children ?? config.icon
835
- });
836
- }
837
- MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
838
- return MarkBubbleItem;
839
- }
840
-
841
- //#endregion
842
- //#region src/ui/bubble-menu/bold.tsx
843
- const BubbleMenuBold = createMarkBubbleItem({
844
- name: "bold",
845
- activeName: "bold",
846
- command: "toggleBold",
847
- icon: /* @__PURE__ */ jsx(BoldIcon, {})
848
- });
849
-
850
- //#endregion
851
- //#region src/ui/bubble-menu/code.tsx
852
- const BubbleMenuCode = createMarkBubbleItem({
853
- name: "code",
854
- activeName: "code",
855
- command: "toggleCode",
856
- icon: /* @__PURE__ */ jsx(CodeIcon, {})
857
- });
858
-
859
- //#endregion
860
- //#region src/ui/bubble-menu/group.tsx
861
- function BubbleMenuItemGroup({ className, children }) {
862
- return /* @__PURE__ */ jsx("fieldset", {
752
+ type: "button",
753
+ "aria-label": "Edit link",
754
+ "data-re-btn-bm-item": "",
755
+ "data-item": "edit-link",
863
756
  className,
864
- "data-re-bubble-menu-group": "",
865
- children
757
+ onMouseDown: (e) => {
758
+ e.preventDefault();
759
+ onMouseDown?.(e);
760
+ },
761
+ onClick: (e) => {
762
+ onClick?.(e);
763
+ setIsEditing(true);
764
+ },
765
+ children: children ?? /* @__PURE__ */ jsx(PencilIcon, {})
866
766
  });
867
767
  }
868
768
 
869
- //#endregion
870
- //#region src/ui/bubble-menu/italic.tsx
871
- const BubbleMenuItalic = createMarkBubbleItem({
872
- name: "italic",
873
- activeName: "italic",
874
- command: "toggleItalic",
875
- icon: /* @__PURE__ */ jsx(ItalicIcon, {})
876
- });
877
-
878
769
  //#endregion
879
770
  //#region src/ui/bubble-menu/utils.ts
880
771
  const SAFE_PROTOCOLS = new Set([
@@ -922,81 +813,27 @@ function focusEditor(editor) {
922
813
  }
923
814
 
924
815
  //#endregion
925
- //#region src/ui/bubble-menu/link-selector.tsx
926
- function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children, open: controlledOpen, onOpenChange }) {
927
- const { editor } = useBubbleMenuContext();
928
- const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
929
- const isControlled = controlledOpen !== void 0;
930
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
931
- const setIsOpen = React.useCallback((value) => {
932
- if (!isControlled) setUncontrolledOpen(value);
933
- onOpenChange?.(value);
934
- }, [isControlled, onOpenChange]);
935
- const editorState = useEditorState({
936
- editor,
937
- selector: ({ editor: editor$1 }) => ({
938
- isLinkActive: editor$1?.isActive("link") ?? false,
939
- hasLink: Boolean(editor$1?.getAttributes("link").href),
940
- currentHref: editor$1?.getAttributes("link").href || ""
941
- })
942
- });
943
- const setIsOpenRef = React.useRef(setIsOpen);
944
- setIsOpenRef.current = setIsOpen;
945
- React.useEffect(() => {
946
- const subscription = editorEventBus.on("bubble-menu:add-link", () => {
947
- setIsOpenRef.current(true);
948
- });
949
- return () => {
950
- setIsOpenRef.current(false);
951
- subscription.unsubscribe();
952
- };
953
- }, []);
954
- if (!editorState) return null;
955
- const handleOpenLink = () => {
956
- setIsOpen(!isOpen);
957
- };
958
- return /* @__PURE__ */ jsxs("div", {
959
- "data-re-link-selector": "",
960
- ...isOpen ? { "data-open": "" } : {},
961
- ...editorState.hasLink ? { "data-has-link": "" } : {},
962
- className,
963
- children: [showToggle && /* @__PURE__ */ jsx("button", {
964
- type: "button",
965
- "aria-expanded": isOpen,
966
- "aria-haspopup": "true",
967
- "aria-label": "Add link",
968
- "aria-pressed": editorState.isLinkActive && editorState.hasLink,
969
- "data-re-link-selector-trigger": "",
970
- onClick: handleOpenLink,
971
- children: /* @__PURE__ */ jsx(LinkIcon, {})
972
- }), isOpen && /* @__PURE__ */ jsx(LinkForm, {
973
- editor,
974
- currentHref: editorState.currentHref,
975
- validateUrl,
976
- onLinkApply,
977
- onLinkRemove,
978
- setIsOpen,
979
- children
980
- })]
981
- });
982
- }
983
- function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
816
+ //#region src/ui/bubble-menu/button-form.tsx
817
+ function BubbleMenuButtonForm({ className, validateUrl, onLinkApply, onLinkRemove }) {
818
+ const { editor, isEditing, setIsEditing } = useBubbleMenuContext();
984
819
  const inputRef = React.useRef(null);
985
820
  const formRef = React.useRef(null);
986
- const displayHref = currentHref === "#" ? "" : currentHref;
821
+ const buttonHref = editor.getAttributes("button").href ?? "";
822
+ const displayHref = buttonHref === "#" ? "" : buttonHref;
987
823
  const [inputValue, setInputValue] = React.useState(displayHref);
988
824
  React.useEffect(() => {
825
+ if (!isEditing) return;
826
+ const currentHref = editor.getAttributes("button").href ?? "";
827
+ setInputValue(currentHref === "#" ? "" : currentHref);
989
828
  const timeoutId = setTimeout(() => {
990
829
  inputRef.current?.focus();
991
830
  }, 0);
992
831
  return () => clearTimeout(timeoutId);
993
- }, []);
832
+ }, [isEditing, editor]);
994
833
  React.useEffect(() => {
834
+ if (!isEditing) return;
995
835
  const handleKeyDown = (event) => {
996
- if (event.key === "Escape") {
997
- if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
998
- setIsOpen(false);
999
- }
836
+ if (event.key === "Escape") setIsEditing(false);
1000
837
  };
1001
838
  const handleClickOutside = (event) => {
1002
839
  if (formRef.current && !formRef.current.contains(event.target)) {
@@ -1006,7 +843,7 @@ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove,
1006
843
  cancelable: true
1007
844
  });
1008
845
  form.dispatchEvent(submitEvent);
1009
- setIsOpen(false);
846
+ setIsEditing(false);
1010
847
  }
1011
848
  };
1012
849
  document.addEventListener("mousedown", handleClickOutside);
@@ -1015,391 +852,403 @@ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove,
1015
852
  window.removeEventListener("keydown", handleKeyDown);
1016
853
  document.removeEventListener("mousedown", handleClickOutside);
1017
854
  };
1018
- }, [editor, setIsOpen]);
855
+ }, [isEditing, setIsEditing]);
856
+ if (!isEditing) return null;
1019
857
  function handleSubmit(e) {
1020
858
  e.preventDefault();
1021
859
  const value = inputValue.trim();
1022
860
  if (value === "") {
1023
- setLinkHref(editor, "");
1024
- setIsOpen(false);
861
+ editor.commands.updateButton({ href: "#" });
862
+ setIsEditing(false);
1025
863
  focusEditor(editor);
1026
864
  onLinkRemove?.();
1027
865
  return;
1028
866
  }
1029
867
  const finalValue = (validateUrl ?? getUrlFromString)(value);
1030
868
  if (!finalValue) {
1031
- setLinkHref(editor, "");
1032
- setIsOpen(false);
869
+ editor.commands.updateButton({ href: "#" });
870
+ setIsEditing(false);
1033
871
  focusEditor(editor);
1034
872
  onLinkRemove?.();
1035
873
  return;
1036
874
  }
1037
- setLinkHref(editor, finalValue);
1038
- setIsOpen(false);
875
+ editor.commands.updateButton({ href: finalValue });
876
+ setIsEditing(false);
1039
877
  focusEditor(editor);
1040
878
  onLinkApply?.(finalValue);
1041
879
  }
1042
880
  function handleUnlink(e) {
1043
881
  e.stopPropagation();
1044
- setLinkHref(editor, "");
1045
- setIsOpen(false);
882
+ editor.commands.updateButton({ href: "#" });
883
+ setIsEditing(false);
1046
884
  focusEditor(editor);
1047
885
  onLinkRemove?.();
1048
886
  }
1049
887
  return /* @__PURE__ */ jsxs("form", {
1050
888
  ref: formRef,
1051
- "data-re-link-selector-form": "",
889
+ "data-re-btn-bm-form": "",
890
+ className,
1052
891
  onMouseDown: (e) => e.stopPropagation(),
1053
892
  onClick: (e) => e.stopPropagation(),
1054
893
  onKeyDown: (e) => e.stopPropagation(),
1055
894
  onSubmit: handleSubmit,
1056
- children: [
1057
- /* @__PURE__ */ jsx("input", {
1058
- ref: inputRef,
1059
- "data-re-link-selector-input": "",
1060
- value: inputValue,
1061
- onFocus: (e) => e.stopPropagation(),
1062
- onChange: (e) => setInputValue(e.target.value),
1063
- placeholder: "Paste a link",
1064
- type: "text"
1065
- }),
1066
- children,
1067
- displayHref ? /* @__PURE__ */ jsx("button", {
1068
- type: "button",
1069
- "aria-label": "Remove link",
1070
- "data-re-link-selector-unlink": "",
1071
- onClick: handleUnlink,
1072
- children: /* @__PURE__ */ jsx(UnlinkIcon, {})
1073
- }) : /* @__PURE__ */ jsx("button", {
1074
- type: "submit",
1075
- "aria-label": "Apply link",
1076
- "data-re-link-selector-apply": "",
1077
- onMouseDown: (e) => e.stopPropagation(),
1078
- children: /* @__PURE__ */ jsx(Check, {})
1079
- })
1080
- ]
895
+ children: [/* @__PURE__ */ jsx("input", {
896
+ ref: inputRef,
897
+ "data-re-btn-bm-input": "",
898
+ value: inputValue,
899
+ onFocus: (e) => e.stopPropagation(),
900
+ onChange: (e) => setInputValue(e.target.value),
901
+ placeholder: "Paste a link",
902
+ type: "text"
903
+ }), displayHref ? /* @__PURE__ */ jsx("button", {
904
+ type: "button",
905
+ "aria-label": "Remove link",
906
+ "data-re-btn-bm-unlink": "",
907
+ onClick: handleUnlink,
908
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
909
+ }) : /* @__PURE__ */ jsx("button", {
910
+ type: "submit",
911
+ "aria-label": "Apply link",
912
+ "data-re-btn-bm-apply": "",
913
+ onMouseDown: (e) => e.stopPropagation(),
914
+ children: /* @__PURE__ */ jsx(Check, {})
915
+ })]
1081
916
  });
1082
917
  }
1083
918
 
1084
919
  //#endregion
1085
- //#region src/ui/bubble-menu/node-selector.tsx
1086
- const NodeSelectorContext = React.createContext(null);
1087
- function useNodeSelectorContext() {
1088
- const context = React.useContext(NodeSelectorContext);
1089
- if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
1090
- return context;
920
+ //#region src/ui/bubble-menu/button-toolbar.tsx
921
+ function BubbleMenuButtonToolbar({ children, ...rest }) {
922
+ const { isEditing } = useBubbleMenuContext();
923
+ if (isEditing) return null;
924
+ return /* @__PURE__ */ jsx("div", {
925
+ "data-re-btn-bm-toolbar": "",
926
+ ...rest,
927
+ children
928
+ });
1091
929
  }
1092
- function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
930
+
931
+ //#endregion
932
+ //#region src/ui/bubble-menu/button-unlink.tsx
933
+ function BubbleMenuButtonUnlink({ className, children, onClick, onMouseDown, onLinkRemove, ...rest }) {
1093
934
  const { editor } = useBubbleMenuContext();
1094
- const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
1095
- const isControlled = controlledOpen !== void 0;
1096
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1097
- const setIsOpen = React.useCallback((value) => {
1098
- if (!isControlled) setUncontrolledOpen(value);
1099
- onOpenChange?.(value);
1100
- }, [isControlled, onOpenChange]);
1101
- const editorState = useEditorState({
1102
- editor,
1103
- selector: ({ editor: editor$1 }) => ({
1104
- isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
1105
- isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
1106
- isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
1107
- isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
1108
- isBulletListActive: editor$1?.isActive("bulletList") ?? false,
1109
- isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
1110
- isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
1111
- isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
1112
- })
1113
- });
1114
- const allItems = React.useMemo(() => [
1115
- {
1116
- name: "Text",
1117
- icon: TextIcon,
1118
- command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
1119
- isActive: editorState?.isParagraphActive ?? false
1120
- },
1121
- {
1122
- name: "Title",
1123
- icon: Heading1,
1124
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
1125
- isActive: editorState?.isHeading1Active ?? false
1126
- },
1127
- {
1128
- name: "Subtitle",
1129
- icon: Heading2,
1130
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
1131
- isActive: editorState?.isHeading2Active ?? false
1132
- },
1133
- {
1134
- name: "Heading",
1135
- icon: Heading3,
1136
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
1137
- isActive: editorState?.isHeading3Active ?? false
1138
- },
1139
- {
1140
- name: "Bullet List",
1141
- icon: List,
1142
- command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
1143
- isActive: editorState?.isBulletListActive ?? false
1144
- },
1145
- {
1146
- name: "Numbered List",
1147
- icon: ListOrdered,
1148
- command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
1149
- isActive: editorState?.isOrderedListActive ?? false
935
+ return /* @__PURE__ */ jsx("button", {
936
+ ...rest,
937
+ type: "button",
938
+ "aria-label": "Remove link",
939
+ "data-re-btn-bm-item": "",
940
+ "data-item": "unlink",
941
+ className,
942
+ onMouseDown: (e) => {
943
+ e.preventDefault();
944
+ onMouseDown?.(e);
1150
945
  },
1151
- {
1152
- name: "Quote",
1153
- icon: TextQuote,
1154
- command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
1155
- isActive: editorState?.isBlockquoteActive ?? false
946
+ onClick: (e) => {
947
+ onClick?.(e);
948
+ editor.commands.updateButton({ href: "#" });
949
+ focusEditor(editor);
950
+ onLinkRemove?.();
1156
951
  },
1157
- {
1158
- name: "Code",
1159
- icon: Code,
1160
- command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
1161
- isActive: editorState?.isCodeBlockActive ?? false
1162
- }
1163
- ], [editor, editorState]);
1164
- const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
1165
- const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
1166
- const contextValue = React.useMemo(() => ({
1167
- items,
1168
- activeItem,
1169
- isOpen,
1170
- setIsOpen
1171
- }), [
1172
- items,
1173
- activeItem,
1174
- isOpen,
1175
- setIsOpen
1176
- ]);
1177
- if (!editorState || items.length === 0) return null;
1178
- return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
1179
- value: contextValue,
1180
- children: /* @__PURE__ */ jsx(Popover.Root, {
1181
- open: isOpen,
1182
- onOpenChange: setIsOpen,
1183
- children: /* @__PURE__ */ jsx("div", {
1184
- "data-re-node-selector": "",
1185
- ...isOpen ? { "data-open": "" } : {},
1186
- className,
1187
- children
1188
- })
1189
- })
1190
- });
1191
- }
1192
- function NodeSelectorTrigger({ className, children }) {
1193
- const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
1194
- return /* @__PURE__ */ jsx(Popover.Trigger, {
1195
- "data-re-node-selector-trigger": "",
1196
- className,
1197
- onClick: () => setIsOpen(!isOpen),
1198
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
1199
- });
1200
- }
1201
- function NodeSelectorContent({ className, align = "start", children }) {
1202
- const { items, setIsOpen } = useNodeSelectorContext();
1203
- return /* @__PURE__ */ jsx(Popover.Content, {
1204
- align,
1205
- "data-re-node-selector-content": "",
1206
- className,
1207
- children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
1208
- const Icon = item.icon;
1209
- return /* @__PURE__ */ jsxs("button", {
1210
- type: "button",
1211
- "data-re-node-selector-item": "",
1212
- ...item.isActive ? { "data-active": "" } : {},
1213
- onClick: () => {
1214
- item.command();
1215
- setIsOpen(false);
1216
- },
1217
- children: [
1218
- /* @__PURE__ */ jsx(Icon, {}),
1219
- /* @__PURE__ */ jsx("span", { children: item.name }),
1220
- item.isActive && /* @__PURE__ */ jsx(Check, {})
1221
- ]
1222
- }, item.name);
1223
- })
1224
- });
1225
- }
1226
- function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
1227
- return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
1228
- omit,
1229
- open,
1230
- onOpenChange,
1231
- className,
1232
- children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
952
+ children: children ?? /* @__PURE__ */ jsx(UnlinkIcon, {})
1233
953
  });
1234
954
  }
1235
955
 
956
+ //#endregion
957
+ //#region src/ui/bubble-menu/triggers.ts
958
+ const bubbleMenuTriggers = {
959
+ textSelection(hideWhenActiveNodes = [], hideWhenActiveMarks = []) {
960
+ return ({ editor, state }) => {
961
+ for (const node of hideWhenActiveNodes) {
962
+ if (editor.isActive(node)) return false;
963
+ const { $from } = state.selection;
964
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === node) return false;
965
+ }
966
+ for (const mark of hideWhenActiveMarks) if (editor.isActive(mark)) return false;
967
+ return editor.view.state.selection.content().size > 0;
968
+ };
969
+ },
970
+ node(name) {
971
+ return ({ editor }) => editor.isActive(name);
972
+ },
973
+ nodeWithoutSelection(name) {
974
+ return ({ editor }) => editor.isActive(name) && editor.view.state.selection.content().size === 0;
975
+ }
976
+ };
977
+
1236
978
  //#endregion
1237
979
  //#region src/ui/bubble-menu/root.tsx
1238
- function BubbleMenuRoot({ excludeNodes = [], excludeMarks = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children, ...rest }) {
980
+ const defaultPluginKey = new PluginKey("bubbleMenu");
981
+ function BubbleMenuRoot({ shouldShow, pluginKey: pluginKey$1 = defaultPluginKey, hideWhenActiveNodes = [], hideWhenActiveMarks = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children, ...rest }) {
1239
982
  const { editor } = useCurrentEditor();
983
+ const [isEditing, setIsEditing] = React.useState(false);
984
+ const resolvedShouldShow = shouldShow ?? bubbleMenuTriggers.textSelection(hideWhenActiveNodes, hideWhenActiveMarks);
1240
985
  if (!editor) return null;
1241
986
  return /* @__PURE__ */ jsx(BubbleMenu, {
1242
987
  editor,
1243
- pluginKey: "textBubbleMenu",
988
+ pluginKey: pluginKey$1,
1244
989
  "data-re-bubble-menu": "",
1245
- shouldShow: ({ editor: editor$1, view, state }) => {
1246
- for (const node of excludeNodes) {
1247
- if (editor$1.isActive(node)) return false;
1248
- const { $from } = state.selection;
1249
- for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === node) return false;
1250
- }
1251
- for (const mark of excludeMarks) if (editor$1.isActive(mark)) return false;
1252
- if (view.dom.classList.contains("dragging")) return false;
1253
- return editor$1.view.state.selection.content().size > 0;
1254
- },
990
+ shouldShow: resolvedShouldShow,
1255
991
  options: {
1256
992
  placement,
1257
993
  offset: offset$1,
1258
- onHide
994
+ onHide: () => {
995
+ setIsEditing(false);
996
+ onHide?.();
997
+ }
1259
998
  },
1260
999
  className,
1261
1000
  ...rest,
1262
1001
  children: /* @__PURE__ */ jsx(BubbleMenuContext.Provider, {
1263
- value: { editor },
1002
+ value: {
1003
+ editor,
1004
+ isEditing,
1005
+ setIsEditing
1006
+ },
1264
1007
  children
1265
1008
  })
1266
1009
  });
1267
1010
  }
1268
1011
 
1269
1012
  //#endregion
1270
- //#region src/ui/bubble-menu/strike.tsx
1271
- const BubbleMenuStrike = createMarkBubbleItem({
1272
- name: "strike",
1273
- activeName: "strike",
1274
- command: "toggleStrike",
1275
- icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
1276
- });
1013
+ //#region src/ui/bubble-menu/button-default.tsx
1014
+ const buttonPluginKey = new PluginKey("buttonBubbleMenu");
1015
+ function BubbleMenuButtonDefaultInner({ validateUrl, onLinkApply, onLinkRemove }) {
1016
+ const { editor } = useBubbleMenuContext();
1017
+ const buttonHref = useEditorState({
1018
+ editor,
1019
+ selector: ({ editor: e }) => e?.getAttributes("button").href ?? ""
1020
+ });
1021
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(BubbleMenuButtonToolbar, { children: [/* @__PURE__ */ jsx(BubbleMenuButtonEditLink, {}), (buttonHref ?? "") !== "" && buttonHref !== "#" && /* @__PURE__ */ jsx(BubbleMenuButtonUnlink, { onLinkRemove })] }), /* @__PURE__ */ jsx(BubbleMenuButtonForm, {
1022
+ validateUrl,
1023
+ onLinkApply,
1024
+ onLinkRemove
1025
+ })] });
1026
+ }
1027
+ function BubbleMenuButtonDefault({ placement = "top", offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove, ...rest }) {
1028
+ return /* @__PURE__ */ jsx(BubbleMenuRoot, {
1029
+ shouldShow: bubbleMenuTriggers.node("button"),
1030
+ pluginKey: buttonPluginKey,
1031
+ placement,
1032
+ offset: offset$1,
1033
+ onHide,
1034
+ className,
1035
+ ...rest,
1036
+ children: /* @__PURE__ */ jsx(BubbleMenuButtonDefaultInner, {
1037
+ validateUrl,
1038
+ onLinkApply,
1039
+ onLinkRemove
1040
+ })
1041
+ });
1042
+ }
1277
1043
 
1278
1044
  //#endregion
1279
- //#region src/ui/bubble-menu/underline.tsx
1280
- const BubbleMenuUnderline = createMarkBubbleItem({
1281
- name: "underline",
1282
- activeName: "underline",
1283
- command: "toggleUnderline",
1284
- icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
1285
- });
1045
+ //#region src/ui/bubble-menu/item.tsx
1046
+ function BubbleMenuItem({ name, isActive, onCommand, className, children, ...rest }) {
1047
+ return /* @__PURE__ */ jsx("button", {
1048
+ type: "button",
1049
+ "aria-label": name,
1050
+ "aria-pressed": isActive,
1051
+ className,
1052
+ "data-re-bubble-menu-item": "",
1053
+ "data-item": name,
1054
+ ...isActive ? { "data-active": "" } : {},
1055
+ onMouseDown: (e) => e.preventDefault(),
1056
+ onClick: onCommand,
1057
+ ...rest,
1058
+ children
1059
+ });
1060
+ }
1286
1061
 
1287
1062
  //#endregion
1288
- //#region src/ui/bubble-menu/uppercase.tsx
1289
- const BubbleMenuUppercase = createMarkBubbleItem({
1290
- name: "uppercase",
1291
- activeName: "uppercase",
1292
- command: "toggleUppercase",
1293
- icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
1294
- });
1063
+ //#region src/ui/bubble-menu/align-center.tsx
1064
+ function BubbleMenuAlignCenter({ className, children }) {
1065
+ const { editor } = useBubbleMenuContext();
1066
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1067
+ name: "align-center",
1068
+ isActive: useEditorState({
1069
+ editor,
1070
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
1071
+ }),
1072
+ onCommand: () => setTextAlignment(editor, "center"),
1073
+ className,
1074
+ children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
1075
+ });
1076
+ }
1295
1077
 
1296
1078
  //#endregion
1297
- //#region src/ui/bubble-menu/default.tsx
1298
- function BubbleMenuDefault({ excludeItems = [], excludeNodes, excludeMarks, placement, offset: offset$1, onHide, className, ...rest }) {
1299
- const [isNodeSelectorOpen, setIsNodeSelectorOpen] = React.useState(false);
1300
- const [isLinkSelectorOpen, setIsLinkSelectorOpen] = React.useState(false);
1301
- const has = (item) => !excludeItems.includes(item);
1302
- const handleNodeSelectorOpenChange = React.useCallback((open) => {
1303
- setIsNodeSelectorOpen(open);
1304
- if (open) setIsLinkSelectorOpen(false);
1305
- }, []);
1306
- const handleLinkSelectorOpenChange = React.useCallback((open) => {
1307
- setIsLinkSelectorOpen(open);
1308
- if (open) setIsNodeSelectorOpen(false);
1309
- }, []);
1310
- const handleHide = React.useCallback(() => {
1311
- setIsNodeSelectorOpen(false);
1312
- setIsLinkSelectorOpen(false);
1313
- onHide?.();
1314
- }, [onHide]);
1315
- const hasFormattingItems = has("bold") || has("italic") || has("underline") || has("strike") || has("code") || has("uppercase");
1316
- const hasAlignmentItems = has("align-left") || has("align-center") || has("align-right");
1317
- return /* @__PURE__ */ jsxs(BubbleMenuRoot, {
1318
- excludeNodes,
1319
- excludeMarks,
1320
- placement,
1321
- offset: offset$1,
1322
- onHide: handleHide,
1079
+ //#region src/ui/bubble-menu/align-left.tsx
1080
+ function BubbleMenuAlignLeft({ className, children }) {
1081
+ const { editor } = useBubbleMenuContext();
1082
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1083
+ name: "align-left",
1084
+ isActive: useEditorState({
1085
+ editor,
1086
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
1087
+ }),
1088
+ onCommand: () => setTextAlignment(editor, "left"),
1323
1089
  className,
1324
- ...rest,
1325
- children: [
1326
- has("node-selector") && /* @__PURE__ */ jsx(BubbleMenuNodeSelector, {
1327
- open: isNodeSelectorOpen,
1328
- onOpenChange: handleNodeSelectorOpenChange
1329
- }),
1330
- has("link-selector") && /* @__PURE__ */ jsx(BubbleMenuLinkSelector, {
1331
- open: isLinkSelectorOpen,
1332
- onOpenChange: handleLinkSelectorOpenChange
1333
- }),
1334
- hasFormattingItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
1335
- has("bold") && /* @__PURE__ */ jsx(BubbleMenuBold, {}),
1336
- has("italic") && /* @__PURE__ */ jsx(BubbleMenuItalic, {}),
1337
- has("underline") && /* @__PURE__ */ jsx(BubbleMenuUnderline, {}),
1338
- has("strike") && /* @__PURE__ */ jsx(BubbleMenuStrike, {}),
1339
- has("code") && /* @__PURE__ */ jsx(BubbleMenuCode, {}),
1340
- has("uppercase") && /* @__PURE__ */ jsx(BubbleMenuUppercase, {})
1341
- ] }),
1342
- hasAlignmentItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
1343
- has("align-left") && /* @__PURE__ */ jsx(BubbleMenuAlignLeft, {}),
1344
- has("align-center") && /* @__PURE__ */ jsx(BubbleMenuAlignCenter, {}),
1345
- has("align-right") && /* @__PURE__ */ jsx(BubbleMenuAlignRight, {})
1346
- ] })
1347
- ]
1090
+ children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
1348
1091
  });
1349
1092
  }
1350
1093
 
1351
1094
  //#endregion
1352
- //#region src/ui/button-bubble-menu/context.tsx
1353
- const ButtonBubbleMenuContext = React.createContext(null);
1354
- function useButtonBubbleMenuContext() {
1355
- const context = React.useContext(ButtonBubbleMenuContext);
1356
- if (!context) throw new Error("ButtonBubbleMenu compound components must be used within <ButtonBubbleMenu.Root>");
1357
- return context;
1095
+ //#region src/ui/bubble-menu/align-right.tsx
1096
+ function BubbleMenuAlignRight({ className, children }) {
1097
+ const { editor } = useBubbleMenuContext();
1098
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1099
+ name: "align-right",
1100
+ isActive: useEditorState({
1101
+ editor,
1102
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
1103
+ }),
1104
+ onCommand: () => setTextAlignment(editor, "right"),
1105
+ className,
1106
+ children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
1107
+ });
1358
1108
  }
1359
1109
 
1360
1110
  //#endregion
1361
- //#region src/ui/button-bubble-menu/edit-link.tsx
1362
- function ButtonBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
1363
- const { setIsEditing } = useButtonBubbleMenuContext();
1364
- return /* @__PURE__ */ jsx("button", {
1365
- ...rest,
1366
- type: "button",
1367
- "aria-label": "Edit link",
1368
- "data-re-btn-bm-item": "",
1369
- "data-item": "edit-link",
1111
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
1112
+ function createMarkBubbleItem(config) {
1113
+ function MarkBubbleItem({ className, children }) {
1114
+ const { editor } = useBubbleMenuContext();
1115
+ const isActive = useEditorState({
1116
+ editor,
1117
+ selector: ({ editor: editor$1 }) => {
1118
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
1119
+ return editor$1?.isActive(config.activeName) ?? false;
1120
+ }
1121
+ });
1122
+ const handleCommand = () => {
1123
+ const chain = editor.chain().focus();
1124
+ const method = chain[config.command];
1125
+ if (method) method.call(chain).run();
1126
+ };
1127
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1128
+ name: config.name,
1129
+ isActive,
1130
+ onCommand: handleCommand,
1131
+ className,
1132
+ children: children ?? config.icon
1133
+ });
1134
+ }
1135
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
1136
+ return MarkBubbleItem;
1137
+ }
1138
+
1139
+ //#endregion
1140
+ //#region src/ui/bubble-menu/bold.tsx
1141
+ const BubbleMenuBold = createMarkBubbleItem({
1142
+ name: "bold",
1143
+ activeName: "bold",
1144
+ command: "toggleBold",
1145
+ icon: /* @__PURE__ */ jsx(BoldIcon, {})
1146
+ });
1147
+
1148
+ //#endregion
1149
+ //#region src/ui/bubble-menu/code.tsx
1150
+ const BubbleMenuCode = createMarkBubbleItem({
1151
+ name: "code",
1152
+ activeName: "code",
1153
+ command: "toggleCode",
1154
+ icon: /* @__PURE__ */ jsx(CodeIcon, {})
1155
+ });
1156
+
1157
+ //#endregion
1158
+ //#region src/ui/bubble-menu/group.tsx
1159
+ function BubbleMenuItemGroup({ className, children }) {
1160
+ return /* @__PURE__ */ jsx("fieldset", {
1370
1161
  className,
1371
- onMouseDown: (e) => {
1372
- e.preventDefault();
1373
- onMouseDown?.(e);
1374
- },
1375
- onClick: (e) => {
1376
- onClick?.(e);
1377
- setIsEditing(true);
1378
- },
1379
- children: children ?? /* @__PURE__ */ jsx(PencilIcon, {})
1162
+ "data-re-bubble-menu-group": "",
1163
+ children
1380
1164
  });
1381
1165
  }
1382
1166
 
1383
1167
  //#endregion
1384
- //#region src/ui/button-bubble-menu/form.tsx
1385
- function ButtonBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove }) {
1386
- const { editor, buttonHref, isEditing, setIsEditing } = useButtonBubbleMenuContext();
1168
+ //#region src/ui/bubble-menu/italic.tsx
1169
+ const BubbleMenuItalic = createMarkBubbleItem({
1170
+ name: "italic",
1171
+ activeName: "italic",
1172
+ command: "toggleItalic",
1173
+ icon: /* @__PURE__ */ jsx(ItalicIcon, {})
1174
+ });
1175
+
1176
+ //#endregion
1177
+ //#region src/ui/bubble-menu/link-selector.tsx
1178
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children, open: controlledOpen, onOpenChange }) {
1179
+ const { editor } = useBubbleMenuContext();
1180
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
1181
+ const isControlled = controlledOpen !== void 0;
1182
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1183
+ const setIsOpen = React.useCallback((value) => {
1184
+ if (!isControlled) setUncontrolledOpen(value);
1185
+ onOpenChange?.(value);
1186
+ }, [isControlled, onOpenChange]);
1187
+ const editorState = useEditorState({
1188
+ editor,
1189
+ selector: ({ editor: editor$1 }) => ({
1190
+ isLinkActive: editor$1?.isActive("link") ?? false,
1191
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
1192
+ currentHref: editor$1?.getAttributes("link").href || ""
1193
+ })
1194
+ });
1195
+ const setIsOpenRef = React.useRef(setIsOpen);
1196
+ setIsOpenRef.current = setIsOpen;
1197
+ React.useEffect(() => {
1198
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
1199
+ setIsOpenRef.current(true);
1200
+ });
1201
+ return () => {
1202
+ setIsOpenRef.current(false);
1203
+ subscription.unsubscribe();
1204
+ };
1205
+ }, []);
1206
+ if (!editorState) return null;
1207
+ const handleOpenLink = () => {
1208
+ setIsOpen(!isOpen);
1209
+ };
1210
+ return /* @__PURE__ */ jsxs("div", {
1211
+ "data-re-link-selector": "",
1212
+ ...isOpen ? { "data-open": "" } : {},
1213
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
1214
+ className,
1215
+ children: [showToggle && /* @__PURE__ */ jsx("button", {
1216
+ type: "button",
1217
+ "aria-expanded": isOpen,
1218
+ "aria-haspopup": "true",
1219
+ "aria-label": "Add link",
1220
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
1221
+ "data-re-link-selector-trigger": "",
1222
+ onClick: handleOpenLink,
1223
+ children: /* @__PURE__ */ jsx(LinkIcon, {})
1224
+ }), isOpen && /* @__PURE__ */ jsx(LinkForm, {
1225
+ editor,
1226
+ currentHref: editorState.currentHref,
1227
+ validateUrl,
1228
+ onLinkApply,
1229
+ onLinkRemove,
1230
+ setIsOpen,
1231
+ children
1232
+ })]
1233
+ });
1234
+ }
1235
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
1387
1236
  const inputRef = React.useRef(null);
1388
1237
  const formRef = React.useRef(null);
1389
- const displayHref = buttonHref === "#" ? "" : buttonHref;
1238
+ const displayHref = currentHref === "#" ? "" : currentHref;
1390
1239
  const [inputValue, setInputValue] = React.useState(displayHref);
1391
1240
  React.useEffect(() => {
1392
- if (!isEditing) return;
1393
- setInputValue(displayHref);
1394
1241
  const timeoutId = setTimeout(() => {
1395
1242
  inputRef.current?.focus();
1396
1243
  }, 0);
1397
1244
  return () => clearTimeout(timeoutId);
1398
- }, [isEditing, displayHref]);
1245
+ }, []);
1399
1246
  React.useEffect(() => {
1400
- if (!isEditing) return;
1401
1247
  const handleKeyDown = (event) => {
1402
- if (event.key === "Escape") setIsEditing(false);
1248
+ if (event.key === "Escape") {
1249
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
1250
+ setIsOpen(false);
1251
+ }
1403
1252
  };
1404
1253
  const handleClickOutside = (event) => {
1405
1254
  if (formRef.current && !formRef.current.contains(event.target)) {
@@ -1409,7 +1258,7 @@ function ButtonBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemov
1409
1258
  cancelable: true
1410
1259
  });
1411
1260
  form.dispatchEvent(submitEvent);
1412
- setIsEditing(false);
1261
+ setIsOpen(false);
1413
1262
  }
1414
1263
  };
1415
1264
  document.addEventListener("mousedown", handleClickOutside);
@@ -1418,182 +1267,312 @@ function ButtonBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemov
1418
1267
  window.removeEventListener("keydown", handleKeyDown);
1419
1268
  document.removeEventListener("mousedown", handleClickOutside);
1420
1269
  };
1421
- }, [isEditing, setIsEditing]);
1422
- if (!isEditing) return null;
1270
+ }, [editor, setIsOpen]);
1423
1271
  function handleSubmit(e) {
1424
1272
  e.preventDefault();
1425
1273
  const value = inputValue.trim();
1426
1274
  if (value === "") {
1427
- editor.commands.updateButton({ href: "#" });
1428
- setIsEditing(false);
1275
+ setLinkHref(editor, "");
1276
+ setIsOpen(false);
1429
1277
  focusEditor(editor);
1430
1278
  onLinkRemove?.();
1431
1279
  return;
1432
1280
  }
1433
1281
  const finalValue = (validateUrl ?? getUrlFromString)(value);
1434
1282
  if (!finalValue) {
1435
- editor.commands.updateButton({ href: "#" });
1436
- setIsEditing(false);
1283
+ setLinkHref(editor, "");
1284
+ setIsOpen(false);
1437
1285
  focusEditor(editor);
1438
1286
  onLinkRemove?.();
1439
1287
  return;
1440
1288
  }
1441
- editor.commands.updateButton({ href: finalValue });
1442
- setIsEditing(false);
1289
+ setLinkHref(editor, finalValue);
1290
+ setIsOpen(false);
1443
1291
  focusEditor(editor);
1444
1292
  onLinkApply?.(finalValue);
1445
1293
  }
1446
1294
  function handleUnlink(e) {
1447
1295
  e.stopPropagation();
1448
- editor.commands.updateButton({ href: "#" });
1449
- setIsEditing(false);
1296
+ setLinkHref(editor, "");
1297
+ setIsOpen(false);
1450
1298
  focusEditor(editor);
1451
1299
  onLinkRemove?.();
1452
1300
  }
1453
1301
  return /* @__PURE__ */ jsxs("form", {
1454
1302
  ref: formRef,
1455
- "data-re-btn-bm-form": "",
1456
- className,
1303
+ "data-re-link-selector-form": "",
1457
1304
  onMouseDown: (e) => e.stopPropagation(),
1458
1305
  onClick: (e) => e.stopPropagation(),
1459
1306
  onKeyDown: (e) => e.stopPropagation(),
1460
1307
  onSubmit: handleSubmit,
1461
- children: [/* @__PURE__ */ jsx("input", {
1462
- ref: inputRef,
1463
- "data-re-btn-bm-input": "",
1464
- value: inputValue,
1465
- onFocus: (e) => e.stopPropagation(),
1466
- onChange: (e) => setInputValue(e.target.value),
1467
- placeholder: "Paste a link",
1468
- type: "text"
1469
- }), displayHref ? /* @__PURE__ */ jsx("button", {
1470
- type: "button",
1471
- "aria-label": "Remove link",
1472
- "data-re-btn-bm-unlink": "",
1473
- onClick: handleUnlink,
1474
- children: /* @__PURE__ */ jsx(UnlinkIcon, {})
1475
- }) : /* @__PURE__ */ jsx("button", {
1476
- type: "submit",
1477
- "aria-label": "Apply link",
1478
- "data-re-btn-bm-apply": "",
1479
- onMouseDown: (e) => e.stopPropagation(),
1480
- children: /* @__PURE__ */ jsx(Check, {})
1481
- })]
1308
+ children: [
1309
+ /* @__PURE__ */ jsx("input", {
1310
+ ref: inputRef,
1311
+ "data-re-link-selector-input": "",
1312
+ value: inputValue,
1313
+ onFocus: (e) => e.stopPropagation(),
1314
+ onChange: (e) => setInputValue(e.target.value),
1315
+ placeholder: "Paste a link",
1316
+ type: "text"
1317
+ }),
1318
+ children,
1319
+ displayHref ? /* @__PURE__ */ jsx("button", {
1320
+ type: "button",
1321
+ "aria-label": "Remove link",
1322
+ "data-re-link-selector-unlink": "",
1323
+ onClick: handleUnlink,
1324
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
1325
+ }) : /* @__PURE__ */ jsx("button", {
1326
+ type: "submit",
1327
+ "aria-label": "Apply link",
1328
+ "data-re-link-selector-apply": "",
1329
+ onMouseDown: (e) => e.stopPropagation(),
1330
+ children: /* @__PURE__ */ jsx(Check, {})
1331
+ })
1332
+ ]
1333
+ });
1334
+ }
1335
+
1336
+ //#endregion
1337
+ //#region src/ui/bubble-menu/node-selector.tsx
1338
+ const NodeSelectorContext = React.createContext(null);
1339
+ function useNodeSelectorContext() {
1340
+ const context = React.useContext(NodeSelectorContext);
1341
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
1342
+ return context;
1343
+ }
1344
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
1345
+ const { editor } = useBubbleMenuContext();
1346
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
1347
+ const isControlled = controlledOpen !== void 0;
1348
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1349
+ const setIsOpen = React.useCallback((value) => {
1350
+ if (!isControlled) setUncontrolledOpen(value);
1351
+ onOpenChange?.(value);
1352
+ }, [isControlled, onOpenChange]);
1353
+ const editorState = useEditorState({
1354
+ editor,
1355
+ selector: ({ editor: editor$1 }) => ({
1356
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
1357
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
1358
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
1359
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
1360
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
1361
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
1362
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
1363
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
1364
+ })
1365
+ });
1366
+ const allItems = React.useMemo(() => [
1367
+ {
1368
+ name: "Text",
1369
+ icon: TextIcon,
1370
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
1371
+ isActive: editorState?.isParagraphActive ?? false
1372
+ },
1373
+ {
1374
+ name: "Title",
1375
+ icon: Heading1,
1376
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
1377
+ isActive: editorState?.isHeading1Active ?? false
1378
+ },
1379
+ {
1380
+ name: "Subtitle",
1381
+ icon: Heading2,
1382
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
1383
+ isActive: editorState?.isHeading2Active ?? false
1384
+ },
1385
+ {
1386
+ name: "Heading",
1387
+ icon: Heading3,
1388
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
1389
+ isActive: editorState?.isHeading3Active ?? false
1390
+ },
1391
+ {
1392
+ name: "Bullet List",
1393
+ icon: List,
1394
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
1395
+ isActive: editorState?.isBulletListActive ?? false
1396
+ },
1397
+ {
1398
+ name: "Numbered List",
1399
+ icon: ListOrdered,
1400
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
1401
+ isActive: editorState?.isOrderedListActive ?? false
1402
+ },
1403
+ {
1404
+ name: "Quote",
1405
+ icon: TextQuote,
1406
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
1407
+ isActive: editorState?.isBlockquoteActive ?? false
1408
+ },
1409
+ {
1410
+ name: "Code",
1411
+ icon: Code,
1412
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
1413
+ isActive: editorState?.isCodeBlockActive ?? false
1414
+ }
1415
+ ], [editor, editorState]);
1416
+ const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
1417
+ const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
1418
+ const contextValue = React.useMemo(() => ({
1419
+ items,
1420
+ activeItem,
1421
+ isOpen,
1422
+ setIsOpen
1423
+ }), [
1424
+ items,
1425
+ activeItem,
1426
+ isOpen,
1427
+ setIsOpen
1428
+ ]);
1429
+ if (!editorState || items.length === 0) return null;
1430
+ return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
1431
+ value: contextValue,
1432
+ children: /* @__PURE__ */ jsx(Popover.Root, {
1433
+ open: isOpen,
1434
+ onOpenChange: setIsOpen,
1435
+ children: /* @__PURE__ */ jsx("div", {
1436
+ "data-re-node-selector": "",
1437
+ ...isOpen ? { "data-open": "" } : {},
1438
+ className,
1439
+ children
1440
+ })
1441
+ })
1482
1442
  });
1483
1443
  }
1484
-
1485
- //#endregion
1486
- //#region src/ui/button-bubble-menu/root.tsx
1487
- function ButtonBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children, ...rest }) {
1488
- const { editor } = useCurrentEditor();
1489
- const [isEditing, setIsEditing] = React.useState(false);
1490
- const buttonHref = useEditorState({
1491
- editor,
1492
- selector: ({ editor: e }) => e?.getAttributes("button").href ?? ""
1444
+ function NodeSelectorTrigger({ className, children }) {
1445
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
1446
+ return /* @__PURE__ */ jsx(Popover.Trigger, {
1447
+ "data-re-node-selector-trigger": "",
1448
+ className,
1449
+ onClick: () => setIsOpen(!isOpen),
1450
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
1493
1451
  });
1494
- if (!editor) return null;
1495
- return /* @__PURE__ */ jsx(BubbleMenu, {
1496
- editor,
1497
- pluginKey: "buttonBubbleMenu",
1498
- "data-re-btn-bm": "",
1499
- shouldShow: ({ editor: e, view }) => e.isActive("button") && !view.dom.classList.contains("dragging"),
1500
- options: {
1501
- placement,
1502
- offset: offset$1,
1503
- onHide: () => {
1504
- setIsEditing(false);
1505
- onHide?.();
1506
- }
1507
- },
1452
+ }
1453
+ function NodeSelectorContent({ className, align = "start", children }) {
1454
+ const { items, setIsOpen } = useNodeSelectorContext();
1455
+ return /* @__PURE__ */ jsx(Popover.Content, {
1456
+ align,
1457
+ "data-re-node-selector-content": "",
1508
1458
  className,
1509
- ...rest,
1510
- children: /* @__PURE__ */ jsx(ButtonBubbleMenuContext.Provider, {
1511
- value: {
1512
- editor,
1513
- buttonHref: buttonHref ?? "",
1514
- isEditing,
1515
- setIsEditing
1516
- },
1517
- children
1459
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
1460
+ const Icon = item.icon;
1461
+ return /* @__PURE__ */ jsxs("button", {
1462
+ type: "button",
1463
+ "data-re-node-selector-item": "",
1464
+ ...item.isActive ? { "data-active": "" } : {},
1465
+ onClick: () => {
1466
+ item.command();
1467
+ setIsOpen(false);
1468
+ },
1469
+ children: [
1470
+ /* @__PURE__ */ jsx(Icon, {}),
1471
+ /* @__PURE__ */ jsx("span", { children: item.name }),
1472
+ item.isActive && /* @__PURE__ */ jsx(Check, {})
1473
+ ]
1474
+ }, item.name);
1518
1475
  })
1519
1476
  });
1520
1477
  }
1521
-
1522
- //#endregion
1523
- //#region src/ui/button-bubble-menu/toolbar.tsx
1524
- function ButtonBubbleMenuToolbar({ children, ...rest }) {
1525
- const { isEditing } = useButtonBubbleMenuContext();
1526
- if (isEditing) return null;
1527
- return /* @__PURE__ */ jsx("div", {
1528
- "data-re-btn-bm-toolbar": "",
1529
- ...rest,
1530
- children
1478
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
1479
+ return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
1480
+ omit,
1481
+ open,
1482
+ onOpenChange,
1483
+ className,
1484
+ children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
1531
1485
  });
1532
1486
  }
1533
1487
 
1534
1488
  //#endregion
1535
- //#region src/ui/button-bubble-menu/unlink.tsx
1536
- function ButtonBubbleMenuUnlink({ className, children, onClick, onMouseDown, onLinkRemove, ...rest }) {
1537
- const { editor } = useButtonBubbleMenuContext();
1538
- return /* @__PURE__ */ jsx("button", {
1539
- ...rest,
1540
- type: "button",
1541
- "aria-label": "Remove link",
1542
- "data-re-btn-bm-item": "",
1543
- "data-item": "unlink",
1544
- className,
1545
- onMouseDown: (e) => {
1546
- e.preventDefault();
1547
- onMouseDown?.(e);
1548
- },
1549
- onClick: (e) => {
1550
- onClick?.(e);
1551
- editor.commands.updateButton({ href: "#" });
1552
- focusEditor(editor);
1553
- onLinkRemove?.();
1554
- },
1555
- children: children ?? /* @__PURE__ */ jsx(UnlinkIcon, {})
1556
- });
1557
- }
1489
+ //#region src/ui/bubble-menu/strike.tsx
1490
+ const BubbleMenuStrike = createMarkBubbleItem({
1491
+ name: "strike",
1492
+ activeName: "strike",
1493
+ command: "toggleStrike",
1494
+ icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
1495
+ });
1558
1496
 
1559
1497
  //#endregion
1560
- //#region src/ui/button-bubble-menu/default.tsx
1561
- function ButtonBubbleMenuDefaultInner({ validateUrl, onLinkApply, onLinkRemove }) {
1562
- const { buttonHref } = useButtonBubbleMenuContext();
1563
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(ButtonBubbleMenuToolbar, { children: [/* @__PURE__ */ jsx(ButtonBubbleMenuEditLink, {}), buttonHref !== "" && buttonHref !== "#" && /* @__PURE__ */ jsx(ButtonBubbleMenuUnlink, { onLinkRemove })] }), /* @__PURE__ */ jsx(ButtonBubbleMenuForm, {
1564
- validateUrl,
1565
- onLinkApply,
1566
- onLinkRemove
1567
- })] });
1568
- }
1569
- function ButtonBubbleMenuDefault({ placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove, ...rest }) {
1570
- return /* @__PURE__ */ jsx(ButtonBubbleMenuRoot, {
1498
+ //#region src/ui/bubble-menu/underline.tsx
1499
+ const BubbleMenuUnderline = createMarkBubbleItem({
1500
+ name: "underline",
1501
+ activeName: "underline",
1502
+ command: "toggleUnderline",
1503
+ icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
1504
+ });
1505
+
1506
+ //#endregion
1507
+ //#region src/ui/bubble-menu/uppercase.tsx
1508
+ const BubbleMenuUppercase = createMarkBubbleItem({
1509
+ name: "uppercase",
1510
+ activeName: "uppercase",
1511
+ command: "toggleUppercase",
1512
+ icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
1513
+ });
1514
+
1515
+ //#endregion
1516
+ //#region src/ui/bubble-menu/default.tsx
1517
+ const textPluginKey = new PluginKey("textBubbleMenu");
1518
+ function BubbleMenuDefault({ excludeItems = [], hideWhenActiveNodes, hideWhenActiveMarks, placement, offset: offset$1, onHide, className, ...rest }) {
1519
+ const [isNodeSelectorOpen, setIsNodeSelectorOpen] = React.useState(false);
1520
+ const [isLinkSelectorOpen, setIsLinkSelectorOpen] = React.useState(false);
1521
+ const has = (item) => !excludeItems.includes(item);
1522
+ const handleNodeSelectorOpenChange = React.useCallback((open) => {
1523
+ setIsNodeSelectorOpen(open);
1524
+ if (open) setIsLinkSelectorOpen(false);
1525
+ }, []);
1526
+ const handleLinkSelectorOpenChange = React.useCallback((open) => {
1527
+ setIsLinkSelectorOpen(open);
1528
+ if (open) setIsNodeSelectorOpen(false);
1529
+ }, []);
1530
+ const handleHide = React.useCallback(() => {
1531
+ setIsNodeSelectorOpen(false);
1532
+ setIsLinkSelectorOpen(false);
1533
+ onHide?.();
1534
+ }, [onHide]);
1535
+ const hasFormattingItems = has("bold") || has("italic") || has("underline") || has("strike") || has("code") || has("uppercase");
1536
+ const hasAlignmentItems = has("align-left") || has("align-center") || has("align-right");
1537
+ return /* @__PURE__ */ jsxs(BubbleMenuRoot, {
1538
+ pluginKey: textPluginKey,
1539
+ hideWhenActiveNodes,
1540
+ hideWhenActiveMarks,
1571
1541
  placement,
1572
1542
  offset: offset$1,
1573
- onHide,
1543
+ onHide: handleHide,
1574
1544
  className,
1575
1545
  ...rest,
1576
- children: /* @__PURE__ */ jsx(ButtonBubbleMenuDefaultInner, {
1577
- validateUrl,
1578
- onLinkApply,
1579
- onLinkRemove
1580
- })
1546
+ children: [
1547
+ has("node-selector") && /* @__PURE__ */ jsx(BubbleMenuNodeSelector, {
1548
+ open: isNodeSelectorOpen,
1549
+ onOpenChange: handleNodeSelectorOpenChange
1550
+ }),
1551
+ has("link-selector") && /* @__PURE__ */ jsx(BubbleMenuLinkSelector, {
1552
+ open: isLinkSelectorOpen,
1553
+ onOpenChange: handleLinkSelectorOpenChange
1554
+ }),
1555
+ hasFormattingItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
1556
+ has("bold") && /* @__PURE__ */ jsx(BubbleMenuBold, {}),
1557
+ has("italic") && /* @__PURE__ */ jsx(BubbleMenuItalic, {}),
1558
+ has("underline") && /* @__PURE__ */ jsx(BubbleMenuUnderline, {}),
1559
+ has("strike") && /* @__PURE__ */ jsx(BubbleMenuStrike, {}),
1560
+ has("code") && /* @__PURE__ */ jsx(BubbleMenuCode, {}),
1561
+ has("uppercase") && /* @__PURE__ */ jsx(BubbleMenuUppercase, {})
1562
+ ] }),
1563
+ hasAlignmentItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
1564
+ has("align-left") && /* @__PURE__ */ jsx(BubbleMenuAlignLeft, {}),
1565
+ has("align-center") && /* @__PURE__ */ jsx(BubbleMenuAlignCenter, {}),
1566
+ has("align-right") && /* @__PURE__ */ jsx(BubbleMenuAlignRight, {})
1567
+ ] })
1568
+ ]
1581
1569
  });
1582
1570
  }
1583
1571
 
1584
1572
  //#endregion
1585
- //#region src/ui/image-bubble-menu/context.tsx
1586
- const ImageBubbleMenuContext = React.createContext(null);
1587
- function useImageBubbleMenuContext() {
1588
- const context = React.useContext(ImageBubbleMenuContext);
1589
- if (!context) throw new Error("ImageBubbleMenu compound components must be used within <ImageBubbleMenu.Root>");
1590
- return context;
1591
- }
1592
-
1593
- //#endregion
1594
- //#region src/ui/image-bubble-menu/edit-link.tsx
1595
- function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
1596
- const { setIsEditing } = useImageBubbleMenuContext();
1573
+ //#region src/ui/bubble-menu/image-edit-link.tsx
1574
+ function BubbleMenuImageEditLink({ className, children, onClick, onMouseDown, ...rest }) {
1575
+ const { setIsEditing } = useBubbleMenuContext();
1597
1576
  return /* @__PURE__ */ jsx("button", {
1598
1577
  ...rest,
1599
1578
  type: "button",
@@ -1614,41 +1593,9 @@ function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ..
1614
1593
  }
1615
1594
 
1616
1595
  //#endregion
1617
- //#region src/ui/image-bubble-menu/root.tsx
1618
- function ImageBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children, ...rest }) {
1619
- const { editor } = useCurrentEditor();
1620
- const [isEditing, setIsEditing] = React.useState(false);
1621
- if (!editor) return null;
1622
- return /* @__PURE__ */ jsx(BubbleMenu, {
1623
- editor,
1624
- pluginKey: "imageBubbleMenu",
1625
- "data-re-img-bm": "",
1626
- shouldShow: ({ editor: e, view }) => e.isActive("image") && !view.dom.classList.contains("dragging"),
1627
- options: {
1628
- placement,
1629
- offset: offset$1,
1630
- onHide: () => {
1631
- setIsEditing(false);
1632
- onHide?.();
1633
- }
1634
- },
1635
- className,
1636
- ...rest,
1637
- children: /* @__PURE__ */ jsx(ImageBubbleMenuContext.Provider, {
1638
- value: {
1639
- editor,
1640
- isEditing,
1641
- setIsEditing
1642
- },
1643
- children
1644
- })
1645
- });
1646
- }
1647
-
1648
- //#endregion
1649
- //#region src/ui/image-bubble-menu/toolbar.tsx
1650
- function ImageBubbleMenuToolbar({ children, ...rest }) {
1651
- const { isEditing } = useImageBubbleMenuContext();
1596
+ //#region src/ui/bubble-menu/image-toolbar.tsx
1597
+ function BubbleMenuImageToolbar({ children, ...rest }) {
1598
+ const { isEditing } = useBubbleMenuContext();
1652
1599
  if (isEditing) return null;
1653
1600
  return /* @__PURE__ */ jsx("div", {
1654
1601
  "data-re-img-bm-toolbar": "",
@@ -1658,32 +1605,26 @@ function ImageBubbleMenuToolbar({ children, ...rest }) {
1658
1605
  }
1659
1606
 
1660
1607
  //#endregion
1661
- //#region src/ui/image-bubble-menu/default.tsx
1662
- function ImageBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, ...rest }) {
1608
+ //#region src/ui/bubble-menu/image-default.tsx
1609
+ const imagePluginKey = new PluginKey("imageBubbleMenu");
1610
+ function BubbleMenuImageDefault({ excludeItems = [], placement = "top", offset: offset$1, onHide, className, ...rest }) {
1663
1611
  const hasEditLink = !excludeItems.includes("edit-link");
1664
- return /* @__PURE__ */ jsx(ImageBubbleMenuRoot, {
1612
+ return /* @__PURE__ */ jsx(BubbleMenuRoot, {
1613
+ shouldShow: bubbleMenuTriggers.node("image"),
1614
+ pluginKey: imagePluginKey,
1665
1615
  placement,
1666
1616
  offset: offset$1,
1667
1617
  onHide,
1668
1618
  className,
1669
1619
  ...rest,
1670
- children: hasEditLink && /* @__PURE__ */ jsx(ImageBubbleMenuToolbar, { children: /* @__PURE__ */ jsx(ImageBubbleMenuEditLink, {}) })
1620
+ children: hasEditLink && /* @__PURE__ */ jsx(BubbleMenuImageToolbar, { children: /* @__PURE__ */ jsx(BubbleMenuImageEditLink, {}) })
1671
1621
  });
1672
1622
  }
1673
1623
 
1674
1624
  //#endregion
1675
- //#region src/ui/link-bubble-menu/context.tsx
1676
- const LinkBubbleMenuContext = React.createContext(null);
1677
- function useLinkBubbleMenuContext() {
1678
- const context = React.useContext(LinkBubbleMenuContext);
1679
- if (!context) throw new Error("LinkBubbleMenu compound components must be used within <LinkBubbleMenu.Root>");
1680
- return context;
1681
- }
1682
-
1683
- //#endregion
1684
- //#region src/ui/link-bubble-menu/edit-link.tsx
1685
- function LinkBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
1686
- const { setIsEditing } = useLinkBubbleMenuContext();
1625
+ //#region src/ui/bubble-menu/link-edit-link.tsx
1626
+ function BubbleMenuLinkEditLink({ className, children, onClick, onMouseDown, ...rest }) {
1627
+ const { setIsEditing } = useBubbleMenuContext();
1687
1628
  return /* @__PURE__ */ jsx("button", {
1688
1629
  type: "button",
1689
1630
  "aria-label": "Edit link",
@@ -1704,12 +1645,16 @@ function LinkBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...
1704
1645
  }
1705
1646
 
1706
1647
  //#endregion
1707
- //#region src/ui/link-bubble-menu/form.tsx
1708
- function LinkBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove, children }) {
1709
- const { editor, linkHref, isEditing, setIsEditing } = useLinkBubbleMenuContext();
1648
+ //#region src/ui/bubble-menu/link-form.tsx
1649
+ function BubbleMenuLinkForm({ className, validateUrl, onLinkApply, onLinkRemove, children }) {
1650
+ const { editor, isEditing, setIsEditing } = useBubbleMenuContext();
1710
1651
  const inputRef = React.useRef(null);
1711
1652
  const formRef = React.useRef(null);
1712
- const displayHref = linkHref === "#" ? "" : linkHref;
1653
+ const linkHref = useEditorState({
1654
+ editor,
1655
+ selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
1656
+ });
1657
+ const displayHref = (linkHref ?? "") === "#" ? "" : linkHref ?? "";
1713
1658
  const [inputValue, setInputValue] = React.useState(displayHref);
1714
1659
  React.useEffect(() => {
1715
1660
  if (!isEditing) return;
@@ -1810,12 +1755,16 @@ function LinkBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove,
1810
1755
  }
1811
1756
 
1812
1757
  //#endregion
1813
- //#region src/ui/link-bubble-menu/open-link.tsx
1814
- function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
1815
- const { linkHref } = useLinkBubbleMenuContext();
1758
+ //#region src/ui/bubble-menu/link-open-link.tsx
1759
+ function BubbleMenuLinkOpenLink({ className, children, ...rest }) {
1760
+ const { editor } = useBubbleMenuContext();
1761
+ const linkHref = useEditorState({
1762
+ editor,
1763
+ selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
1764
+ });
1816
1765
  return /* @__PURE__ */ jsx("a", {
1817
1766
  ...rest,
1818
- href: linkHref,
1767
+ href: linkHref ?? "",
1819
1768
  target: "_blank",
1820
1769
  rel: "noopener noreferrer",
1821
1770
  "aria-label": "Open link",
@@ -1827,46 +1776,9 @@ function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
1827
1776
  }
1828
1777
 
1829
1778
  //#endregion
1830
- //#region src/ui/link-bubble-menu/root.tsx
1831
- function LinkBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children, ...rest }) {
1832
- const { editor } = useCurrentEditor();
1833
- const [isEditing, setIsEditing] = React.useState(false);
1834
- const linkHref = useEditorState({
1835
- editor,
1836
- selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
1837
- });
1838
- if (!editor) return null;
1839
- return /* @__PURE__ */ jsx(BubbleMenu, {
1840
- editor,
1841
- pluginKey: "linkBubbleMenu",
1842
- "data-re-link-bm": "",
1843
- shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
1844
- options: {
1845
- placement,
1846
- offset: offset$1,
1847
- onHide: () => {
1848
- setIsEditing(false);
1849
- onHide?.();
1850
- }
1851
- },
1852
- className,
1853
- ...rest,
1854
- children: /* @__PURE__ */ jsx(LinkBubbleMenuContext.Provider, {
1855
- value: {
1856
- editor,
1857
- linkHref: linkHref ?? "",
1858
- isEditing,
1859
- setIsEditing
1860
- },
1861
- children
1862
- })
1863
- });
1864
- }
1865
-
1866
- //#endregion
1867
- //#region src/ui/link-bubble-menu/toolbar.tsx
1868
- function LinkBubbleMenuToolbar({ children, ...rest }) {
1869
- const { isEditing } = useLinkBubbleMenuContext();
1779
+ //#region src/ui/bubble-menu/link-toolbar.tsx
1780
+ function BubbleMenuLinkToolbar({ children, ...rest }) {
1781
+ const { isEditing } = useBubbleMenuContext();
1870
1782
  if (isEditing) return null;
1871
1783
  return /* @__PURE__ */ jsx("div", {
1872
1784
  "data-re-link-bm-toolbar": "",
@@ -1876,9 +1788,9 @@ function LinkBubbleMenuToolbar({ children, ...rest }) {
1876
1788
  }
1877
1789
 
1878
1790
  //#endregion
1879
- //#region src/ui/link-bubble-menu/unlink.tsx
1880
- function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...rest }) {
1881
- const { editor } = useLinkBubbleMenuContext();
1791
+ //#region src/ui/bubble-menu/link-unlink.tsx
1792
+ function BubbleMenuLinkUnlink({ className, children, onClick, onMouseDown, ...rest }) {
1793
+ const { editor } = useBubbleMenuContext();
1882
1794
  return /* @__PURE__ */ jsx("button", {
1883
1795
  type: "button",
1884
1796
  "aria-label": "Remove link",
@@ -1899,21 +1811,24 @@ function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...re
1899
1811
  }
1900
1812
 
1901
1813
  //#endregion
1902
- //#region src/ui/link-bubble-menu/default.tsx
1903
- function LinkBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove, ...rest }) {
1814
+ //#region src/ui/bubble-menu/link-default.tsx
1815
+ const linkPluginKey = new PluginKey("linkBubbleMenu");
1816
+ function BubbleMenuLinkDefault({ excludeItems = [], placement = "top", offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove, ...rest }) {
1904
1817
  const has = (item) => !excludeItems.includes(item);
1905
1818
  const hasToolbarItems = has("edit-link") || has("open-link") || has("unlink");
1906
- return /* @__PURE__ */ jsxs(LinkBubbleMenuRoot, {
1819
+ return /* @__PURE__ */ jsxs(BubbleMenuRoot, {
1820
+ shouldShow: bubbleMenuTriggers.nodeWithoutSelection("link"),
1821
+ pluginKey: linkPluginKey,
1907
1822
  placement,
1908
1823
  offset: offset$1,
1909
1824
  onHide,
1910
1825
  className,
1911
1826
  ...rest,
1912
- children: [hasToolbarItems && /* @__PURE__ */ jsxs(LinkBubbleMenuToolbar, { children: [
1913
- has("edit-link") && /* @__PURE__ */ jsx(LinkBubbleMenuEditLink, {}),
1914
- has("open-link") && /* @__PURE__ */ jsx(LinkBubbleMenuOpenLink, {}),
1915
- has("unlink") && /* @__PURE__ */ jsx(LinkBubbleMenuUnlink, {})
1916
- ] }), /* @__PURE__ */ jsx(LinkBubbleMenuForm, {
1827
+ children: [hasToolbarItems && /* @__PURE__ */ jsxs(BubbleMenuLinkToolbar, { children: [
1828
+ has("edit-link") && /* @__PURE__ */ jsx(BubbleMenuLinkEditLink, {}),
1829
+ has("open-link") && /* @__PURE__ */ jsx(BubbleMenuLinkOpenLink, {}),
1830
+ has("unlink") && /* @__PURE__ */ jsx(BubbleMenuLinkUnlink, {})
1831
+ ] }), /* @__PURE__ */ jsx(BubbleMenuLinkForm, {
1917
1832
  validateUrl,
1918
1833
  onLinkApply,
1919
1834
  onLinkRemove
@@ -2397,5 +2312,5 @@ function SlashCommandRoot({ items: itemsProp, filterItems: filterItemsProp, char
2397
2312
  }
2398
2313
 
2399
2314
  //#endregion
2400
- export { BubbleMenuItemGroup as $, ImageBubbleMenuDefault as A, ChevronDown as At, ButtonBubbleMenuEditLink as B, LinkBubbleMenuUnlink as C, Heading1 as Ct, LinkBubbleMenuForm as D, Columns2 as Dt, LinkBubbleMenuOpenLink as E, Columns3 as Et, ButtonBubbleMenuDefault as F, AlignLeftIcon as Ft, BubbleMenuStrike as G, BubbleMenuDefault as H, ButtonBubbleMenuUnlink as I, AlignCenterIcon as It, NodeSelectorContent as J, BubbleMenuRoot as K, ButtonBubbleMenuToolbar as L, ImageBubbleMenuRoot as M, CaseUpperIcon as Mt, ImageBubbleMenuEditLink as N, BoldIcon as Nt, LinkBubbleMenuEditLink as O, Code as Ot, useImageBubbleMenuContext as P, AlignRightIcon as Pt, BubbleMenuItalic as Q, ButtonBubbleMenuRoot as R, LinkBubbleMenuDefault as S, Heading2 as St, LinkBubbleMenuRoot as T, Columns4 as Tt, BubbleMenuUppercase as U, useButtonBubbleMenuContext as V, BubbleMenuUnderline as W, NodeSelectorTrigger as X, NodeSelectorRoot as Y, BubbleMenuLinkSelector as Z, TWO_COLUMNS as _, ListOrdered as _t, BUTTON as a, BubbleMenuItem as at, isAtMaxColumnsDepth as b, ItalicIcon as bt, FOUR_COLUMNS as c, TextQuote as ct, H3 as d, StrikethroughIcon as dt, BubbleMenuCode as et, NUMBERED_LIST as f, SquareCode as ft, THREE_COLUMNS as g, MousePointer as gt, TEXT as h, PencilIcon as ht, BULLET_LIST as i, BubbleMenuAlignCenter as it, ImageBubbleMenuToolbar as j, Check as jt, useLinkBubbleMenuContext as k, CodeIcon as kt, H1 as l, Text as lt, SECTION as m, Rows2 as mt, filterAndRankItems as n, BubbleMenuAlignRight as nt, CODE as o, UnlinkIcon as ot, QUOTE as p, SplitSquareVertical as pt, BubbleMenuNodeSelector as q, scoreItem as r, BubbleMenuAlignLeft as rt, DIVIDER as s, UnderlineIcon as st, SlashCommandRoot as t, BubbleMenuBold as tt, H2 as u, TextIcon as ut, defaultSlashCommands as v, List as vt, LinkBubbleMenuToolbar as w, ExternalLinkIcon as wt, isInsideNode as x, Heading3 as xt, CommandList as y, LinkIcon as yt, ButtonBubbleMenuForm as z };
2401
- //# sourceMappingURL=root-BMxsq1NF.mjs.map
2315
+ export { BubbleMenuButtonForm as $, BubbleMenuImageEditLink as A, AlignRightIcon as At, BubbleMenuItalic as B, BubbleMenuLinkUnlink as C, Columns2 as Ct, BubbleMenuLinkEditLink as D, Check as Dt, BubbleMenuLinkForm as E, ChevronDown as Et, BubbleMenuNodeSelector as F, BubbleMenuAlignLeft as G, BubbleMenuCode as H, NodeSelectorContent as I, BubbleMenuButtonDefault as J, BubbleMenuAlignCenter as K, NodeSelectorRoot as L, BubbleMenuUppercase as M, AlignCenterIcon as Mt, BubbleMenuUnderline as N, BubbleMenuImageDefault as O, CaseUpperIcon as Ot, BubbleMenuStrike as P, BubbleMenuButtonToolbar as Q, NodeSelectorTrigger as R, BubbleMenuLinkDefault as S, Columns3 as St, BubbleMenuLinkOpenLink as T, CodeIcon as Tt, BubbleMenuBold as U, BubbleMenuItemGroup as V, BubbleMenuAlignRight as W, bubbleMenuTriggers as X, BubbleMenuRoot as Y, BubbleMenuButtonUnlink as Z, TWO_COLUMNS as _, Heading3 as _t, BUTTON as a, Text as at, isAtMaxColumnsDepth as b, ExternalLinkIcon as bt, FOUR_COLUMNS as c, SquareCode as ct, H3 as d, PencilIcon as dt, BubbleMenuButtonEditLink as et, NUMBERED_LIST as f, MousePointer as ft, THREE_COLUMNS as g, ItalicIcon as gt, TEXT as h, LinkIcon as ht, BULLET_LIST as i, TextQuote as it, BubbleMenuDefault as j, AlignLeftIcon as jt, BubbleMenuImageToolbar as k, BoldIcon as kt, H1 as l, SplitSquareVertical as lt, SECTION as m, List as mt, filterAndRankItems as n, UnlinkIcon as nt, CODE as o, TextIcon as ot, QUOTE as p, ListOrdered as pt, BubbleMenuItem as q, scoreItem as r, UnderlineIcon as rt, DIVIDER as s, StrikethroughIcon as st, SlashCommandRoot as t, useBubbleMenuContext as tt, H2 as u, Rows2 as ut, defaultSlashCommands as v, Heading2 as vt, BubbleMenuLinkToolbar as w, Code as wt, isInsideNode as x, Columns4 as xt, CommandList as y, Heading1 as yt, BubbleMenuLinkSelector as z };
2316
+ //# sourceMappingURL=root-CkYaJZpj.mjs.map