@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as ReactEmailComponents from "@react-email/components";
4
4
  import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
5
5
  import CodeBlock$1 from "@tiptap/extension-code-block";
@@ -8,6 +8,11 @@ import { Decoration, DecorationSet } from "@tiptap/pm/view";
8
8
  import { fromHtml } from "hast-util-from-html";
9
9
  import Prism from "prismjs";
10
10
  import TipTapPlaceholder from "@tiptap/extension-placeholder";
11
+ import { useCurrentEditor, useEditorState } from "@tiptap/react";
12
+ import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code, CodeIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, StrikethroughIcon, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
13
+ import * as React from "react";
14
+ import * as Popover from "@radix-ui/react-popover";
15
+ import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
11
16
 
12
17
  //#region src/core/email-node.ts
13
18
  var EmailNode = class EmailNode extends Node {
@@ -39,6 +44,48 @@ var EmailNode = class EmailNode extends Node {
39
44
  }
40
45
  };
41
46
 
47
+ //#endregion
48
+ //#region src/core/event-bus.ts
49
+ const EVENT_PREFIX = "@react-email/editor:";
50
+ var EditorEventBus = class {
51
+ prefixEventName(eventName) {
52
+ return `${EVENT_PREFIX}${String(eventName)}`;
53
+ }
54
+ dispatch(eventName, payload, options) {
55
+ const target = options?.target ?? window;
56
+ const prefixedEventName = this.prefixEventName(eventName);
57
+ const event = new CustomEvent(prefixedEventName, {
58
+ detail: payload,
59
+ bubbles: false,
60
+ cancelable: false
61
+ });
62
+ target.dispatchEvent(event);
63
+ }
64
+ on(eventName, handler, options) {
65
+ const target = options?.target ?? window;
66
+ const prefixedEventName = this.prefixEventName(eventName);
67
+ const abortController = new AbortController();
68
+ const wrappedHandler = (event) => {
69
+ const customEvent = event;
70
+ const result = handler(customEvent.detail);
71
+ if (result instanceof Promise) result.catch((error) => {
72
+ console.error(`Error in async event handler for ${prefixedEventName}:`, {
73
+ event: customEvent.detail,
74
+ error
75
+ });
76
+ });
77
+ };
78
+ target.addEventListener(prefixedEventName, wrappedHandler, {
79
+ ...options,
80
+ signal: abortController.signal
81
+ });
82
+ return { unsubscribe: () => {
83
+ abortController.abort();
84
+ } };
85
+ }
86
+ };
87
+ const editorEventBus = new EditorEventBus();
88
+
42
89
  //#endregion
43
90
  //#region src/extensions/alignment-attribute.tsx
44
91
  const AlignmentAttribute = Extension.create({
@@ -772,6 +819,25 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
772
819
  ]
773
820
  ];
774
821
  },
