@react-email/editor 0.0.0-experimental.6 → 0.0.0-experimental.7

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/dist/index.js CHANGED
@@ -39,6 +39,16 @@ let prismjs = require("prismjs");
39
39
  prismjs = __toESM(prismjs);
40
40
  let __tiptap_extension_placeholder = require("@tiptap/extension-placeholder");
41
41
  __tiptap_extension_placeholder = __toESM(__tiptap_extension_placeholder);
42
+ let __tiptap_react = require("@tiptap/react");
43
+ __tiptap_react = __toESM(__tiptap_react);
44
+ let lucide_react = require("lucide-react");
45
+ lucide_react = __toESM(lucide_react);
46
+ let react = require("react");
47
+ react = __toESM(react);
48
+ let __radix_ui_react_popover = require("@radix-ui/react-popover");
49
+ __radix_ui_react_popover = __toESM(__radix_ui_react_popover);
50
+ let __tiptap_react_menus = require("@tiptap/react/menus");
51
+ __tiptap_react_menus = __toESM(__tiptap_react_menus);
42
52
 
43
53
  //#region src/core/email-node.ts
44
54
  var EmailNode = class EmailNode extends __tiptap_core.Node {
@@ -70,6 +80,48 @@ var EmailNode = class EmailNode extends __tiptap_core.Node {
70
80
  }
71
81
  };
72
82
 
83
+ //#endregion
84
+ //#region src/core/event-bus.ts
85
+ const EVENT_PREFIX = "@react-email/editor:";
86
+ var EditorEventBus = class {
87
+ prefixEventName(eventName) {
88
+ return `${EVENT_PREFIX}${String(eventName)}`;
89
+ }
90
+ dispatch(eventName, payload, options) {
91
+ const target = options?.target ?? window;
92
+ const prefixedEventName = this.prefixEventName(eventName);
93
+ const event = new CustomEvent(prefixedEventName, {
94
+ detail: payload,
95
+ bubbles: false,
96
+ cancelable: false
97
+ });
98
+ target.dispatchEvent(event);
99
+ }
100
+ on(eventName, handler, options) {
101
+ const target = options?.target ?? window;
102
+ const prefixedEventName = this.prefixEventName(eventName);
103
+ const abortController = new AbortController();
104
+ const wrappedHandler = (event) => {
105
+ const customEvent = event;
106
+ const result = handler(customEvent.detail);
107
+ if (result instanceof Promise) result.catch((error) => {
108
+ console.error(`Error in async event handler for ${prefixedEventName}:`, {
109
+ event: customEvent.detail,
110
+ error
111
+ });
112
+ });
113
+ };
114
+ target.addEventListener(prefixedEventName, wrappedHandler, {
115
+ ...options,
116
+ signal: abortController.signal
117
+ });
118
+ return { unsubscribe: () => {
119
+ abortController.abort();
120
+ } };
121
+ }
122
+ };
123
+ const editorEventBus = new EditorEventBus();
124
+
73
125
  //#endregion
74
126
  //#region src/extensions/alignment-attribute.tsx
75
127
  const AlignmentAttribute = __tiptap_core.Extension.create({
@@ -803,6 +855,25 @@ const CodeBlockPrism = EmailNode.from(__tiptap_extension_code_block.default.exte
803
855
  ]
804
856
  ];
805
857
  },
