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

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,6 @@
1
1
  import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { StarterKit } from "@tiptap/starter-kit";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import * as ReactEmailComponents from "@react-email/components";
4
5
  import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
5
6
  import CodeBlock$1 from "@tiptap/extension-code-block";
@@ -8,6 +9,11 @@ import { Decoration, DecorationSet } from "@tiptap/pm/view";
8
9
  import { fromHtml } from "hast-util-from-html";
9
10
  import Prism from "prismjs";
10
11
  import TipTapPlaceholder from "@tiptap/extension-placeholder";
12
+ import { useCurrentEditor, useEditorState } from "@tiptap/react";
13
+ 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";
14
+ import * as React from "react";
15
+ import * as Popover from "@radix-ui/react-popover";
16
+ import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
11
17
 
12
18
  //#region src/core/email-node.ts
13
19
  var EmailNode = class EmailNode extends Node {
@@ -39,6 +45,48 @@ var EmailNode = class EmailNode extends Node {
39
45
  }
40
46
  };
41
47
 
48
+ //#endregion
49
+ //#region src/core/event-bus.ts
50
+ const EVENT_PREFIX = "@react-email/editor:";
51
+ var EditorEventBus = class {
52
+ prefixEventName(eventName) {
53
+ return `${EVENT_PREFIX}${String(eventName)}`;
54
+ }
55
+ dispatch(eventName, payload, options) {
56
+ const target = options?.target ?? window;
57
+ const prefixedEventName = this.prefixEventName(eventName);
58
+ const event = new CustomEvent(prefixedEventName, {
59
+ detail: payload,
60
+ bubbles: false,
61
+ cancelable: false
62
+ });
63
+ target.dispatchEvent(event);
64
+ }
65
+ on(eventName, handler, options) {
66
+ const target = options?.target ?? window;
67
+ const prefixedEventName = this.prefixEventName(eventName);
68
+ const abortController = new AbortController();
69
+ const wrappedHandler = (event) => {
70
+ const customEvent = event;
71
+ const result = handler(customEvent.detail);
72
+ if (result instanceof Promise) result.catch((error) => {
73
+ console.error(`Error in async event handler for ${prefixedEventName}:`, {
74
+ event: customEvent.detail,
75
+ error
76
+ });
77
+ });
78
+ };
79
+ target.addEventListener(prefixedEventName, wrappedHandler, {
80
+ ...options,
81
+ signal: abortController.signal
82
+ });
83
+ return { unsubscribe: () => {
84
+ abortController.abort();
85
+ } };
86
+ }
87
+ };
88
+ const editorEventBus = new EditorEventBus();
89
+
42
90
  //#endregion
43
91
  //#region src/extensions/alignment-attribute.tsx