822
+ addKeyboardShortcuts() {
823
+ return {
824
+ ...this.parent?.(),
825
+ "Mod-a": ({ editor }) => {
826
+ const { state } = editor;
827
+ const { selection } = state;
828
+ const { $from } = selection;
829
+ for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
830
+ const blockStart = $from.start(depth);
831
+ const blockEnd = $from.end(depth);
832
+ if (selection.from === blockStart && selection.to === blockEnd) return false;
833
+ const tr = state.tr.setSelection(TextSelection.create(state.doc, blockStart, blockEnd));
834
+ editor.view.dispatch(tr);
835
+ return true;
836
+ }
837
+ return false;
838
+ }
839
+ };
840
+ },
775
841
  addProseMirrorPlugins() {
776
842
  return [...this.parent?.() || [], PrismPlugin({
777
843
  name: this.name,
@@ -1553,5 +1619,637 @@ const TableHeader = Node.create({
1553
1619
  });
1554
1620
 
1555
1621
  //#endregion
1556
- export { AlignmentAttribute, Body, Bold, Button, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, Div, EmailNode, FourColumns, MAX_COLUMNS_DEPTH, MaxNesting, Placeholder, PreservedStyle, PreviewText, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, getColumnsDepth, processStylesForUnlink };
1622
+ //#region src/extensions/uppercase.ts
1623
+ const Uppercase = Mark.create({
1624
+ name: "uppercase",
1625
+ addOptions() {
1626
+ return { HTMLAttributes: {} };
1627
+ },
1628
+ parseHTML() {
1629
+ return [{
1630
+ tag: "span",
1631
+ getAttrs: (node) => {
1632
+ if (node.style.textTransform === "uppercase") return {};
1633
+ return false;
1634
+ }
1635
+ }];
1636
+ },
1637
+ renderHTML({ HTMLAttributes }) {
1638
+ return [
1639
+ "span",
1640
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1641
+ 0
1642
+ ];
1643
+ },
1644
+ addCommands() {
1645
+ return {
1646
+ setUppercase: () => ({ commands }) => {
1647
+ return commands.setMark(this.name);
1648
+ },
1649
+ toggleUppercase: () => ({ commands }) => {
1650
+ return commands.toggleMark(this.name);
1651
+ },
1652
+ unsetUppercase: () => ({ commands }) => {
1653
+ return commands.unsetMark(this.name);
1654
+ }
1655
+ };
1656
+ }
1657
+ });
1658
+
1659
+ //#endregion
1660
+ //#region src/utils/set-text-alignment.ts
1661
+ function setTextAlignment(editor, alignment) {
1662
+ const { from, to } = editor.state.selection;
1663
+ const tr = editor.state.tr;
1664
+ editor.state.doc.nodesBetween(from, to, (node, pos) => {
1665
+ if (node.isTextblock) {
1666
+ const prop = "align" in node.attrs ? "align" : "alignment";
1667
+ tr.setNodeMarkup(pos, null, {
1668
+ ...node.attrs,
1669
+ [prop]: alignment
1670
+ });
1671
+ }
1672
+ });
1673
+ editor.view.dispatch(tr);
1674
+ }
1675
+
1676
+ //#endregion
1677
+ //#region src/ui/bubble-menu/context.tsx
1678
+ const BubbleMenuContext = React.createContext(null);
1679
+ function useBubbleMenuContext() {
1680
+ const context = React.useContext(BubbleMenuContext);
1681
+ if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
1682
+ return context;
1683
+ }
1684
+
1685
+ //#endregion
1686
+ //#region src/ui/bubble-menu/item.tsx
1687
+ function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
1688
+ return /* @__PURE__ */ jsx("button", {
1689
+ type: "button",
1690
+ "aria-label": name,
1691
+ "aria-pressed": isActive,
1692
+ className,
1693
+ "data-re-bubble-menu-item": "",
1694
+ "data-item": name,
1695
+ ...isActive ? { "data-active": "" } : {},
1696
+ onMouseDown: (e) => e.preventDefault(),
1697
+ onClick: onCommand,
1698
+ ...rest,
1699
+ children
1700
+ });
1701
+ }
1702
+
1703
+ //#endregion
1704
+ //#region src/ui/bubble-menu/align-center.tsx
1705
+ function BubbleMenuAlignCenter({ className, children }) {
1706
+ const { editor } = useBubbleMenuContext();
1707
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1708
+ name: "align-center",
1709
+ isActive: useEditorState({
1710
+ editor,
1711
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
1712
+ }),
1713
+ onCommand: () => setTextAlignment(editor, "center"),
1714
+ className,
1715
+ children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
1716
+ });
1717
+ }
1718
+
1719
+ //#endregion
1720
+ //#region src/ui/bubble-menu/align-left.tsx
1721
+ function BubbleMenuAlignLeft({ className, children }) {
1722
+ const { editor } = useBubbleMenuContext();
1723
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1724
+ name: "align-left",
1725
+ isActive: useEditorState({
1726
+ editor,
1727
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
1728
+ }),
1729
+ onCommand: () => setTextAlignment(editor, "left"),
1730
+ className,
1731
+ children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
1732
+ });
1733
+ }
1734
+
1735
+ //#endregion
1736
+ //#region src/ui/bubble-menu/align-right.tsx
1737
+ function BubbleMenuAlignRight({ className, children }) {
1738
+ const { editor } = useBubbleMenuContext();
1739
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1740
+ name: "align-right",
1741
+ isActive: useEditorState({
1742
+ editor,
1743
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
1744
+ }),
1745
+ onCommand: () => setTextAlignment(editor, "right"),
1746
+ className,
1747
+ children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
1748
+ });
1749
+ }
1750
+
1751
+ //#endregion
1752
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
1753
+ function createMarkBubbleItem(config) {
1754
+ function MarkBubbleItem({ className, children }) {
1755
+ const { editor } = useBubbleMenuContext();
1756
+ const isActive = useEditorState({
1757
+ editor,
1758
+ selector: ({ editor: editor$1 }) => {
1759
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
1760
+ return editor$1?.isActive(config.activeName) ?? false;
1761
+ }
1762
+ });
1763
+ const handleCommand = () => {
1764
+ const chain = editor.chain().focus();
1765
+ const method = chain[config.command];
1766
+ if (method) method.call(chain).run();
1767
+ };
1768
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1769
+ name: config.name,
1770
+ isActive,
1771
+ onCommand: handleCommand,
1772
+ className,
1773
+ children: children ?? config.icon
1774
+ });
1775
+ }
1776
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
1777
+ return MarkBubbleItem;
1778
+ }
1779
+
1780
+ //#endregion
1781
+ //#region src/ui/bubble-menu/bold.tsx
1782
+ const BubbleMenuBold = createMarkBubbleItem({
1783
+ name: "bold",
1784
+ activeName: "bold",
1785
+ command: "toggleBold",
1786
+ icon: /* @__PURE__ */ jsx(BoldIcon, {})
1787
+ });
1788
+
1789
+ //#endregion
1790
+ //#region src/ui/bubble-menu/code.tsx
1791
+ const BubbleMenuCode = createMarkBubbleItem({
1792
+ name: "code",
1793
+ activeName: "code",
1794
+ command: "toggleCode",
1795
+ icon: /* @__PURE__ */ jsx(CodeIcon, {})
1796
+ });
1797
+
1798
+ //#endregion
1799
+ //#region src/ui/bubble-menu/group.tsx
1800
+ function BubbleMenuItemGroup({ className, children }) {
1801
+ return /* @__PURE__ */ jsx("fieldset", {
1802
+ className,
1803
+ "data-re-bubble-menu-group": "",
1804
+ children
1805
+ });
1806
+ }
1807
+
1808
+ //#endregion
1809
+ //#region src/ui/bubble-menu/italic.tsx
1810
+ const BubbleMenuItalic = createMarkBubbleItem({
1811
+ name: "italic",
1812
+ activeName: "italic",
1813
+ command: "toggleItalic",
1814
+ icon: /* @__PURE__ */ jsx(ItalicIcon, {})
1815
+ });
1816
+
1817
+ //#endregion
1818
+ //#region src/ui/bubble-menu/utils.ts
1819
+ const SAFE_PROTOCOLS = new Set([
1820
+ "http:",
1821
+ "https:",
1822
+ "mailto:",
1823
+ "tel:"
1824
+ ]);
1825
+ /**
1826
+ * Basic URL validation and auto-prefixing.
1827
+ * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
1828
+ * Returns the valid URL string or null.
1829
+ */
1830
+ function getUrlFromString(str) {
1831
+ if (str === "#") return str;
1832
+ try {
1833
+ const url = new URL(str);
1834
+ if (SAFE_PROTOCOLS.has(url.protocol)) return str;
1835
+ return null;
1836
+ } catch {}
1837
+ try {
1838
+ if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
1839
+ } catch {}
1840
+ return null;
1841
+ }
1842
+
1843
+ //#endregion
1844
+ //#region src/ui/bubble-menu/link-selector.tsx
1845
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children }) {
1846
+ const { editor } = useBubbleMenuContext();
1847
+ const [isOpen, setIsOpen] = React.useState(false);
1848
+ const editorState = useEditorState({
1849
+ editor,
1850
+ selector: ({ editor: editor$1 }) => ({
1851
+ isLinkActive: editor$1?.isActive("link") ?? false,
1852
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
1853
+ currentHref: editor$1?.getAttributes("link").href || ""
1854
+ })
1855
+ });
1856
+ React.useEffect(() => {
1857
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
1858
+ setIsOpen(true);
1859
+ });
1860
+ return () => {
1861
+ setIsOpen(false);
1862
+ subscription.unsubscribe();
1863
+ };
1864
+ }, []);
1865
+ if (!editorState) return null;
1866
+ const handleOpenLink = () => {
1867
+ setIsOpen(!isOpen);
1868
+ };
1869
+ return /* @__PURE__ */ jsxs("div", {
1870
+ "data-re-link-selector": "",
1871
+ ...isOpen ? { "data-open": "" } : {},
1872
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
1873
+ className,
1874
+ children: [showToggle && /* @__PURE__ */ jsx("button", {
1875
+ type: "button",
1876
+ "aria-expanded": isOpen,
1877
+ "aria-haspopup": "true",
1878
+ "aria-label": "Add link",
1879
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
1880
+ "data-re-link-selector-trigger": "",
1881
+ onClick: handleOpenLink,
1882
+ children: /* @__PURE__ */ jsx(LinkIcon, {})
1883
+ }), isOpen && /* @__PURE__ */ jsx(LinkForm, {
1884
+ editor,
1885
+ currentHref: editorState.currentHref,
1886
+ validateUrl,
1887
+ onLinkApply,
1888
+ onLinkRemove,
1889
+ setIsOpen,
1890
+ children
1891
+ })]
1892
+ });
1893
+ }
1894
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
1895
+ const inputRef = React.useRef(null);
1896
+ const formRef = React.useRef(null);
1897
+ const displayHref = currentHref === "#" ? "" : currentHref;
1898
+ const [inputValue, setInputValue] = React.useState(displayHref);
1899
+ React.useEffect(() => {
1900
+ const timeoutId = setTimeout(() => {
1901
+ inputRef.current?.focus();
1902
+ }, 0);
1903
+ return () => clearTimeout(timeoutId);
1904
+ }, []);
1905
+ React.useEffect(() => {
1906
+ const handleKeyDown = (event) => {
1907
+ if (event.key === "Escape") {
1908
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
1909
+ setIsOpen(false);
1910
+ }
1911
+ };
1912
+ const handleClickOutside = (event) => {
1913
+ if (formRef.current && !formRef.current.contains(event.target)) {
1914
+ const form = formRef.current;
1915
+ const submitEvent = new Event("submit", {
1916
+ bubbles: true,
1917
+ cancelable: true
1918
+ });
1919
+ form.dispatchEvent(submitEvent);
1920
+ setIsOpen(false);
1921
+ }
1922
+ };
1923
+ document.addEventListener("mousedown", handleClickOutside);
1924
+ window.addEventListener("keydown", handleKeyDown);
1925
+ return () => {
1926
+ window.removeEventListener("keydown", handleKeyDown);
1927
+ document.removeEventListener("mousedown", handleClickOutside);
1928
+ };
1929
+ }, [editor, setIsOpen]);
1930
+ function handleSubmit(e) {
1931
+ e.preventDefault();
1932
+ const value = inputValue.trim();
1933
+ if (value === "") {
1934
+ setLinkHref(editor, "");
1935
+ setIsOpen(false);
1936
+ focusEditor(editor);
1937
+ onLinkRemove?.();
1938
+ return;
1939
+ }
1940
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
1941
+ if (!finalValue) {
1942
+ setLinkHref(editor, "");
1943
+ setIsOpen(false);
1944
+ focusEditor(editor);
1945
+ onLinkRemove?.();
1946
+ return;
1947
+ }
1948
+ setLinkHref(editor, finalValue);
1949
+ setIsOpen(false);
1950
+ focusEditor(editor);
1951
+ onLinkApply?.(finalValue);
1952
+ }
1953
+ function handleUnlink(e) {
1954
+ e.stopPropagation();
1955
+ setLinkHref(editor, "");
1956
+ setIsOpen(false);
1957
+ focusEditor(editor);
1958
+ onLinkRemove?.();
1959
+ }
1960
+ return /* @__PURE__ */ jsxs("form", {
1961
+ ref: formRef,
1962
+ "data-re-link-selector-form": "",
1963
+ onMouseDown: (e) => e.stopPropagation(),
1964
+ onClick: (e) => e.stopPropagation(),
1965
+ onKeyDown: (e) => e.stopPropagation(),
1966
+ onSubmit: handleSubmit,
1967
+ children: [
1968
+ /* @__PURE__ */ jsx("input", {
1969
+ ref: inputRef,
1970
+ "data-re-link-selector-input": "",
1971
+ value: inputValue,
1972
+ onFocus: (e) => e.stopPropagation(),
1973
+ onChange: (e) => setInputValue(e.target.value),
1974
+ placeholder: "Paste a link",
1975
+ type: "text"
1976
+ }),
1977
+ children,
1978
+ displayHref ? /* @__PURE__ */ jsx("button", {
1979
+ type: "button",
1980
+ "aria-label": "Remove link",
1981
+ "data-re-link-selector-unlink": "",
1982
+ onClick: handleUnlink,
1983
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
1984
+ }) : /* @__PURE__ */ jsx("button", {
1985
+ type: "submit",
1986
+ "aria-label": "Apply link",
1987
+ "data-re-link-selector-apply": "",
1988
+ onMouseDown: (e) => e.stopPropagation(),
1989
+ children: /* @__PURE__ */ jsx(Check, {})
1990
+ })
1991
+ ]
1992
+ });
1993
+ }
1994
+ function setLinkHref(editor, href) {
1995
+ if (href.length === 0) {
1996
+ editor.chain().unsetLink().run();
1997
+ return;
1998
+ }
1999
+ const { from, to } = editor.state.selection;
2000
+ if (from === to) {
2001
+ editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
2002
+ from,
2003
+ to
2004
+ }).run();
2005
+ return;
2006
+ }
2007
+ editor.chain().setLink({ href }).run();
2008
+ }
2009
+ function focusEditor(editor) {
2010
+ setTimeout(() => {
2011
+ editor.commands.focus();
2012
+ }, 0);
2013
+ }
2014
+
2015
+ //#endregion
2016
+ //#region src/ui/bubble-menu/node-selector.tsx
2017
+ const NodeSelectorContext = React.createContext(null);
2018
+ function useNodeSelectorContext() {
2019
+ const context = React.useContext(NodeSelectorContext);
2020
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
2021
+ return context;
2022
+ }
2023
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
2024
+ const { editor } = useBubbleMenuContext();
2025
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
2026
+ const isControlled = controlledOpen !== void 0;
2027
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2028
+ const setIsOpen = React.useCallback((value) => {
2029
+ if (!isControlled) setUncontrolledOpen(value);
2030
+ onOpenChange?.(value);
2031
+ }, [isControlled, onOpenChange]);
2032
+ const editorState = useEditorState({
2033
+ editor,
2034
+ selector: ({ editor: editor$1 }) => ({
2035
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
2036
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
2037
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
2038
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
2039
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
2040
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
2041
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
2042
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
2043
+ })
2044
+ });
2045
+ const allItems = React.useMemo(() => [
2046
+ {
2047
+ name: "Text",
2048
+ icon: TextIcon,
2049
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
2050
+ isActive: editorState?.isParagraphActive ?? false
2051
+ },
2052
+ {
2053
+ name: "Title",
2054
+ icon: Heading1,
2055
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
2056
+ isActive: editorState?.isHeading1Active ?? false
2057
+ },
2058
+ {
2059
+ name: "Subtitle",
2060
+ icon: Heading2,
2061
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
2062
+ isActive: editorState?.isHeading2Active ?? false
2063
+ },
2064
+ {
2065
+ name: "Heading",
2066
+ icon: Heading3,
2067
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
2068
+ isActive: editorState?.isHeading3Active ?? false
2069
+ },
2070
+ {
2071
+ name: "Bullet List",
2072
+ icon: List,
2073
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
2074
+ isActive: editorState?.isBulletListActive ?? false
2075
+ },
2076
+ {
2077
+ name: "Numbered List",
2078
+ icon: ListOrdered,
2079
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
2080
+ isActive: editorState?.isOrderedListActive ?? false
2081
+ },
2082
+ {
2083
+ name: "Quote",
2084
+ icon: TextQuote,
2085
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
2086
+ isActive: editorState?.isBlockquoteActive ?? false
2087
+ },
2088
+ {
2089
+ name: "Code",
2090
+ icon: Code,
2091
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2092
+ isActive: editorState?.isCodeBlockActive ?? false
2093
+ }
2094
+ ], [editor, editorState]);
2095
+ const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
2096
+ const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
2097
+ const contextValue = React.useMemo(() => ({
2098
+ items,
2099
+ activeItem,
2100
+ isOpen,
2101
+ setIsOpen
2102
+ }), [
2103
+ items,
2104
+ activeItem,
2105
+ isOpen,
2106
+ setIsOpen
2107
+ ]);
2108
+ if (!editorState || items.length === 0) return null;
2109
+ return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
2110
+ value: contextValue,
2111
+ children: /* @__PURE__ */ jsx(Popover.Root, {
2112
+ open: isOpen,
2113
+ onOpenChange: setIsOpen,
2114
+ children: /* @__PURE__ */ jsx("div", {
2115
+ "data-re-node-selector": "",
2116
+ ...isOpen ? { "data-open": "" } : {},
2117
+ className,
2118
+ children
2119
+ })
2120
+ })
2121
+ });
2122
+ }
2123
+ function NodeSelectorTrigger({ className, children }) {
2124
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
2125
+ return /* @__PURE__ */ jsx(Popover.Trigger, {
2126
+ "data-re-node-selector-trigger": "",
2127
+ className,
2128
+ onClick: () => setIsOpen(!isOpen),
2129
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
2130
+ });
2131
+ }
2132
+ function NodeSelectorContent({ className, align = "start", children }) {
2133
+ const { items, setIsOpen } = useNodeSelectorContext();
2134
+ return /* @__PURE__ */ jsx(Popover.Content, {
2135
+ align,
2136
+ "data-re-node-selector-content": "",
2137
+ className,
2138
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
2139
+ const Icon = item.icon;
2140
+ return /* @__PURE__ */ jsxs("button", {
2141
+ type: "button",
2142
+ "data-re-node-selector-item": "",
2143
+ ...item.isActive ? { "data-active": "" } : {},
2144
+ onClick: () => {
2145
+ item.command();
2146
+ setIsOpen(false);
2147
+ },
2148
+ children: [
2149
+ /* @__PURE__ */ jsx(Icon, {}),
2150
+ /* @__PURE__ */ jsx("span", { children: item.name }),
2151
+ item.isActive && /* @__PURE__ */ jsx(Check, {})
2152
+ ]
2153
+ }, item.name);
2154
+ })
2155
+ });
2156
+ }
2157
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
2158
+ return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
2159
+ omit,
2160
+ open,
2161
+ onOpenChange,
2162
+ className,
2163
+ children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
2164
+ });
2165
+ }
2166
+
2167
+ //#endregion
2168
+ //#region src/ui/bubble-menu/root.tsx
2169
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
2170
+ const { editor } = useCurrentEditor();
2171
+ if (!editor) return null;
2172
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
2173
+ editor,
2174
+ "data-re-bubble-menu": "",
2175
+ shouldShow: ({ editor: editor$1, view }) => {
2176
+ for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
2177
+ if (view.dom.classList.contains("dragging")) return false;
2178
+ return editor$1.view.state.selection.content().size > 0;
2179
+ },
2180
+ options: {
2181
+ placement,
2182
+ offset,
2183
+ onHide
2184
+ },
2185
+ className,
2186
+ children: /* @__PURE__ */ jsx(BubbleMenuContext.Provider, {
2187
+ value: { editor },
2188
+ children
2189
+ })
2190
+ });
2191
+ }
2192
+
2193
+ //#endregion
2194
+ //#region src/ui/bubble-menu/separator.tsx
2195
+ function BubbleMenuSeparator({ className }) {
2196
+ return /* @__PURE__ */ jsx("hr", {
2197
+ className,
2198
+ "data-re-bubble-menu-separator": ""
2199
+ });
2200
+ }
2201
+
2202
+ //#endregion
2203
+ //#region src/ui/bubble-menu/strike.tsx
2204
+ const BubbleMenuStrike = createMarkBubbleItem({
2205
+ name: "strike",
2206
+ activeName: "strike",
2207
+ command: "toggleStrike",
2208
+ icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
2209
+ });
2210
+
2211
+ //#endregion
2212
+ //#region src/ui/bubble-menu/underline.tsx
2213
+ const BubbleMenuUnderline = createMarkBubbleItem({
2214
+ name: "underline",
2215
+ activeName: "underline",
2216
+ command: "toggleUnderline",
2217
+ icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
2218
+ });
2219
+
2220
+ //#endregion
2221
+ //#region src/ui/bubble-menu/uppercase.tsx
2222
+ const BubbleMenuUppercase = createMarkBubbleItem({
2223
+ name: "uppercase",
2224
+ activeName: "uppercase",
2225
+ command: "toggleUppercase",
2226
+ icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
2227
+ });
2228
+
2229
+ //#endregion
2230
+ //#region src/ui/bubble-menu/index.ts
2231
+ const BubbleMenu = {
2232
+ Root: BubbleMenuRoot,
2233
+ ItemGroup: BubbleMenuItemGroup,
2234
+ Separator: BubbleMenuSeparator,
2235
+ Item: BubbleMenuItem,
2236
+ Bold: BubbleMenuBold,
2237
+ Italic: BubbleMenuItalic,
2238
+ Underline: BubbleMenuUnderline,
2239
+ Strike: BubbleMenuStrike,
2240
+ Code: BubbleMenuCode,
2241
+ Uppercase: BubbleMenuUppercase,
2242
+ AlignLeft: BubbleMenuAlignLeft,
2243
+ AlignCenter: BubbleMenuAlignCenter,
2244
+ AlignRight: BubbleMenuAlignRight,
2245
+ NodeSelector: Object.assign(BubbleMenuNodeSelector, {
2246
+ Root: NodeSelectorRoot,
2247
+ Trigger: NodeSelectorTrigger,
2248
+ Content: NodeSelectorContent
2249
+ }),
2250
+ LinkSelector: BubbleMenuLinkSelector
2251
+ };
2252
+
2253
+ //#endregion
2254
+ export { AlignmentAttribute, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, Button, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, Div, EmailNode, FourColumns, MAX_COLUMNS_DEPTH, MaxNesting, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, Placeholder, PreservedStyle, PreviewText, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, editorEventBus, getColumnsDepth, processStylesForUnlink, setTextAlignment };
1557
2255
  //# sourceMappingURL=index.mjs.map