858
+ addKeyboardShortcuts() {
859
+ return {
860
+ ...this.parent?.(),
861
+ "Mod-a": ({ editor }) => {
862
+ const { state } = editor;
863
+ const { selection } = state;
864
+ const { $from } = selection;
865
+ for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
866
+ const blockStart = $from.start(depth);
867
+ const blockEnd = $from.end(depth);
868
+ if (selection.from === blockStart && selection.to === blockEnd) return false;
869
+ const tr = state.tr.setSelection(__tiptap_pm_state.TextSelection.create(state.doc, blockStart, blockEnd));
870
+ editor.view.dispatch(tr);
871
+ return true;
872
+ }
873
+ return false;
874
+ }
875
+ };
876
+ },
806
877
  addProseMirrorPlugins() {
807
878
  return [...this.parent?.() || [], PrismPlugin({
808
879
  name: this.name,
@@ -1583,10 +1654,658 @@ const TableHeader = __tiptap_core.Node.create({
1583
1654
  }
1584
1655
  });
1585
1656
 
1657
+ //#endregion
1658
+ //#region src/extensions/uppercase.ts
1659
+ const Uppercase = __tiptap_core.Mark.create({
1660
+ name: "uppercase",
1661
+ addOptions() {
1662
+ return { HTMLAttributes: {} };
1663
+ },
1664
+ parseHTML() {
1665
+ return [{
1666
+ tag: "span",
1667
+ getAttrs: (node) => {
1668
+ if (node.style.textTransform === "uppercase") return {};
1669
+ return false;
1670
+ }
1671
+ }];
1672
+ },
1673
+ renderHTML({ HTMLAttributes }) {
1674
+ return [
1675
+ "span",
1676
+ (0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1677
+ 0
1678
+ ];
1679
+ },
1680
+ addCommands() {
1681
+ return {
1682
+ setUppercase: () => ({ commands }) => {
1683
+ return commands.setMark(this.name);
1684
+ },
1685
+ toggleUppercase: () => ({ commands }) => {
1686
+ return commands.toggleMark(this.name);
1687
+ },
1688
+ unsetUppercase: () => ({ commands }) => {
1689
+ return commands.unsetMark(this.name);
1690
+ }
1691
+ };
1692
+ }
1693
+ });
1694
+
1695
+ //#endregion
1696
+ //#region src/utils/set-text-alignment.ts
1697
+ function setTextAlignment(editor, alignment) {
1698
+ const { from, to } = editor.state.selection;
1699
+ const tr = editor.state.tr;
1700
+ editor.state.doc.nodesBetween(from, to, (node, pos) => {
1701
+ if (node.isTextblock) {
1702
+ const prop = "align" in node.attrs ? "align" : "alignment";
1703
+ tr.setNodeMarkup(pos, null, {
1704
+ ...node.attrs,
1705
+ [prop]: alignment
1706
+ });
1707
+ }
1708
+ });
1709
+ editor.view.dispatch(tr);
1710
+ }
1711
+
1712
+ //#endregion
1713
+ //#region src/ui/bubble-menu/context.tsx
1714
+ const BubbleMenuContext = react.createContext(null);
1715
+ function useBubbleMenuContext() {
1716
+ const context = react.useContext(BubbleMenuContext);
1717
+ if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
1718
+ return context;
1719
+ }
1720
+
1721
+ //#endregion
1722
+ //#region src/ui/bubble-menu/item.tsx
1723
+ function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
1724
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1725
+ type: "button",
1726
+ "aria-label": name,
1727
+ "aria-pressed": isActive,
1728
+ className,
1729
+ "data-re-bubble-menu-item": "",
1730
+ "data-item": name,
1731
+ ...isActive ? { "data-active": "" } : {},
1732
+ onMouseDown: (e) => e.preventDefault(),
1733
+ onClick: onCommand,
1734
+ ...rest,
1735
+ children
1736
+ });
1737
+ }
1738
+
1739
+ //#endregion
1740
+ //#region src/ui/bubble-menu/align-center.tsx
1741
+ function BubbleMenuAlignCenter({ className, children }) {
1742
+ const { editor } = useBubbleMenuContext();
1743
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1744
+ name: "align-center",
1745
+ isActive: (0, __tiptap_react.useEditorState)({
1746
+ editor,
1747
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
1748
+ }),
1749
+ onCommand: () => setTextAlignment(editor, "center"),
1750
+ className,
1751
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignCenterIcon, {})
1752
+ });
1753
+ }
1754
+
1755
+ //#endregion
1756
+ //#region src/ui/bubble-menu/align-left.tsx
1757
+ function BubbleMenuAlignLeft({ className, children }) {
1758
+ const { editor } = useBubbleMenuContext();
1759
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1760
+ name: "align-left",
1761
+ isActive: (0, __tiptap_react.useEditorState)({
1762
+ editor,
1763
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
1764
+ }),
1765
+ onCommand: () => setTextAlignment(editor, "left"),
1766
+ className,
1767
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignLeftIcon, {})
1768
+ });
1769
+ }
1770
+
1771
+ //#endregion
1772
+ //#region src/ui/bubble-menu/align-right.tsx
1773
+ function BubbleMenuAlignRight({ className, children }) {
1774
+ const { editor } = useBubbleMenuContext();
1775
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1776
+ name: "align-right",
1777
+ isActive: (0, __tiptap_react.useEditorState)({
1778
+ editor,
1779
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
1780
+ }),
1781
+ onCommand: () => setTextAlignment(editor, "right"),
1782
+ className,
1783
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignRightIcon, {})
1784
+ });
1785
+ }
1786
+
1787
+ //#endregion
1788
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
1789
+ function createMarkBubbleItem(config) {
1790
+ function MarkBubbleItem({ className, children }) {
1791
+ const { editor } = useBubbleMenuContext();
1792
+ const isActive = (0, __tiptap_react.useEditorState)({
1793
+ editor,
1794
+ selector: ({ editor: editor$1 }) => {
1795
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
1796
+ return editor$1?.isActive(config.activeName) ?? false;
1797
+ }
1798
+ });
1799
+ const handleCommand = () => {
1800
+ const chain = editor.chain().focus();
1801
+ const method = chain[config.command];
1802
+ if (method) method.call(chain).run();
1803
+ };
1804
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1805
+ name: config.name,
1806
+ isActive,
1807
+ onCommand: handleCommand,
1808
+ className,
1809
+ children: children ?? config.icon
1810
+ });
1811
+ }
1812
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
1813
+ return MarkBubbleItem;
1814
+ }
1815
+
1816
+ //#endregion
1817
+ //#region src/ui/bubble-menu/bold.tsx
1818
+ const BubbleMenuBold = createMarkBubbleItem({
1819
+ name: "bold",
1820
+ activeName: "bold",
1821
+ command: "toggleBold",
1822
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BoldIcon, {})
1823
+ });
1824
+
1825
+ //#endregion
1826
+ //#region src/ui/bubble-menu/code.tsx
1827
+ const BubbleMenuCode = createMarkBubbleItem({
1828
+ name: "code",
1829
+ activeName: "code",
1830
+ command: "toggleCode",
1831
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CodeIcon, {})
1832
+ });
1833
+
1834
+ //#endregion
1835
+ //#region src/ui/bubble-menu/group.tsx
1836
+ function BubbleMenuItemGroup({ className, children }) {
1837
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
1838
+ className,
1839
+ "data-re-bubble-menu-group": "",
1840
+ children
1841
+ });
1842
+ }
1843
+
1844
+ //#endregion
1845
+ //#region src/ui/bubble-menu/italic.tsx
1846
+ const BubbleMenuItalic = createMarkBubbleItem({
1847
+ name: "italic",
1848
+ activeName: "italic",
1849
+ command: "toggleItalic",
1850
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ItalicIcon, {})
1851
+ });
1852
+
1853
+ //#endregion
1854
+ //#region src/ui/bubble-menu/utils.ts
1855
+ const SAFE_PROTOCOLS = new Set([
1856
+ "http:",
1857
+ "https:",
1858
+ "mailto:",
1859
+ "tel:"
1860
+ ]);
1861
+ /**
1862
+ * Basic URL validation and auto-prefixing.
1863
+ * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
1864
+ * Returns the valid URL string or null.
1865
+ */
1866
+ function getUrlFromString(str) {
1867
+ if (str === "#") return str;
1868
+ try {
1869
+ const url = new URL(str);
1870
+ if (SAFE_PROTOCOLS.has(url.protocol)) return str;
1871
+ return null;
1872
+ } catch {}
1873
+ try {
1874
+ if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
1875
+ } catch {}
1876
+ return null;
1877
+ }
1878
+
1879
+ //#endregion
1880
+ //#region src/ui/bubble-menu/link-selector.tsx
1881
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children }) {
1882
+ const { editor } = useBubbleMenuContext();
1883
+ const [isOpen, setIsOpen] = react.useState(false);
1884
+ const editorState = (0, __tiptap_react.useEditorState)({
1885
+ editor,
1886
+ selector: ({ editor: editor$1 }) => ({
1887
+ isLinkActive: editor$1?.isActive("link") ?? false,
1888
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
1889
+ currentHref: editor$1?.getAttributes("link").href || ""
1890
+ })
1891
+ });
1892
+ react.useEffect(() => {
1893
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
1894
+ setIsOpen(true);
1895
+ });
1896
+ return () => {
1897
+ setIsOpen(false);
1898
+ subscription.unsubscribe();
1899
+ };
1900
+ }, []);
1901
+ if (!editorState) return null;
1902
+ const handleOpenLink = () => {
1903
+ setIsOpen(!isOpen);
1904
+ };
1905
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1906
+ "data-re-link-selector": "",
1907
+ ...isOpen ? { "data-open": "" } : {},
1908
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
1909
+ className,
1910
+ children: [showToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1911
+ type: "button",
1912
+ "aria-expanded": isOpen,
1913
+ "aria-haspopup": "true",
1914
+ "aria-label": "Add link",
1915
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
1916
+ "data-re-link-selector-trigger": "",
1917
+ onClick: handleOpenLink,
1918
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
1919
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkForm, {
1920
+ editor,
1921
+ currentHref: editorState.currentHref,
1922
+ validateUrl,
1923
+ onLinkApply,
1924
+ onLinkRemove,
1925
+ setIsOpen,
1926
+ children
1927
+ })]
1928
+ });
1929
+ }
1930
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
1931
+ const inputRef = react.useRef(null);
1932
+ const formRef = react.useRef(null);
1933
+ const displayHref = currentHref === "#" ? "" : currentHref;
1934
+ const [inputValue, setInputValue] = react.useState(displayHref);
1935
+ react.useEffect(() => {
1936
+ const timeoutId = setTimeout(() => {
1937
+ inputRef.current?.focus();
1938
+ }, 0);
1939
+ return () => clearTimeout(timeoutId);
1940
+ }, []);
1941
+ react.useEffect(() => {
1942
+ const handleKeyDown = (event) => {
1943
+ if (event.key === "Escape") {
1944
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
1945
+ setIsOpen(false);
1946
+ }
1947
+ };
1948
+ const handleClickOutside = (event) => {
1949
+ if (formRef.current && !formRef.current.contains(event.target)) {
1950
+ const form = formRef.current;
1951
+ const submitEvent = new Event("submit", {
1952
+ bubbles: true,
1953
+ cancelable: true
1954
+ });
1955
+ form.dispatchEvent(submitEvent);
1956
+ setIsOpen(false);
1957
+ }
1958
+ };
1959
+ document.addEventListener("mousedown", handleClickOutside);
1960
+ window.addEventListener("keydown", handleKeyDown);
1961
+ return () => {
1962
+ window.removeEventListener("keydown", handleKeyDown);
1963
+ document.removeEventListener("mousedown", handleClickOutside);
1964
+ };
1965
+ }, [editor, setIsOpen]);
1966
+ function handleSubmit(e) {
1967
+ e.preventDefault();
1968
+ const value = inputValue.trim();
1969
+ if (value === "") {
1970
+ setLinkHref(editor, "");
1971
+ setIsOpen(false);
1972
+ focusEditor(editor);
1973
+ onLinkRemove?.();
1974
+ return;
1975
+ }
1976
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
1977
+ if (!finalValue) {
1978
+ setLinkHref(editor, "");
1979
+ setIsOpen(false);
1980
+ focusEditor(editor);
1981
+ onLinkRemove?.();
1982
+ return;
1983
+ }
1984
+ setLinkHref(editor, finalValue);
1985
+ setIsOpen(false);
1986
+ focusEditor(editor);
1987
+ onLinkApply?.(finalValue);
1988
+ }
1989
+ function handleUnlink(e) {
1990
+ e.stopPropagation();
1991
+ setLinkHref(editor, "");
1992
+ setIsOpen(false);
1993
+ focusEditor(editor);
1994
+ onLinkRemove?.();
1995
+ }
1996
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1997
+ ref: formRef,
1998
+ "data-re-link-selector-form": "",
1999
+ onMouseDown: (e) => e.stopPropagation(),
2000
+ onClick: (e) => e.stopPropagation(),
2001
+ onKeyDown: (e) => e.stopPropagation(),
2002
+ onSubmit: handleSubmit,
2003
+ children: [
2004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2005
+ ref: inputRef,
2006
+ "data-re-link-selector-input": "",
2007
+ value: inputValue,
2008
+ onFocus: (e) => e.stopPropagation(),
2009
+ onChange: (e) => setInputValue(e.target.value),
2010
+ placeholder: "Paste a link",
2011
+ type: "text"
2012
+ }),
2013
+ children,
2014
+ displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2015
+ type: "button",
2016
+ "aria-label": "Remove link",
2017
+ "data-re-link-selector-unlink": "",
2018
+ onClick: handleUnlink,
2019
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
2020
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2021
+ type: "submit",
2022
+ "aria-label": "Apply link",
2023
+ "data-re-link-selector-apply": "",
2024
+ onMouseDown: (e) => e.stopPropagation(),
2025
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2026
+ })
2027
+ ]
2028
+ });
2029
+ }
2030
+ function setLinkHref(editor, href) {
2031
+ if (href.length === 0) {
2032
+ editor.chain().unsetLink().run();
2033
+ return;
2034
+ }
2035
+ const { from, to } = editor.state.selection;
2036
+ if (from === to) {
2037
+ editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
2038
+ from,
2039
+ to
2040
+ }).run();
2041
+ return;
2042
+ }
2043
+ editor.chain().setLink({ href }).run();
2044
+ }
2045
+ function focusEditor(editor) {
2046
+ setTimeout(() => {
2047
+ editor.commands.focus();
2048
+ }, 0);
2049
+ }
2050
+
2051
+ //#endregion
2052
+ //#region src/ui/bubble-menu/node-selector.tsx
2053
+ const NodeSelectorContext = react.createContext(null);
2054
+ function useNodeSelectorContext() {
2055
+ const context = react.useContext(NodeSelectorContext);
2056
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
2057
+ return context;
2058
+ }
2059
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
2060
+ const { editor } = useBubbleMenuContext();
2061
+ const [uncontrolledOpen, setUncontrolledOpen] = react.useState(false);
2062
+ const isControlled = controlledOpen !== void 0;
2063
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2064
+ const setIsOpen = react.useCallback((value) => {
2065
+ if (!isControlled) setUncontrolledOpen(value);
2066
+ onOpenChange?.(value);
2067
+ }, [isControlled, onOpenChange]);
2068
+ const editorState = (0, __tiptap_react.useEditorState)({
2069
+ editor,
2070
+ selector: ({ editor: editor$1 }) => ({
2071
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
2072
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
2073
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
2074
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
2075
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
2076
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
2077
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
2078
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
2079
+ })
2080
+ });
2081
+ const allItems = react.useMemo(() => [
2082
+ {
2083
+ name: "Text",
2084
+ icon: lucide_react.TextIcon,
2085
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
2086
+ isActive: editorState?.isParagraphActive ?? false
2087
+ },
2088
+ {
2089
+ name: "Title",
2090
+ icon: lucide_react.Heading1,
2091
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
2092
+ isActive: editorState?.isHeading1Active ?? false
2093
+ },
2094
+ {
2095
+ name: "Subtitle",
2096
+ icon: lucide_react.Heading2,
2097
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
2098
+ isActive: editorState?.isHeading2Active ?? false
2099
+ },
2100
+ {
2101
+ name: "Heading",
2102
+ icon: lucide_react.Heading3,
2103
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
2104
+ isActive: editorState?.isHeading3Active ?? false
2105
+ },
2106
+ {
2107
+ name: "Bullet List",
2108
+ icon: lucide_react.List,
2109
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
2110
+ isActive: editorState?.isBulletListActive ?? false
2111
+ },
2112
+ {
2113
+ name: "Numbered List",
2114
+ icon: lucide_react.ListOrdered,
2115
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
2116
+ isActive: editorState?.isOrderedListActive ?? false
2117
+ },
2118
+ {
2119
+ name: "Quote",
2120
+ icon: lucide_react.TextQuote,
2121
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
2122
+ isActive: editorState?.isBlockquoteActive ?? false
2123
+ },
2124
+ {
2125
+ name: "Code",
2126
+ icon: lucide_react.Code,
2127
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2128
+ isActive: editorState?.isCodeBlockActive ?? false
2129
+ }
2130
+ ], [editor, editorState]);
2131
+ const items = react.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
2132
+ const activeItem = react.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
2133
+ const contextValue = react.useMemo(() => ({
2134
+ items,
2135
+ activeItem,
2136
+ isOpen,
2137
+ setIsOpen
2138
+ }), [
2139
+ items,
2140
+ activeItem,
2141
+ isOpen,
2142
+ setIsOpen
2143
+ ]);
2144
+ if (!editorState || items.length === 0) return null;
2145
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContext.Provider, {
2146
+ value: contextValue,
2147
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Root, {
2148
+ open: isOpen,
2149
+ onOpenChange: setIsOpen,
2150
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2151
+ "data-re-node-selector": "",
2152
+ ...isOpen ? { "data-open": "" } : {},
2153
+ className,
2154
+ children
2155
+ })
2156
+ })
2157
+ });
2158
+ }
2159
+ function NodeSelectorTrigger({ className, children }) {
2160
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
2161
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Trigger, {
2162
+ "data-re-node-selector-trigger": "",
2163
+ className,
2164
+ onClick: () => setIsOpen(!isOpen),
2165
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activeItem.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {})] })
2166
+ });
2167
+ }
2168
+ function NodeSelectorContent({ className, align = "start", children }) {
2169
+ const { items, setIsOpen } = useNodeSelectorContext();
2170
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Content, {
2171
+ align,
2172
+ "data-re-node-selector-content": "",
2173
+ className,
2174
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
2175
+ const Icon = item.icon;
2176
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2177
+ type: "button",
2178
+ "data-re-node-selector-item": "",
2179
+ ...item.isActive ? { "data-active": "" } : {},
2180
+ onClick: () => {
2181
+ item.command();
2182
+ setIsOpen(false);
2183
+ },
2184
+ children: [
2185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}),
2186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.name }),
2187
+ item.isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2188
+ ]
2189
+ }, item.name);
2190
+ })
2191
+ });
2192
+ }
2193
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
2194
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(NodeSelectorRoot, {
2195
+ omit,
2196
+ open,
2197
+ onOpenChange,
2198
+ className,
2199
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContent, {})]
2200
+ });
2201
+ }
2202
+
2203
+ //#endregion
2204
+ //#region src/ui/bubble-menu/root.tsx
2205
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
2206
+ const { editor } = (0, __tiptap_react.useCurrentEditor)();
2207
+ if (!editor) return null;
2208
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__tiptap_react_menus.BubbleMenu, {
2209
+ editor,
2210
+ "data-re-bubble-menu": "",
2211
+ shouldShow: ({ editor: editor$1, view }) => {
2212
+ for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
2213
+ if (view.dom.classList.contains("dragging")) return false;
2214
+ return editor$1.view.state.selection.content().size > 0;
2215
+ },
2216
+ options: {
2217
+ placement,
2218
+ offset,
2219
+ onHide
2220
+ },
2221
+ className,
2222
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuContext.Provider, {
2223
+ value: { editor },
2224
+ children
2225
+ })
2226
+ });
2227
+ }
2228
+
2229
+ //#endregion
2230
+ //#region src/ui/bubble-menu/separator.tsx
2231
+ function BubbleMenuSeparator({ className }) {
2232
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
2233
+ className,
2234
+ "data-re-bubble-menu-separator": ""
2235
+ });
2236
+ }
2237
+
2238
+ //#endregion
2239
+ //#region src/ui/bubble-menu/strike.tsx
2240
+ const BubbleMenuStrike = createMarkBubbleItem({
2241
+ name: "strike",
2242
+ activeName: "strike",
2243
+ command: "toggleStrike",
2244
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.StrikethroughIcon, {})
2245
+ });
2246
+
2247
+ //#endregion
2248
+ //#region src/ui/bubble-menu/underline.tsx
2249
+ const BubbleMenuUnderline = createMarkBubbleItem({
2250
+ name: "underline",
2251
+ activeName: "underline",
2252
+ command: "toggleUnderline",
2253
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnderlineIcon, {})
2254
+ });
2255
+
2256
+ //#endregion
2257
+ //#region src/ui/bubble-menu/uppercase.tsx
2258
+ const BubbleMenuUppercase = createMarkBubbleItem({
2259
+ name: "uppercase",
2260
+ activeName: "uppercase",
2261
+ command: "toggleUppercase",
2262
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CaseUpperIcon, {})
2263
+ });
2264
+
2265
+ //#endregion
2266
+ //#region src/ui/bubble-menu/index.ts
2267
+ const BubbleMenu = {
2268
+ Root: BubbleMenuRoot,
2269
+ ItemGroup: BubbleMenuItemGroup,
2270
+ Separator: BubbleMenuSeparator,
2271
+ Item: BubbleMenuItem,
2272
+ Bold: BubbleMenuBold,
2273
+ Italic: BubbleMenuItalic,
2274
+ Underline: BubbleMenuUnderline,
2275
+ Strike: BubbleMenuStrike,
2276
+ Code: BubbleMenuCode,
2277
+ Uppercase: BubbleMenuUppercase,
2278
+ AlignLeft: BubbleMenuAlignLeft,
2279
+ AlignCenter: BubbleMenuAlignCenter,
2280
+ AlignRight: BubbleMenuAlignRight,
2281
+ NodeSelector: Object.assign(BubbleMenuNodeSelector, {
2282
+ Root: NodeSelectorRoot,
2283
+ Trigger: NodeSelectorTrigger,
2284
+ Content: NodeSelectorContent
2285
+ }),
2286
+ LinkSelector: BubbleMenuLinkSelector
2287
+ };
2288
+
1586
2289
  //#endregion