44
92
  const AlignmentAttribute = Extension.create({
@@ -373,12 +421,12 @@ const Body = EmailNode.create({
373
421
  0
374
422
  ];
375
423
  },
376
- renderToReactEmail({ children, node, styles }) {
424
+ renderToReactEmail({ children, node, style }) {
377
425
  const inlineStyles = inlineCssToJs(node.attrs?.style);
378
426
  return /* @__PURE__ */ jsx("div", {
379
427
  className: node.attrs?.class || void 0,
380
428
  style: {
381
- ...styles.reset,
429
+ ...style,
382
430
  ...inlineStyles
383
431
  },
384
432
  children
@@ -534,7 +582,7 @@ const Button = EmailNode.create({
534
582
  }
535
583
  };
536
584
  },
537
- renderToReactEmail({ children, node, styles }) {
585
+ renderToReactEmail({ children, node, style }) {
538
586
  const inlineStyles = inlineCssToJs(node.attrs?.style);
539
587
  return /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Column, {
540
588
  align: node.attrs?.align || node.attrs?.alignment,
@@ -542,8 +590,7 @@ const Button = EmailNode.create({
542
590
  className: node.attrs?.class || void 0,
543
591
  href: node.attrs?.href,
544
592
  style: {
545
- ...styles.reset,
546
- ...styles.button,
593
+ ...style,
547
594
  ...inlineStyles
548
595
  },
549
596
  children
@@ -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,
@@ -779,7 +845,7 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
779
845
  defaultTheme: this.options.defaultTheme
780
846
  })];
781
847
  }
782
- }), ({ node, styles }) => {
848
+ }), ({ node, style }) => {
783
849
  const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
784
850
  const userTheme = ReactEmailComponents[node.attrs?.theme];
785
851
  const theme = userTheme ? {
@@ -803,179 +869,9 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
803
869
  theme,
804
870
  style: {
805
871
  width: "auto",
806
- ...styles.codeBlock
807
- }
808
- });
809
- });
810
-
811
- //#endregion
812
- //#region src/extensions/columns.tsx
813
- const COLUMN_PARENT_TYPES = [
814
- "twoColumns",
815
- "threeColumns",
816
- "fourColumns"
817
- ];
818
- const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
819
- const MAX_COLUMNS_DEPTH = 3;
820
- function getColumnsDepth(doc, from) {
821
- const $from = doc.resolve(from);
822
- let depth = 0;
823
- for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
824
- return depth;
825
- }
826
- const VARIANTS = [
827
- {
828
- name: "twoColumns",
829
- columnCount: 2,
830
- content: "columnsColumn columnsColumn",
831
- dataType: "two-columns"
832
- },
833
- {
834
- name: "threeColumns",
835
- columnCount: 3,
836
- content: "columnsColumn columnsColumn columnsColumn",
837
- dataType: "three-columns"
838
- },
839
- {
840
- name: "fourColumns",
841
- columnCount: 4,
842
- content: "columnsColumn{4}",
843
- dataType: "four-columns"
844
- }
845
- ];
846
- const NODE_TYPE_MAP = {
847
- 2: "twoColumns",
848
- 3: "threeColumns",
849
- 4: "fourColumns"
850
- };
851
- function createColumnsNode(config, includeCommands) {
852
- return EmailNode.create({
853
- name: config.name,
854
- group: "block",
855
- content: config.content,
856
- isolating: true,
857
- defining: true,
858
- addAttributes() {
859
- return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
860
- },
861
- parseHTML() {
862
- return [{ tag: `div[data-type="${config.dataType}"]` }];
863
- },
864
- renderHTML({ HTMLAttributes }) {
865
- return [
866
- "div",
867
- mergeAttributes({
868
- "data-type": config.dataType,
869
- class: "node-columns"
870
- }, HTMLAttributes),
871
- 0
872
- ];
873
- },
874
- ...includeCommands && { addCommands() {
875
- return { insertColumns: (count) => ({ commands, state }) => {
876
- if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
877
- const nodeType = NODE_TYPE_MAP[count];
878
- const children = Array.from({ length: count }, () => ({
879
- type: "columnsColumn",
880
- content: [{
881
- type: "paragraph",
882
- content: []
883
- }]
884
- }));
885
- return commands.insertContent({
886
- type: nodeType,
887
- content: children
888
- });
889
- } };
890
- } },
891
- renderToReactEmail({ children, node, styles }) {
892
- const inlineStyles = inlineCssToJs(node.attrs?.style);
893
- return /* @__PURE__ */ jsx(Row, {
894
- className: node.attrs?.class || void 0,
895
- style: {
896
- ...styles.reset,
897
- ...inlineStyles
898
- },
899
- children
900
- });
872
+ ...style
901
873
  }
902
874
  });
903
- }
904
- const TwoColumns = createColumnsNode(VARIANTS[0], true);
905
- const ThreeColumns = createColumnsNode(VARIANTS[1], false);
906
- const FourColumns = createColumnsNode(VARIANTS[2], false);
907
- const ColumnsColumn = EmailNode.create({
908
- name: "columnsColumn",
909
- group: "columnsColumn",
910
- content: "block+",
911
- isolating: true,
912
- addAttributes() {
913
- return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
914
- },
915
- parseHTML() {
916
- return [{ tag: "div[data-type=\"column\"]" }];
917
- },
918
- renderHTML({ HTMLAttributes }) {
919
- return [
920
- "div",
921
- mergeAttributes({
922
- "data-type": "column",
923
- class: "node-column"
924
- }, HTMLAttributes),
925
- 0
926
- ];
927
- },
928
- addKeyboardShortcuts() {
929
- return {
930
- Backspace: ({ editor }) => {
931
- const { state } = editor;
932
- const { selection } = state;
933
- const { empty, $from } = selection;
934
- if (!empty) return false;
935
- for (let depth = $from.depth; depth >= 1; depth--) {
936
- if ($from.pos !== $from.start(depth)) break;
937
- const indexInParent = $from.index(depth - 1);
938
- if (indexInParent === 0) continue;
939
- const prevNode = $from.node(depth - 1).child(indexInParent - 1);
940
- if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
941
- const deleteFrom = $from.before(depth) - prevNode.nodeSize;
942
- const deleteTo = $from.before(depth);
943
- editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
944
- return true;
945
- }
946
- break;
947
- }
948
- return false;
949
- },
950
- "Mod-a": ({ editor }) => {
951
- const { state } = editor;
952
- const { $from } = state.selection;
953
- for (let d = $from.depth; d > 0; d--) {
954
- if ($from.node(d).type.name !== "columnsColumn") continue;
955
- const columnStart = $from.start(d);
956
- const columnEnd = $from.end(d);
957
- const { from, to } = state.selection;
958
- if (from === columnStart && to === columnEnd) return false;
959
- editor.view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, columnStart, columnEnd)));
960
- return true;
961
- }
962
- return false;
963
- }
964
- };
965
- },
966
- renderToReactEmail({ children, node, styles }) {
967
- const inlineStyles = inlineCssToJs(node.attrs?.style);
968
- const width = node.attrs?.width;
969
- return /* @__PURE__ */ jsx(Column, {
970
- className: node.attrs?.class || void 0,
971
- style: {
972
- ...styles.reset,
973
- ...inlineStyles,
974
- ...width ? { width } : {}
975
- },
976
- children
977
- });
978
- }
979
875
  });
980
876
 
981
877
  //#endregion