1587
2290
  exports.AlignmentAttribute = AlignmentAttribute;
1588
2291
  exports.Body = Body;
1589
2292
  exports.Bold = Bold;
2293
+ exports.BubbleMenu = BubbleMenu;
2294
+ exports.BubbleMenuAlignCenter = BubbleMenuAlignCenter;
2295
+ exports.BubbleMenuAlignLeft = BubbleMenuAlignLeft;
2296
+ exports.BubbleMenuAlignRight = BubbleMenuAlignRight;
2297
+ exports.BubbleMenuBold = BubbleMenuBold;
2298
+ exports.BubbleMenuCode = BubbleMenuCode;
2299
+ exports.BubbleMenuItalic = BubbleMenuItalic;
2300
+ exports.BubbleMenuItem = BubbleMenuItem;
2301
+ exports.BubbleMenuItemGroup = BubbleMenuItemGroup;
2302
+ exports.BubbleMenuLinkSelector = BubbleMenuLinkSelector;
2303
+ exports.BubbleMenuNodeSelector = BubbleMenuNodeSelector;
2304
+ exports.BubbleMenuRoot = BubbleMenuRoot;
2305
+ exports.BubbleMenuSeparator = BubbleMenuSeparator;
2306
+ exports.BubbleMenuStrike = BubbleMenuStrike;
2307
+ exports.BubbleMenuUnderline = BubbleMenuUnderline;
2308
+ exports.BubbleMenuUppercase = BubbleMenuUppercase;
1590
2309
  exports.Button = Button;
1591
2310
  exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
1592
2311
  exports.ClassAttribute = ClassAttribute;
@@ -1597,6 +2316,9 @@ exports.EmailNode = EmailNode;
1597
2316
  exports.FourColumns = FourColumns;
1598
2317
  exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
1599
2318
  exports.MaxNesting = MaxNesting;
2319
+ exports.NodeSelectorContent = NodeSelectorContent;
2320
+ exports.NodeSelectorRoot = NodeSelectorRoot;
2321
+ exports.NodeSelectorTrigger = NodeSelectorTrigger;
1600
2322
  exports.Placeholder = Placeholder;
1601
2323
  exports.PreservedStyle = PreservedStyle;
1602
2324
  exports.PreviewText = PreviewText;
@@ -1609,5 +2331,8 @@ exports.TableHeader = TableHeader;
1609
2331
  exports.TableRow = TableRow;
1610
2332
  exports.ThreeColumns = ThreeColumns;
1611
2333
  exports.TwoColumns = TwoColumns;
2334
+ exports.Uppercase = Uppercase;
2335
+ exports.editorEventBus = editorEventBus;
1612
2336
  exports.getColumnsDepth = getColumnsDepth;
1613
- exports.processStylesForUnlink = processStylesForUnlink;
2337
+ exports.processStylesForUnlink = processStylesForUnlink;
2338
+ exports.setTextAlignment = setTextAlignment;