@@ -1010,12 +906,12 @@ const Div = EmailNode.create({
1010
906
  addAttributes() {
1011
907
  return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1012
908
  },
1013
- renderToReactEmail({ children, node, styles }) {
909
+ renderToReactEmail({ children, node, style }) {
1014
910
  const inlineStyles = inlineCssToJs(node.attrs?.style);
1015
911
  return /* @__PURE__ */ jsx("div", {
1016
912
  className: node.attrs?.class || void 0,
1017
913
  style: {
1018
- ...styles.reset,
914
+ ...style,
1019
915
  ...inlineStyles
1020
916
  },
1021
917
  children
@@ -1266,14 +1162,14 @@ const Section = EmailNode.create({
1266
1162
  });
1267
1163
  } };
1268
1164
  },
1269
- renderToReactEmail({ children, node, styles }) {
1165
+ renderToReactEmail({ children, node, style }) {
1270
1166
  const inlineStyles = inlineCssToJs(node.attrs?.style);
1271
1167
  const textAlign = node.attrs?.align || node.attrs?.alignment;
1272
1168
  return /* @__PURE__ */ jsx(Section$1, {
1273
1169
  className: node.attrs?.class || void 0,
1274
1170
  align: textAlign,
1275
1171
  style: {
1276
- ...styles.section,
1172
+ ...style,
1277
1173
  ...inlineStyles,
1278
1174
  ...getTextAlignment(textAlign)
1279
1175
  },
@@ -1406,7 +1302,7 @@ const Table = EmailNode.create({
1406
1302
  ]
1407
1303
  ];
1408
1304
  },
1409
- renderToReactEmail({ children, node, styles }) {
1305
+ renderToReactEmail({ children, node, style }) {
1410
1306
  const inlineStyles = inlineCssToJs(node.attrs?.style);
1411
1307
  const alignment = node.attrs?.align || node.attrs?.alignment;
1412
1308
  const width = node.attrs?.width;
@@ -1417,7 +1313,7 @@ const Table = EmailNode.create({
1417
1313
  return /* @__PURE__ */ jsx(Section$1, {
1418
1314
  className: node.attrs?.class || void 0,
1419
1315
  align: alignment,
1420
- style: resolveConflictingStyles(styles.reset, {
1316
+ style: resolveConflictingStyles(style, {
1421
1317
  ...inlineStyles,
1422
1318
  ...centeringStyles
1423
1319
  }),
@@ -1458,12 +1354,12 @@ const TableRow = EmailNode.create({
1458
1354
  0
1459
1355
  ];
1460
1356
  },
1461
- renderToReactEmail({ children, node, styles }) {
1357
+ renderToReactEmail({ children, node, style }) {
1462
1358
  const inlineStyles = inlineCssToJs(node.attrs?.style);
1463
1359
  return /* @__PURE__ */ jsx("tr", {
1464
1360
  className: node.attrs?.class || void 0,
1465
1361
  style: {
1466
- ...styles.reset,
1362
+ ...style,
1467
1363
  ...inlineStyles
1468
1364
  },
1469
1365
  children
@@ -1503,13 +1399,13 @@ const TableCell = EmailNode.create({
1503
1399
  0
1504
1400
  ];
1505
1401
  },
1506
- renderToReactEmail({ children, node, styles }) {
1402
+ renderToReactEmail({ children, node, style }) {
1507
1403
  const inlineStyles = inlineCssToJs(node.attrs?.style);
1508
1404
  return /* @__PURE__ */ jsx(Column, {
1509
1405
  className: node.attrs?.class || void 0,
1510
1406
  align: node.attrs?.align || node.attrs?.alignment,
1511
1407
  style: {
1512
- ...styles.reset,
1408
+ ...style,
1513
1409
  ...inlineStyles
1514
1410
  },
1515
1411
  children
@@ -1553,5 +1449,928 @@ const TableHeader = Node.create({
1553
1449
  });
1554
1450
 
1555
1451
  //#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 };
1452
+ //#region src/extensions/uppercase.ts
1453
+ const Uppercase = Mark.create({
1454
+ name: "uppercase",
1455
+ addOptions() {
1456
+ return { HTMLAttributes: {} };
1457
+ },
1458
+ parseHTML() {
1459
+ return [{
1460
+ tag: "span",
1461
+ getAttrs: (node) => {
1462
+ if (node.style.textTransform === "uppercase") return {};
1463
+ return false;
1464
+ }
1465
+ }];
1466
+ },
1467
+ renderHTML({ HTMLAttributes }) {
1468
+ return [
1469
+ "span",
1470
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1471
+ 0
1472
+ ];
1473
+ },
1474
+ addCommands() {
1475
+ return {
1476
+ setUppercase: () => ({ commands }) => {
1477
+ return commands.setMark(this.name);
1478
+ },
1479
+ toggleUppercase: () => ({ commands }) => {
1480
+ return commands.toggleMark(this.name);
1481
+ },
1482
+ unsetUppercase: () => ({ commands }) => {
1483
+ return commands.unsetMark(this.name);
1484
+ }
1485
+ };
1486
+ }
1487
+ });
1488
+
1489
+ //#endregion
1490
+ //#region src/extensions/columns.tsx
1491
+ const COLUMN_PARENT_TYPES = [
1492
+ "twoColumns",
1493
+ "threeColumns",
1494
+ "fourColumns"
1495
+ ];
1496
+ const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
1497
+ const MAX_COLUMNS_DEPTH = 3;
1498
+ function getColumnsDepth(doc, from) {
1499
+ const $from = doc.resolve(from);
1500
+ let depth = 0;
1501
+ for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
1502
+ return depth;
1503
+ }
1504
+ const VARIANTS = [
1505
+ {
1506
+ name: "twoColumns",
1507
+ columnCount: 2,
1508
+ content: "columnsColumn columnsColumn",
1509
+ dataType: "two-columns"
1510
+ },
1511
+ {
1512
+ name: "threeColumns",
1513
+ columnCount: 3,
1514
+ content: "columnsColumn columnsColumn columnsColumn",
1515
+ dataType: "three-columns"
1516
+ },
1517
+ {
1518
+ name: "fourColumns",
1519
+ columnCount: 4,
1520
+ content: "columnsColumn{4}",
1521
+ dataType: "four-columns"
1522
+ }
1523
+ ];
1524
+ const NODE_TYPE_MAP = {
1525
+ 2: "twoColumns",
1526
+ 3: "threeColumns",
1527
+ 4: "fourColumns"
1528
+ };
1529
+ function createColumnsNode(config, includeCommands) {
1530
+ return EmailNode.create({
1531
+ name: config.name,
1532
+ group: "block",
1533
+ content: config.content,
1534
+ isolating: true,
1535
+ defining: true,
1536
+ addAttributes() {
1537
+ return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
1538
+ },
1539
+ parseHTML() {
1540
+ return [{ tag: `div[data-type="${config.dataType}"]` }];
1541
+ },
1542
+ renderHTML({ HTMLAttributes }) {
1543
+ return [
1544
+ "div",
1545
+ mergeAttributes({
1546
+ "data-type": config.dataType,
1547
+ class: "node-columns"
1548
+ }, HTMLAttributes),
1549
+ 0
1550
+ ];
1551
+ },
1552
+ ...includeCommands && { addCommands() {
1553
+ return { insertColumns: (count) => ({ commands, state }) => {
1554
+ if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
1555
+ const nodeType = NODE_TYPE_MAP[count];
1556
+ const children = Array.from({ length: count }, () => ({
1557
+ type: "columnsColumn",
1558
+ content: [{
1559
+ type: "paragraph",
1560
+ content: []
1561
+ }]
1562
+ }));
1563
+ return commands.insertContent({
1564
+ type: nodeType,
1565
+ content: children
1566
+ });
1567
+ } };
1568
+ } },
1569
+ renderToReactEmail({ children, node, style }) {
1570
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1571
+ return /* @__PURE__ */ jsx(Row, {
1572
+ className: node.attrs?.class || void 0,
1573
+ style: {
1574
+ ...style,
1575
+ ...inlineStyles
1576
+ },
1577
+ children
1578
+ });
1579
+ }
1580
+ });
1581
+ }
1582
+ const TwoColumns = createColumnsNode(VARIANTS[0], true);
1583
+ const ThreeColumns = createColumnsNode(VARIANTS[1], false);
1584
+ const FourColumns = createColumnsNode(VARIANTS[2], false);
1585
+ const ColumnsColumn = EmailNode.create({
1586
+ name: "columnsColumn",
1587
+ group: "columnsColumn",
1588
+ content: "block+",
1589
+ isolating: true,
1590
+ addAttributes() {
1591
+ return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
1592
+ },
1593
+ parseHTML() {
1594
+ return [{ tag: "div[data-type=\"column\"]" }];
1595
+ },
1596
+ renderHTML({ HTMLAttributes }) {
1597
+ return [
1598
+ "div",
1599
+ mergeAttributes({
1600
+ "data-type": "column",
1601
+ class: "node-column"
1602
+ }, HTMLAttributes),
1603
+ 0
1604
+ ];
1605
+ },
1606
+ addKeyboardShortcuts() {
1607
+ return {
1608
+ Backspace: ({ editor }) => {
1609
+ const { state } = editor;
1610
+ const { selection } = state;
1611
+ const { empty, $from } = selection;
1612
+ if (!empty) return false;
1613
+ for (let depth = $from.depth; depth >= 1; depth--) {
1614
+ if ($from.pos !== $from.start(depth)) break;
1615
+ const indexInParent = $from.index(depth - 1);
1616
+ if (indexInParent === 0) continue;
1617
+ const prevNode = $from.node(depth - 1).child(indexInParent - 1);
1618
+ if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
1619
+ const deleteFrom = $from.before(depth) - prevNode.nodeSize;
1620
+ const deleteTo = $from.before(depth);
1621
+ editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
1622
+ return true;
1623
+ }
1624
+ break;
1625
+ }
1626
+ return false;
1627
+ },
1628
+ "Mod-a": ({ editor }) => {
1629
+ const { state } = editor;
1630
+ const { $from } = state.selection;
1631
+ for (let d = $from.depth; d > 0; d--) {
1632
+ if ($from.node(d).type.name !== "columnsColumn") continue;
1633
+ const columnStart = $from.start(d);
1634
+ const columnEnd = $from.end(d);
1635
+ const { from, to } = state.selection;
1636
+ if (from === columnStart && to === columnEnd) return false;
1637
+ editor.view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, columnStart, columnEnd)));
1638
+ return true;
1639
+ }
1640
+ return false;
1641
+ }
1642
+ };
1643
+ },
1644
+ renderToReactEmail({ children, node, style }) {
1645
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1646
+ const width = node.attrs?.width;
1647
+ return /* @__PURE__ */ jsx(Column, {
1648
+ className: node.attrs?.class || void 0,
1649
+ style: {
1650
+ ...style,
1651
+ ...inlineStyles,
1652
+ ...width ? { width } : {}
1653
+ },
1654
+ children
1655
+ });
1656
+ }
1657
+ });
1658
+
1659
+ //#endregion
1660
+ //#region src/extensions/index.ts
1661
+ const coreExtensions = [
1662
+ StarterKit.configure({
1663
+ undoRedo: false,
1664
+ heading: false,
1665
+ link: false,
1666
+ underline: false,
1667
+ trailingNode: false,
1668
+ bold: false,
1669
+ gapcursor: false,
1670
+ listItem: {},
1671
+ bulletList: { HTMLAttributes: { class: "node-bulletList" } },
1672
+ paragraph: { HTMLAttributes: { class: "node-paragraph" } },
1673
+ orderedList: { HTMLAttributes: { class: "node-orderedList" } },
1674
+ blockquote: { HTMLAttributes: { class: "node-blockquote" } },
1675
+ codeBlock: false,
1676
+ code: { HTMLAttributes: {
1677
+ class: "node-inlineCode",
1678
+ spellcheck: "false"
1679
+ } },
1680
+ horizontalRule: false,
1681
+ dropcursor: {
1682
+ color: "#61a8f8",
1683
+ class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
1684
+ width: 4
1685
+ }
1686
+ }),
1687
+ CodeBlockPrism.configure({
1688
+ defaultLanguage: "javascript",
1689
+ HTMLAttributes: { class: "prism node-codeBlock" }
1690
+ }),
1691
+ Placeholder,
1692
+ PreviewText,
1693
+ Bold,
1694
+ Sup,
1695
+ Uppercase,
1696
+ PreservedStyle,
1697
+ Table,
1698
+ TableRow,
1699
+ TableCell,
1700
+ TableHeader,
1701
+ Body,
1702
+ Div,
1703
+ Button,
1704
+ Section,
1705
+ AlignmentAttribute.configure({ types: [
1706
+ "heading",
1707
+ "paragraph",
1708
+ "image",
1709
+ "blockquote",
1710
+ "codeBlock",
1711
+ "bulletList",
1712
+ "orderedList",
1713
+ "listItem",
1714
+ "button",
1715
+ "youtube",
1716
+ "twitter",
1717
+ "table",
1718
+ "tableRow",
1719
+ "tableCell",
1720
+ "tableHeader",
1721
+ "columnsColumn"
1722
+ ] }),
1723
+ StyleAttribute.configure({ types: [
1724
+ "heading",
1725
+ "paragraph",
1726
+ "image",
1727
+ "blockquote",
1728
+ "codeBlock",
1729
+ "bulletList",
1730
+ "orderedList",
1731
+ "listItem",
1732
+ "button",
1733
+ "youtube",
1734
+ "twitter",
1735
+ "horizontalRule",
1736
+ "footer",
1737
+ "section",
1738
+ "div",
1739
+ "body",
1740
+ "table",
1741
+ "tableRow",
1742
+ "tableCell",
1743
+ "tableHeader",
1744
+ "columnsColumn",
1745
+ "link"
1746
+ ] }),
1747
+ ClassAttribute.configure({ types: [
1748
+ "heading",
1749
+ "paragraph",
1750
+ "image",
1751
+ "blockquote",
1752
+ "bulletList",
1753
+ "orderedList",
1754
+ "listItem",
1755
+ "button",
1756
+ "youtube",
1757
+ "twitter",
1758
+ "horizontalRule",
1759
+ "footer",
1760
+ "section",
1761
+ "div",
1762
+ "body",
1763
+ "table",
1764
+ "tableRow",
1765
+ "tableCell",
1766
+ "tableHeader",
1767
+ "columnsColumn",
1768
+ "link"
1769
+ ] }),
1770
+ MaxNesting.configure({
1771
+ maxDepth: 50,
1772
+ nodeTypes: [
1773
+ "section",
1774
+ "bulletList",
1775
+ "orderedList"
1776
+ ]
1777
+ })
1778
+ ];
1779
+
1780
+ //#endregion
1781
+ //#region src/utils/set-text-alignment.ts
1782
+ function setTextAlignment(editor, alignment) {
1783
+ const { from, to } = editor.state.selection;
1784
+ const tr = editor.state.tr;
1785
+ editor.state.doc.nodesBetween(from, to, (node, pos) => {
1786
+ if (node.isTextblock) {
1787
+ const prop = "align" in node.attrs ? "align" : "alignment";
1788
+ tr.setNodeMarkup(pos, null, {
1789
+ ...node.attrs,
1790
+ [prop]: alignment
1791
+ });
1792
+ }
1793
+ });
1794
+ editor.view.dispatch(tr);
1795
+ }
1796
+
1797
+ //#endregion
1798
+ //#region src/ui/bubble-menu/context.tsx
1799
+ const BubbleMenuContext = React.createContext(null);
1800
+ function useBubbleMenuContext() {
1801
+ const context = React.useContext(BubbleMenuContext);
1802
+ if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
1803
+ return context;
1804
+ }
1805
+
1806
+ //#endregion
1807
+ //#region src/ui/bubble-menu/item.tsx
1808
+ function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
1809
+ return /* @__PURE__ */ jsx("button", {
1810
+ type: "button",
1811
+ "aria-label": name,
1812
+ "aria-pressed": isActive,
1813
+ className,
1814
+ "data-re-bubble-menu-item": "",
1815
+ "data-item": name,
1816
+ ...isActive ? { "data-active": "" } : {},
1817
+ onMouseDown: (e) => e.preventDefault(),
1818
+ onClick: onCommand,
1819
+ ...rest,
1820
+ children
1821
+ });
1822
+ }
1823
+
1824
+ //#endregion
1825
+ //#region src/ui/bubble-menu/align-center.tsx
1826
+ function BubbleMenuAlignCenter({ className, children }) {
1827
+ const { editor } = useBubbleMenuContext();
1828
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1829
+ name: "align-center",
1830
+ isActive: useEditorState({
1831
+ editor,
1832
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
1833
+ }),
1834
+ onCommand: () => setTextAlignment(editor, "center"),
1835
+ className,
1836
+ children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
1837
+ });
1838
+ }
1839
+
1840
+ //#endregion
1841
+ //#region src/ui/bubble-menu/align-left.tsx
1842
+ function BubbleMenuAlignLeft({ className, children }) {
1843
+ const { editor } = useBubbleMenuContext();
1844
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1845
+ name: "align-left",
1846
+ isActive: useEditorState({
1847
+ editor,
1848
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
1849
+ }),
1850
+ onCommand: () => setTextAlignment(editor, "left"),
1851
+ className,
1852
+ children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
1853
+ });
1854
+ }
1855
+
1856
+ //#endregion
1857
+ //#region src/ui/bubble-menu/align-right.tsx
1858
+ function BubbleMenuAlignRight({ className, children }) {
1859
+ const { editor } = useBubbleMenuContext();
1860
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1861
+ name: "align-right",
1862
+ isActive: useEditorState({
1863
+ editor,
1864
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
1865
+ }),
1866
+ onCommand: () => setTextAlignment(editor, "right"),
1867
+ className,
1868
+ children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
1869
+ });
1870
+ }
1871
+
1872
+ //#endregion
1873
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
1874
+ function createMarkBubbleItem(config) {
1875
+ function MarkBubbleItem({ className, children }) {
1876
+ const { editor } = useBubbleMenuContext();
1877
+ const isActive = useEditorState({
1878
+ editor,
1879
+ selector: ({ editor: editor$1 }) => {
1880
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
1881
+ return editor$1?.isActive(config.activeName) ?? false;
1882
+ }
1883
+ });
1884
+ const handleCommand = () => {
1885
+ const chain = editor.chain().focus();
1886
+ const method = chain[config.command];
1887
+ if (method) method.call(chain).run();
1888
+ };
1889
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
1890
+ name: config.name,
1891
+ isActive,
1892
+ onCommand: handleCommand,
1893
+ className,
1894
+ children: children ?? config.icon
1895
+ });
1896
+ }
1897
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
1898
+ return MarkBubbleItem;
1899
+ }
1900
+
1901
+ //#endregion
1902
+ //#region src/ui/bubble-menu/bold.tsx
1903
+ const BubbleMenuBold = createMarkBubbleItem({
1904
+ name: "bold",
1905
+ activeName: "bold",
1906
+ command: "toggleBold",
1907
+ icon: /* @__PURE__ */ jsx(BoldIcon, {})
1908
+ });
1909
+
1910
+ //#endregion
1911
+ //#region src/ui/bubble-menu/code.tsx
1912
+ const BubbleMenuCode = createMarkBubbleItem({
1913
+ name: "code",
1914
+ activeName: "code",
1915
+ command: "toggleCode",
1916
+ icon: /* @__PURE__ */ jsx(CodeIcon, {})
1917
+ });
1918
+
1919
+ //#endregion
1920
+ //#region src/ui/bubble-menu/group.tsx
1921
+ function BubbleMenuItemGroup({ className, children }) {
1922
+ return /* @__PURE__ */ jsx("fieldset", {
1923
+ className,
1924
+ "data-re-bubble-menu-group": "",
1925
+ children
1926
+ });
1927
+ }
1928
+
1929
+ //#endregion
1930
+ //#region src/ui/bubble-menu/italic.tsx
1931
+ const BubbleMenuItalic = createMarkBubbleItem({
1932
+ name: "italic",
1933
+ activeName: "italic",
1934
+ command: "toggleItalic",
1935
+ icon: /* @__PURE__ */ jsx(ItalicIcon, {})
1936
+ });
1937
+
1938
+ //#endregion
1939
+ //#region src/ui/bubble-menu/utils.ts
1940
+ const SAFE_PROTOCOLS = new Set([
1941
+ "http:",
1942
+ "https:",
1943
+ "mailto:",
1944
+ "tel:"
1945
+ ]);
1946
+ /**
1947
+ * Basic URL validation and auto-prefixing.
1948
+ * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
1949
+ * Returns the valid URL string or null.
1950
+ */
1951
+ function getUrlFromString(str) {
1952
+ if (str === "#") return str;
1953
+ try {
1954
+ const url = new URL(str);
1955
+ if (SAFE_PROTOCOLS.has(url.protocol)) return str;
1956
+ return null;
1957
+ } catch {}
1958
+ try {
1959
+ if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
1960
+ } catch {}
1961
+ return null;
1962
+ }
1963
+
1964
+ //#endregion
1965
+ //#region src/ui/bubble-menu/link-selector.tsx
1966
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children }) {
1967
+ const { editor } = useBubbleMenuContext();
1968
+ const [isOpen, setIsOpen] = React.useState(false);
1969
+ const editorState = useEditorState({
1970
+ editor,
1971
+ selector: ({ editor: editor$1 }) => ({
1972
+ isLinkActive: editor$1?.isActive("link") ?? false,
1973
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
1974
+ currentHref: editor$1?.getAttributes("link").href || ""
1975
+ })
1976
+ });
1977
+ React.useEffect(() => {
1978
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
1979
+ setIsOpen(true);
1980
+ });
1981
+ return () => {
1982
+ setIsOpen(false);
1983
+ subscription.unsubscribe();
1984
+ };
1985
+ }, []);
1986
+ if (!editorState) return null;
1987
+ const handleOpenLink = () => {
1988
+ setIsOpen(!isOpen);
1989
+ };
1990
+ return /* @__PURE__ */ jsxs("div", {
1991
+ "data-re-link-selector": "",
1992
+ ...isOpen ? { "data-open": "" } : {},
1993
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
1994
+ className,
1995
+ children: [showToggle && /* @__PURE__ */ jsx("button", {
1996
+ type: "button",
1997
+ "aria-expanded": isOpen,
1998
+ "aria-haspopup": "true",
1999
+ "aria-label": "Add link",
2000
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
2001
+ "data-re-link-selector-trigger": "",
2002
+ onClick: handleOpenLink,
2003
+ children: /* @__PURE__ */ jsx(LinkIcon, {})
2004
+ }), isOpen && /* @__PURE__ */ jsx(LinkForm, {
2005
+ editor,
2006
+ currentHref: editorState.currentHref,
2007
+ validateUrl,
2008
+ onLinkApply,
2009
+ onLinkRemove,
2010
+ setIsOpen,
2011
+ children
2012
+ })]
2013
+ });
2014
+ }
2015
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
2016
+ const inputRef = React.useRef(null);
2017
+ const formRef = React.useRef(null);
2018
+ const displayHref = currentHref === "#" ? "" : currentHref;
2019
+ const [inputValue, setInputValue] = React.useState(displayHref);
2020
+ React.useEffect(() => {
2021
+ const timeoutId = setTimeout(() => {
2022
+ inputRef.current?.focus();
2023
+ }, 0);
2024
+ return () => clearTimeout(timeoutId);
2025
+ }, []);
2026
+ React.useEffect(() => {
2027
+ const handleKeyDown = (event) => {
2028
+ if (event.key === "Escape") {
2029
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
2030
+ setIsOpen(false);
2031
+ }
2032
+ };
2033
+ const handleClickOutside = (event) => {
2034
+ if (formRef.current && !formRef.current.contains(event.target)) {
2035
+ const form = formRef.current;
2036
+ const submitEvent = new Event("submit", {
2037
+ bubbles: true,
2038
+ cancelable: true
2039
+ });
2040
+ form.dispatchEvent(submitEvent);
2041
+ setIsOpen(false);
2042
+ }
2043
+ };
2044
+ document.addEventListener("mousedown", handleClickOutside);
2045
+ window.addEventListener("keydown", handleKeyDown);
2046
+ return () => {
2047
+ window.removeEventListener("keydown", handleKeyDown);
2048
+ document.removeEventListener("mousedown", handleClickOutside);
2049
+ };
2050
+ }, [editor, setIsOpen]);
2051
+ function handleSubmit(e) {
2052
+ e.preventDefault();
2053
+ const value = inputValue.trim();
2054
+ if (value === "") {
2055
+ setLinkHref(editor, "");
2056
+ setIsOpen(false);
2057
+ focusEditor(editor);
2058
+ onLinkRemove?.();
2059
+ return;
2060
+ }
2061
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
2062
+ if (!finalValue) {
2063
+ setLinkHref(editor, "");
2064
+ setIsOpen(false);
2065
+ focusEditor(editor);
2066
+ onLinkRemove?.();
2067
+ return;
2068
+ }
2069
+ setLinkHref(editor, finalValue);
2070
+ setIsOpen(false);
2071
+ focusEditor(editor);
2072
+ onLinkApply?.(finalValue);
2073
+ }
2074
+ function handleUnlink(e) {
2075
+ e.stopPropagation();
2076
+ setLinkHref(editor, "");
2077
+ setIsOpen(false);
2078
+ focusEditor(editor);
2079
+ onLinkRemove?.();
2080
+ }
2081
+ return /* @__PURE__ */ jsxs("form", {
2082
+ ref: formRef,
2083
+ "data-re-link-selector-form": "",
2084
+ onMouseDown: (e) => e.stopPropagation(),
2085
+ onClick: (e) => e.stopPropagation(),
2086
+ onKeyDown: (e) => e.stopPropagation(),
2087
+ onSubmit: handleSubmit,
2088
+ children: [
2089
+ /* @__PURE__ */ jsx("input", {
2090
+ ref: inputRef,
2091
+ "data-re-link-selector-input": "",
2092
+ value: inputValue,
2093
+ onFocus: (e) => e.stopPropagation(),
2094
+ onChange: (e) => setInputValue(e.target.value),
2095
+ placeholder: "Paste a link",
2096
+ type: "text"
2097
+ }),
2098
+ children,
2099
+ displayHref ? /* @__PURE__ */ jsx("button", {
2100
+ type: "button",
2101
+ "aria-label": "Remove link",
2102
+ "data-re-link-selector-unlink": "",
2103
+ onClick: handleUnlink,
2104
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
2105
+ }) : /* @__PURE__ */ jsx("button", {
2106
+ type: "submit",
2107
+ "aria-label": "Apply link",
2108
+ "data-re-link-selector-apply": "",
2109
+ onMouseDown: (e) => e.stopPropagation(),
2110
+ children: /* @__PURE__ */ jsx(Check, {})
2111
+ })
2112
+ ]
2113
+ });
2114
+ }
2115
+ function setLinkHref(editor, href) {
2116
+ if (href.length === 0) {
2117
+ editor.chain().unsetLink().run();
2118
+ return;
2119
+ }
2120
+ const { from, to } = editor.state.selection;
2121
+ if (from === to) {
2122
+ editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
2123
+ from,
2124
+ to
2125
+ }).run();
2126
+ return;
2127
+ }
2128
+ editor.chain().setLink({ href }).run();
2129
+ }
2130
+ function focusEditor(editor) {
2131
+ setTimeout(() => {
2132
+ editor.commands.focus();
2133
+ }, 0);
2134
+ }
2135
+
2136
+ //#endregion
2137
+ //#region src/ui/bubble-menu/node-selector.tsx
2138
+ const NodeSelectorContext = React.createContext(null);
2139
+ function useNodeSelectorContext() {
2140
+ const context = React.useContext(NodeSelectorContext);
2141
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
2142
+ return context;
2143
+ }
2144
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
2145
+ const { editor } = useBubbleMenuContext();
2146
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
2147
+ const isControlled = controlledOpen !== void 0;
2148
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2149
+ const setIsOpen = React.useCallback((value) => {
2150
+ if (!isControlled) setUncontrolledOpen(value);
2151
+ onOpenChange?.(value);
2152
+ }, [isControlled, onOpenChange]);
2153
+ const editorState = useEditorState({
2154
+ editor,
2155
+ selector: ({ editor: editor$1 }) => ({
2156
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
2157
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
2158
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
2159
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
2160
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
2161
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
2162
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
2163
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
2164
+ })
2165
+ });
2166
+ const allItems = React.useMemo(() => [
2167
+ {
2168
+ name: "Text",
2169
+ icon: TextIcon,
2170
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
2171
+ isActive: editorState?.isParagraphActive ?? false
2172
+ },
2173
+ {
2174
+ name: "Title",
2175
+ icon: Heading1,
2176
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
2177
+ isActive: editorState?.isHeading1Active ?? false
2178
+ },
2179
+ {
2180
+ name: "Subtitle",
2181
+ icon: Heading2,
2182
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
2183
+ isActive: editorState?.isHeading2Active ?? false
2184
+ },
2185
+ {
2186
+ name: "Heading",
2187
+ icon: Heading3,
2188
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
2189
+ isActive: editorState?.isHeading3Active ?? false
2190
+ },
2191
+ {
2192
+ name: "Bullet List",
2193
+ icon: List,
2194
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
2195
+ isActive: editorState?.isBulletListActive ?? false
2196
+ },
2197
+ {
2198
+ name: "Numbered List",
2199
+ icon: ListOrdered,
2200
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
2201
+ isActive: editorState?.isOrderedListActive ?? false
2202
+ },
2203
+ {
2204
+ name: "Quote",
2205
+ icon: TextQuote,
2206
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
2207
+ isActive: editorState?.isBlockquoteActive ?? false
2208
+ },
2209
+ {
2210
+ name: "Code",
2211
+ icon: Code,
2212
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2213
+ isActive: editorState?.isCodeBlockActive ?? false
2214
+ }
2215
+ ], [editor, editorState]);
2216
+ const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
2217
+ const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
2218
+ const contextValue = React.useMemo(() => ({
2219
+ items,
2220
+ activeItem,
2221
+ isOpen,
2222
+ setIsOpen
2223
+ }), [
2224
+ items,
2225
+ activeItem,
2226
+ isOpen,
2227
+ setIsOpen
2228
+ ]);
2229
+ if (!editorState || items.length === 0) return null;
2230
+ return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
2231
+ value: contextValue,
2232
+ children: /* @__PURE__ */ jsx(Popover.Root, {
2233
+ open: isOpen,
2234
+ onOpenChange: setIsOpen,
2235
+ children: /* @__PURE__ */ jsx("div", {
2236
+ "data-re-node-selector": "",
2237
+ ...isOpen ? { "data-open": "" } : {},
2238
+ className,
2239
+ children
2240
+ })
2241
+ })
2242
+ });
2243
+ }
2244
+ function NodeSelectorTrigger({ className, children }) {
2245
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
2246
+ return /* @__PURE__ */ jsx(Popover.Trigger, {
2247
+ "data-re-node-selector-trigger": "",
2248
+ className,
2249
+ onClick: () => setIsOpen(!isOpen),
2250
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
2251
+ });
2252
+ }
2253
+ function NodeSelectorContent({ className, align = "start", children }) {
2254
+ const { items, setIsOpen } = useNodeSelectorContext();
2255
+ return /* @__PURE__ */ jsx(Popover.Content, {
2256
+ align,
2257
+ "data-re-node-selector-content": "",
2258
+ className,
2259
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
2260
+ const Icon = item.icon;
2261
+ return /* @__PURE__ */ jsxs("button", {
2262
+ type: "button",
2263
+ "data-re-node-selector-item": "",
2264
+ ...item.isActive ? { "data-active": "" } : {},
2265
+ onClick: () => {
2266
+ item.command();
2267
+ setIsOpen(false);
2268
+ },
2269
+ children: [
2270
+ /* @__PURE__ */ jsx(Icon, {}),
2271
+ /* @__PURE__ */ jsx("span", { children: item.name }),
2272
+ item.isActive && /* @__PURE__ */ jsx(Check, {})
2273
+ ]
2274
+ }, item.name);
2275
+ })
2276
+ });
2277
+ }
2278
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
2279
+ return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
2280
+ omit,
2281
+ open,
2282
+ onOpenChange,
2283
+ className,
2284
+ children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
2285
+ });
2286
+ }
2287
+
2288
+ //#endregion
2289
+ //#region src/ui/bubble-menu/root.tsx
2290
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
2291
+ const { editor } = useCurrentEditor();
2292
+ if (!editor) return null;
2293
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
2294
+ editor,
2295
+ "data-re-bubble-menu": "",
2296
+ shouldShow: ({ editor: editor$1, view }) => {
2297
+ for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
2298
+ if (view.dom.classList.contains("dragging")) return false;
2299
+ return editor$1.view.state.selection.content().size > 0;
2300
+ },
2301
+ options: {
2302
+ placement,
2303
+ offset,
2304
+ onHide
2305
+ },
2306
+ className,
2307
+ children: /* @__PURE__ */ jsx(BubbleMenuContext.Provider, {
2308
+ value: { editor },
2309
+ children
2310
+ })
2311
+ });
2312
+ }
2313
+
2314
+ //#endregion
2315
+ //#region src/ui/bubble-menu/separator.tsx
2316
+ function BubbleMenuSeparator({ className }) {
2317
+ return /* @__PURE__ */ jsx("hr", {
2318
+ className,
2319
+ "data-re-bubble-menu-separator": ""
2320
+ });
2321
+ }
2322
+
2323
+ //#endregion
2324
+ //#region src/ui/bubble-menu/strike.tsx
2325
+ const BubbleMenuStrike = createMarkBubbleItem({
2326
+ name: "strike",
2327
+ activeName: "strike",
2328
+ command: "toggleStrike",
2329
+ icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
2330
+ });
2331
+
2332
+ //#endregion
2333
+ //#region src/ui/bubble-menu/underline.tsx
2334
+ const BubbleMenuUnderline = createMarkBubbleItem({
2335
+ name: "underline",
2336
+ activeName: "underline",
2337
+ command: "toggleUnderline",
2338
+ icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
2339
+ });
2340
+
2341
+ //#endregion
2342
+ //#region src/ui/bubble-menu/uppercase.tsx
2343
+ const BubbleMenuUppercase = createMarkBubbleItem({
2344
+ name: "uppercase",
2345
+ activeName: "uppercase",
2346
+ command: "toggleUppercase",
2347
+ icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
2348
+ });
2349
+
2350
+ //#endregion
2351
+ //#region src/ui/bubble-menu/index.ts
2352
+ const BubbleMenu = {
2353
+ Root: BubbleMenuRoot,
2354
+ ItemGroup: BubbleMenuItemGroup,
2355
+ Separator: BubbleMenuSeparator,
2356
+ Item: BubbleMenuItem,
2357
+ Bold: BubbleMenuBold,
2358
+ Italic: BubbleMenuItalic,
2359
+ Underline: BubbleMenuUnderline,
2360
+ Strike: BubbleMenuStrike,
2361
+ Code: BubbleMenuCode,
2362
+ Uppercase: BubbleMenuUppercase,
2363
+ AlignLeft: BubbleMenuAlignLeft,
2364
+ AlignCenter: BubbleMenuAlignCenter,
2365
+ AlignRight: BubbleMenuAlignRight,
2366
+ NodeSelector: Object.assign(BubbleMenuNodeSelector, {
2367
+ Root: NodeSelectorRoot,
2368
+ Trigger: NodeSelectorTrigger,
2369
+ Content: NodeSelectorContent
2370
+ }),
2371
+ LinkSelector: BubbleMenuLinkSelector
2372
+ };
2373
+
2374
+ //#endregion
2375
+ 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, coreExtensions, editorEventBus, getColumnsDepth, processStylesForUnlink, setTextAlignment };
1557
2376
  //# sourceMappingURL=index.mjs.map