@react-email/editor 0.0.0-experimental.0 → 0.0.0-experimental.10

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
@@ -23,6 +23,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let __tiptap_core = require("@tiptap/core");
25
25
  __tiptap_core = __toESM(__tiptap_core);
26
+ let __tiptap_starter_kit = require("@tiptap/starter-kit");
27
+ __tiptap_starter_kit = __toESM(__tiptap_starter_kit);
26
28
  let react_jsx_runtime = require("react/jsx-runtime");
27
29
  react_jsx_runtime = __toESM(react_jsx_runtime);
28
30
  let __react_email_components = require("@react-email/components");
@@ -37,6 +39,18 @@ let hast_util_from_html = require("hast-util-from-html");
37
39
  hast_util_from_html = __toESM(hast_util_from_html);
38
40
  let prismjs = require("prismjs");
39
41
  prismjs = __toESM(prismjs);
42
+ let __tiptap_extension_placeholder = require("@tiptap/extension-placeholder");
43
+ __tiptap_extension_placeholder = __toESM(__tiptap_extension_placeholder);
44
+ let __tiptap_react = require("@tiptap/react");
45
+ __tiptap_react = __toESM(__tiptap_react);
46
+ let lucide_react = require("lucide-react");
47
+ lucide_react = __toESM(lucide_react);
48
+ let react = require("react");
49
+ react = __toESM(react);
50
+ let __radix_ui_react_popover = require("@radix-ui/react-popover");
51
+ __radix_ui_react_popover = __toESM(__radix_ui_react_popover);
52
+ let __tiptap_react_menus = require("@tiptap/react/menus");
53
+ __tiptap_react_menus = __toESM(__tiptap_react_menus);
40
54
 
41
55
  //#region src/core/email-node.ts
42
56
  var EmailNode = class EmailNode extends __tiptap_core.Node {
@@ -68,6 +82,103 @@ var EmailNode = class EmailNode extends __tiptap_core.Node {
68
82
  }
69
83
  };
70
84
 
85
+ //#endregion
86
+ //#region src/core/event-bus.ts
87
+ const EVENT_PREFIX = "@react-email/editor:";
88
+ var EditorEventBus = class {
89
+ prefixEventName(eventName) {
90
+ return `${EVENT_PREFIX}${String(eventName)}`;
91
+ }
92
+ dispatch(eventName, payload, options) {
93
+ const target = options?.target ?? window;
94
+ const prefixedEventName = this.prefixEventName(eventName);
95
+ const event = new CustomEvent(prefixedEventName, {
96
+ detail: payload,
97
+ bubbles: false,
98
+ cancelable: false
99
+ });
100
+ target.dispatchEvent(event);
101
+ }
102
+ on(eventName, handler, options) {
103
+ const target = options?.target ?? window;
104
+ const prefixedEventName = this.prefixEventName(eventName);
105
+ const abortController = new AbortController();
106
+ const wrappedHandler = (event) => {
107
+ const customEvent = event;
108
+ const result = handler(customEvent.detail);
109
+ if (result instanceof Promise) result.catch((error) => {
110
+ console.error(`Error in async event handler for ${prefixedEventName}:`, {
111
+ event: customEvent.detail,
112
+ error
113
+ });
114
+ });
115
+ };
116
+ target.addEventListener(prefixedEventName, wrappedHandler, {
117
+ ...options,
118
+ signal: abortController.signal
119
+ });
120
+ return { unsubscribe: () => {
121
+ abortController.abort();
122
+ } };
123
+ }
124
+ };
125
+ const editorEventBus = new EditorEventBus();
126
+
127
+ //#endregion
128
+ //#region src/extensions/alignment-attribute.tsx
129
+ const AlignmentAttribute = __tiptap_core.Extension.create({
130
+ name: "alignmentAttribute",
131
+ addOptions() {
132
+ return {
133
+ types: [],
134
+ alignments: [
135
+ "left",
136
+ "center",
137
+ "right",
138
+ "justify"
139
+ ]
140
+ };
141
+ },
142
+ addGlobalAttributes() {
143
+ return [{
144
+ types: this.options.types,
145
+ attributes: { alignment: {
146
+ parseHTML: (element) => {
147
+ const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
148
+ if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
149
+ return null;
150
+ },
151
+ renderHTML: (attributes) => {
152
+ if (attributes.alignment === "left") return {};
153
+ return { alignment: attributes.alignment };
154
+ }
155
+ } }
156
+ }];
157
+ },
158
+ addCommands() {
159
+ return { setAlignment: (alignment) => ({ commands }) => {
160
+ if (!this.options.alignments.includes(alignment)) return false;
161
+ return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
162
+ } };
163
+ },
164
+ addKeyboardShortcuts() {
165
+ return {
166
+ Enter: () => {
167
+ const { from } = this.editor.state.selection;
168
+ const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
169
+ if (currentAlignment) requestAnimationFrame(() => {
170
+ this.editor.commands.setAlignment(currentAlignment);
171
+ });
172
+ return false;
173
+ },
174
+ "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
175
+ "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
176
+ "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
177
+ "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
178
+ };
179
+ }
180
+ });
181
+
71
182
  //#endregion
72
183
  //#region src/utils/attribute-helpers.ts
73
184
  /**
@@ -124,6 +235,14 @@ const LAYOUT_ATTRIBUTES = [
124
235
  "height"
125
236
  ];
126
237
  /**
238
+ * Table-specific HTML attributes used for table layout and styling.
239
+ */
240
+ const TABLE_ATTRIBUTES = [
241
+ "border",
242
+ "cellpadding",
243
+ "cellspacing"
244
+ ];
245
+ /**
127
246
  * Table cell-specific HTML attributes.
128
247
  */
129
248
  const TABLE_CELL_ATTRIBUTES = [
@@ -140,6 +259,7 @@ const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
140
259
 
141
260
  //#endregion
142
261
  //#region src/utils/styles.ts
262
+ const WHITE_SPACE_REGEX = /\s+/;
143
263
  const inlineCssToJs = (inlineStyle, options = {}) => {
144
264
  const styleObject = {};
145
265
  if (!inlineStyle || inlineStyle === "" || typeof inlineStyle === "object") return styleObject;
@@ -154,6 +274,157 @@ const inlineCssToJs = (inlineStyle, options = {}) => {
154
274
  });
155
275
  return styleObject;
156
276
  };
277
+ /**
278
+ * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.
279
+ * This prevents shorthand properties from overriding specific longhand properties in email clients.
280
+ *
281
+ * @param styles - Style object that may contain shorthand properties
282
+ * @returns New style object with shorthand properties expanded to longhand
283
+ *
284
+ * @example
285
+ * expandShorthandProperties({ margin: '0', paddingTop: '10px' })
286
+ * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }
287
+ */
288
+ function expandShorthandProperties(styles) {
289
+ if (!styles || typeof styles !== "object") return {};
290
+ const expanded = {};
291
+ for (const key in styles) {
292
+ const value = styles[key];
293
+ if (value === void 0 || value === null || value === "") continue;
294
+ switch (key) {
295
+ case "margin": {
296
+ const values = parseShorthandValue(value);
297
+ expanded.marginTop = values.top;
298
+ expanded.marginRight = values.right;
299
+ expanded.marginBottom = values.bottom;
300
+ expanded.marginLeft = values.left;
301
+ break;
302
+ }
303
+ case "padding": {
304
+ const values = parseShorthandValue(value);
305
+ expanded.paddingTop = values.top;
306
+ expanded.paddingRight = values.right;
307
+ expanded.paddingBottom = values.bottom;
308
+ expanded.paddingLeft = values.left;
309
+ break;
310
+ }
311
+ case "border": {
312
+ const values = convertBorderValue(value);
313
+ expanded.borderStyle = values.style;
314
+ expanded.borderWidth = values.width;
315
+ expanded.borderColor = values.color;
316
+ break;
317
+ }
318
+ case "borderTopLeftRadius":
319
+ case "borderTopRightRadius":
320
+ case "borderBottomLeftRadius":
321
+ case "borderBottomRightRadius":
322
+ expanded[key] = value;
323
+ if (styles.borderTopLeftRadius && styles.borderTopRightRadius && styles.borderBottomLeftRadius && styles.borderBottomRightRadius) {
324
+ const values = [
325
+ styles.borderTopLeftRadius,
326
+ styles.borderTopRightRadius,
327
+ styles.borderBottomLeftRadius,
328
+ styles.borderBottomRightRadius
329
+ ];
330
+ if (new Set(values).size === 1) expanded.borderRadius = values[0];
331
+ }
332
+ break;
333
+ default: expanded[key] = value;
334
+ }
335
+ }
336
+ return expanded;
337
+ }
338
+ /**
339
+ * Parses CSS shorthand value (1-4 values) into individual side values.
340
+ * Follows CSS specification for shorthand property value parsing.
341
+ *
342
+ * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')
343
+ * @returns Object with top, right, bottom, left values
344
+ */
345
+ function parseShorthandValue(value) {
346
+ const stringValue = String(value).trim();
347
+ const parts = stringValue.split(WHITE_SPACE_REGEX);
348
+ const len = parts.length;
349
+ if (len === 1) return {
350
+ top: parts[0],
351
+ right: parts[0],
352
+ bottom: parts[0],
353
+ left: parts[0]
354
+ };
355
+ if (len === 2) return {
356
+ top: parts[0],
357
+ right: parts[1],
358
+ bottom: parts[0],
359
+ left: parts[1]
360
+ };
361
+ if (len === 3) return {
362
+ top: parts[0],
363
+ right: parts[1],
364
+ bottom: parts[2],
365
+ left: parts[1]
366
+ };
367
+ if (len === 4) return {
368
+ top: parts[0],
369
+ right: parts[1],
370
+ bottom: parts[2],
371
+ left: parts[3]
372
+ };
373
+ return {
374
+ top: stringValue,
375
+ right: stringValue,
376
+ bottom: stringValue,
377
+ left: stringValue
378
+ };
379
+ }
380
+ function convertBorderValue(value) {
381
+ const stringValue = String(value).trim();
382
+ const parts = stringValue.split(WHITE_SPACE_REGEX);
383
+ switch (parts.length) {
384
+ case 1: return {
385
+ style: "solid",
386
+ width: parts[0],
387
+ color: "black"
388
+ };
389
+ case 2: return {
390
+ style: parts[1],
391
+ width: parts[0],
392
+ color: "black"
393
+ };
394
+ case 3: return {
395
+ style: parts[1],
396
+ width: parts[0],
397
+ color: parts[2]
398
+ };
399
+ case 4: return {
400
+ style: parts[1],
401
+ width: parts[0],
402
+ color: parts[2]
403
+ };
404
+ default: return {
405
+ style: "solid",
406
+ width: stringValue,
407
+ color: "black"
408
+ };
409
+ }
410
+ }
411
+ /**
412
+ * Resolves conflicts between reset styles and inline styles by expanding
413
+ * shorthand properties (margin, padding) to longhand before merging.
414
+ * This prevents shorthand properties from overriding specific longhand properties.
415
+ *
416
+ * @param resetStyles - Base reset styles that may contain shorthand properties
417
+ * @param inlineStyles - Inline styles that should override reset styles
418
+ * @returns Merged styles with inline styles taking precedence
419
+ */
420
+ function resolveConflictingStyles(resetStyles, inlineStyles) {
421
+ const expandedResetStyles = expandShorthandProperties(resetStyles);
422
+ const expandedInlineStyles = expandShorthandProperties(inlineStyles);
423
+ return {
424
+ ...expandedResetStyles,
425
+ ...expandedInlineStyles
426
+ };
427
+ }
157
428
 
158
429
  //#endregion
159
430
  //#region src/extensions/body.tsx
@@ -187,12 +458,12 @@ const Body = EmailNode.create({
187
458
  0
188
459
  ];
189
460
  },
190
- renderToReactEmail({ children, node, styles }) {
461
+ renderToReactEmail({ children, node, style }) {
191
462
  const inlineStyles = inlineCssToJs(node.attrs?.style);
192
463
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
193
464
  className: node.attrs?.class || void 0,
194
465
  style: {
195
- ...styles.reset,
466
+ ...style,
196
467
  ...inlineStyles
197
468
  },
198
469
  children
@@ -200,6 +471,92 @@ const Body = EmailNode.create({
200
471
  }
201
472
  });
202
473
 
474
+ //#endregion
475
+ //#region src/extensions/bold.ts
476
+ /**
477
+ * Matches bold text via `**` as input.
478
+ */
479
+ const starInputRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
480
+ /**
481
+ * Matches bold text via `**` while pasting.
482
+ */
483
+ const starPasteRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
484
+ /**
485
+ * Matches bold text via `__` as input.
486
+ */
487
+ const underscoreInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
488
+ /**
489
+ * Matches bold text via `__` while pasting.
490
+ */
491
+ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
492
+ /**
493
+ * This extension allows you to mark text as bold.
494
+ * @see https://tiptap.dev/api/marks/bold
495
+ */
496
+ const Bold = __tiptap_core.Mark.create({
497
+ name: "bold",
498
+ addOptions() {
499
+ return { HTMLAttributes: {} };
500
+ },
501
+ parseHTML() {
502
+ return [
503
+ { tag: "strong" },
504
+ {
505
+ tag: "b",
506
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
507
+ },
508
+ {
509
+ style: "font-weight=400",
510
+ clearMark: (mark) => mark.type.name === this.name
511
+ }
512
+ ];
513
+ },
514
+ renderHTML({ HTMLAttributes }) {
515
+ return [
516
+ "strong",
517
+ (0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
518
+ 0
519
+ ];
520
+ },
521
+ addCommands() {
522
+ return {
523
+ setBold: () => ({ commands }) => {
524
+ return commands.setMark(this.name);
525
+ },
526
+ toggleBold: () => ({ commands }) => {
527
+ return commands.toggleMark(this.name);
528
+ },
529
+ unsetBold: () => ({ commands }) => {
530
+ return commands.unsetMark(this.name);
531
+ }
532
+ };
533
+ },
534
+ addKeyboardShortcuts() {
535
+ return {
536
+ "Mod-b": () => this.editor.commands.toggleBold(),
537
+ "Mod-B": () => this.editor.commands.toggleBold()
538
+ };
539
+ },
540
+ addInputRules() {
541
+ return [(0, __tiptap_core.markInputRule)({
542
+ find: starInputRegex,
543
+ type: this.type
544
+ }), (0, __tiptap_core.markInputRule)({
545
+ find: underscoreInputRegex,
546
+ type: this.type
547
+ })];
548
+ },
549
+ addPasteRules() {
550
+ return [(0, __tiptap_core.markPasteRule)({
551
+ find: starPasteRegex,
552
+ type: this.type
553
+ }), (0, __tiptap_core.markPasteRule)({
554
+ find: underscorePasteRegex,
555
+ type: this.type
556
+ })];
557
+ }
558
+ });
559
+
203
560
  //#endregion
204
561
  //#region src/extensions/button.tsx
205
562
  const Button = EmailNode.create({
@@ -262,7 +619,7 @@ const Button = EmailNode.create({
262
619
  }
263
620
  };
264
621
  },
265
- renderToReactEmail({ children, node, styles }) {
622
+ renderToReactEmail({ children, node, style }) {
266
623
  const inlineStyles = inlineCssToJs(node.attrs?.style);
267
624
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Row, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Column, {
268
625
  align: node.attrs?.align || node.attrs?.alignment,
@@ -270,8 +627,7 @@ const Button = EmailNode.create({
270
627
  className: node.attrs?.class || void 0,
271
628
  href: node.attrs?.href,
272
629
  style: {
273
- ...styles.reset,
274
- ...styles.button,
630
+ ...style,
275
631
  ...inlineStyles
276
632
  },
277
633
  children
@@ -280,6 +636,48 @@ const Button = EmailNode.create({
280
636
  }
281
637
  });
282
638
 
639
+ //#endregion
640
+ //#region src/extensions/class-attribute.tsx
641
+ const ClassAttribute = __tiptap_core.Extension.create({
642
+ name: "classAttribute",
643
+ addOptions() {
644
+ return {
645
+ types: [],
646
+ class: []
647
+ };
648
+ },
649
+ addGlobalAttributes() {
650
+ return [{
651
+ types: this.options.types,
652
+ attributes: { class: {
653
+ default: "",
654
+ parseHTML: (element) => element.className || "",
655
+ renderHTML: (attributes) => {
656
+ return attributes.class ? { class: attributes.class } : {};
657
+ }
658
+ } }
659
+ }];
660
+ },
661
+ addCommands() {
662
+ return {
663
+ unsetClass: () => ({ commands }) => {
664
+ return this.options.types.every((type) => commands.resetAttributes(type, "class"));
665
+ },
666
+ setClass: (classList) => ({ commands }) => {
667
+ return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
668
+ }
669
+ };
670
+ },
671
+ addKeyboardShortcuts() {
672
+ return { Enter: ({ editor }) => {
673
+ requestAnimationFrame(() => {
674
+ editor.commands.resetAttributes("paragraph", "class");
675
+ });
676
+ return false;
677
+ } };
678
+ }
679
+ });
680
+
283
681
  //#endregion
284
682
  //#region src/utils/prism-utils.ts
285
683
  const publicURL = "/styles/prism";
@@ -458,6 +856,25 @@ const CodeBlockPrism = EmailNode.from(__tiptap_extension_code_block.default.exte
458
856
  ]
459
857
  ];
460
858
  },
859
+ addKeyboardShortcuts() {
860
+ return {
861
+ ...this.parent?.(),
862
+ "Mod-a": ({ editor }) => {
863
+ const { state } = editor;
864
+ const { selection } = state;
865
+ const { $from } = selection;
866
+ for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
867
+ const blockStart = $from.start(depth);
868
+ const blockEnd = $from.end(depth);
869
+ if (selection.from === blockStart && selection.to === blockEnd) return false;
870
+ const tr = state.tr.setSelection(__tiptap_pm_state.TextSelection.create(state.doc, blockStart, blockEnd));
871
+ editor.view.dispatch(tr);
872
+ return true;
873
+ }
874
+ return false;
875
+ }
876
+ };
877
+ },
461
878
  addProseMirrorPlugins() {
462
879
  return [...this.parent?.() || [], PrismPlugin({
463
880
  name: this.name,
@@ -465,7 +882,7 @@ const CodeBlockPrism = EmailNode.from(__tiptap_extension_code_block.default.exte
465
882
  defaultTheme: this.options.defaultTheme
466
883
  })];
467
884
  }
468
- }), ({ node, styles }) => {
885
+ }), ({ node, style }) => {
469
886
  const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
470
887
  const userTheme = __react_email_components[node.attrs?.theme];
471
888
  const theme = userTheme ? {
@@ -489,7 +906,7 @@ const CodeBlockPrism = EmailNode.from(__tiptap_extension_code_block.default.exte
489
906
  theme,
490
907
  style: {
491
908
  width: "auto",
492
- ...styles.codeBlock
909
+ ...style
493
910
  }
494
911
  });
495
912
  });
@@ -526,12 +943,12 @@ const Div = EmailNode.create({
526
943
  addAttributes() {
527
944
  return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
528
945
  },
529
- renderToReactEmail({ children, node, styles }) {
946
+ renderToReactEmail({ children, node, style }) {
530
947
  const inlineStyles = inlineCssToJs(node.attrs?.style);
531
948
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
532
949
  className: node.attrs?.class || void 0,
533
950
  style: {
534
- ...styles.reset,
951
+ ...style,
535
952
  ...inlineStyles
536
953
  },
537
954
  children
@@ -539,6 +956,206 @@ const Div = EmailNode.create({
539
956
  }
540
957
  });
541
958
 
959
+ //#endregion
960
+ //#region src/extensions/max-nesting.ts
961
+ const MaxNesting = __tiptap_core.Extension.create({
962
+ name: "maxNesting",
963
+ addOptions() {
964
+ return {
965
+ maxDepth: 3,
966
+ nodeTypes: void 0
967
+ };
968
+ },
969
+ addProseMirrorPlugins() {
970
+ const { maxDepth, nodeTypes } = this.options;
971
+ if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
972
+ return [new __tiptap_pm_state.Plugin({
973
+ key: new __tiptap_pm_state.PluginKey("maxNesting"),
974
+ appendTransaction(transactions, _oldState, newState) {
975
+ if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
976
+ const rangesToLift = [];
977
+ newState.doc.descendants((node, pos) => {
978
+ let depth = 0;
979
+ let currentPos = pos;
980
+ let currentNode = node;
981
+ while (currentNode && depth <= maxDepth) {
982
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
983
+ const $pos = newState.doc.resolve(currentPos);
984
+ if ($pos.depth === 0) break;
985
+ currentPos = $pos.before($pos.depth);
986
+ currentNode = newState.doc.nodeAt(currentPos);
987
+ }
988
+ if (depth > maxDepth) {
989
+ const $pos = newState.doc.resolve(pos);
990
+ if ($pos.depth > 0) {
991
+ const range = $pos.blockRange();
992
+ if (range && "canReplace" in newState.schema.nodes.doc && typeof newState.schema.nodes.doc.canReplace === "function" && newState.schema.nodes.doc.canReplace(range.start - 1, range.end + 1, newState.doc.slice(range.start, range.end).content)) rangesToLift.push({
993
+ range,
994
+ target: range.start - 1
995
+ });
996
+ }
997
+ }
998
+ });
999
+ if (rangesToLift.length === 0) return null;
1000
+ const tr = newState.tr;
1001
+ for (let i = rangesToLift.length - 1; i >= 0; i--) {
1002
+ const { range, target } = rangesToLift[i];
1003
+ tr.lift(range, target);
1004
+ }
1005
+ return tr;
1006
+ },
1007
+ filterTransaction(tr) {
1008
+ if (!tr.docChanged) return true;
1009
+ let wouldCreateDeepNesting = false;
1010
+ const newDoc = tr.doc;
1011
+ newDoc.descendants((node, pos) => {
1012
+ if (wouldCreateDeepNesting) return false;
1013
+ let depth = 0;
1014
+ let currentPos = pos;
1015
+ let currentNode = node;
1016
+ while (currentNode && depth <= maxDepth) {
1017
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
1018
+ const $pos = newDoc.resolve(currentPos);
1019
+ if ($pos.depth === 0) break;
1020
+ currentPos = $pos.before($pos.depth);
1021
+ currentNode = newDoc.nodeAt(currentPos);
1022
+ }
1023
+ if (depth > maxDepth) {
1024
+ wouldCreateDeepNesting = true;
1025
+ return false;
1026
+ }
1027
+ });
1028
+ return !wouldCreateDeepNesting;
1029
+ }
1030
+ })];
1031
+ }
1032
+ });
1033
+
1034
+ //#endregion
1035
+ //#region src/extensions/placeholder.ts
1036
+ const Placeholder = __tiptap_extension_placeholder.default.configure({
1037
+ placeholder: ({ node }) => {
1038
+ if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
1039
+ return "Press '/' for commands";
1040
+ },
1041
+ includeChildren: true
1042
+ });
1043
+
1044
+ //#endregion
1045
+ //#region src/extensions/preserved-style.ts
1046
+ const PreservedStyle = __tiptap_core.Mark.create({
1047
+ name: "preservedStyle",
1048
+ addAttributes() {
1049
+ return { style: {
1050
+ default: null,
1051
+ parseHTML: (element) => element.getAttribute("style"),
1052
+ renderHTML: (attributes) => {
1053
+ if (!attributes.style) return {};
1054
+ return { style: attributes.style };
1055
+ }
1056
+ } };
1057
+ },
1058
+ parseHTML() {
1059
+ return [{
1060
+ tag: "span[style]",
1061
+ getAttrs: (element) => {
1062
+ if (typeof element === "string") return false;
1063
+ const style = element.getAttribute("style");
1064
+ if (style && hasPreservableStyles(style)) return { style };
1065
+ return false;
1066
+ }
1067
+ }];
1068
+ },
1069
+ renderHTML({ HTMLAttributes }) {
1070
+ return [
1071
+ "span",
1072
+ (0, __tiptap_core.mergeAttributes)(HTMLAttributes),
1073
+ 0
1074
+ ];
1075
+ }
1076
+ });
1077
+ const LINK_INDICATOR_STYLES = [
1078
+ "color",
1079
+ "text-decoration",
1080
+ "text-decoration-line",
1081
+ "text-decoration-color",
1082
+ "text-decoration-style"
1083
+ ];
1084
+ function parseStyleString(styleString) {
1085
+ const temp = document.createElement("div");
1086
+ temp.style.cssText = styleString;
1087
+ return temp.style;
1088
+ }
1089
+ function hasBackground(style) {
1090
+ const bgColor = style.backgroundColor;
1091
+ const bg = style.background;
1092
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1093
+ if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1094
+ return false;
1095
+ }
1096
+ function hasPreservableStyles(styleString) {
1097
+ return processStylesForUnlink(styleString) !== null;
1098
+ }
1099
+ /**
1100
+ * Processes styles when unlinking:
1101
+ * - Has background (button-like): preserve all styles
1102
+ * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1103
+ */
1104
+ function processStylesForUnlink(styleString) {
1105
+ if (!styleString) return null;
1106
+ const style = parseStyleString(styleString);
1107
+ if (hasBackground(style)) return styleString;
1108
+ const filtered = [];
1109
+ for (let i = 0; i < style.length; i++) {
1110
+ const prop = style[i];
1111
+ if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1112
+ const value = style.getPropertyValue(prop);
1113
+ if (value) filtered.push(`${prop}: ${value}`);
1114
+ }
1115
+ return filtered.length > 0 ? filtered.join("; ") : null;
1116
+ }
1117
+
1118
+ //#endregion
1119
+ //#region src/extensions/preview-text.ts
1120
+ const PreviewText = __tiptap_core.Node.create({
1121
+ name: "previewText",
1122
+ group: "block",
1123
+ selectable: false,
1124
+ draggable: false,
1125
+ atom: true,
1126
+ addOptions() {
1127
+ return { HTMLAttributes: {} };
1128
+ },
1129
+ addStorage() {
1130
+ return { previewText: null };
1131
+ },
1132
+ renderHTML() {
1133
+ return ["div", { style: "display: none" }];
1134
+ },
1135
+ parseHTML() {
1136
+ return [{
1137
+ tag: "div[data-skip-in-text=\"true\"]",
1138
+ getAttrs: (node) => {
1139
+ if (typeof node === "string") return false;
1140
+ const element = node;
1141
+ let directText = "";
1142
+ for (const child of element.childNodes) if (child.nodeType === 3) directText += child.textContent || "";
1143
+ const cleanText = directText.trim();
1144
+ if (cleanText) this.storage.previewText = cleanText;
1145
+ return false;
1146
+ }
1147
+ }, {
1148
+ tag: "span.preheader",
1149
+ getAttrs: (node) => {
1150
+ if (typeof node === "string") return false;
1151
+ const preheaderText = node.textContent?.trim();
1152
+ if (preheaderText) this.storage.previewText = preheaderText;
1153
+ return false;
1154
+ }
1155
+ }];
1156
+ }
1157
+ });
1158
+
542
1159
  //#endregion
543
1160
  //#region src/utils/get-text-alignment.ts
544
1161
  function getTextAlignment(alignment) {
@@ -582,14 +1199,14 @@ const Section = EmailNode.create({
582
1199
  });
583
1200
  } };
584
1201
  },
585
- renderToReactEmail({ children, node, styles }) {
1202
+ renderToReactEmail({ children, node, style }) {
586
1203
  const inlineStyles = inlineCssToJs(node.attrs?.style);
587
1204
  const textAlign = node.attrs?.align || node.attrs?.alignment;
588
1205
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Section, {
589
1206
  className: node.attrs?.class || void 0,
590
1207
  align: textAlign,
591
1208
  style: {
592
- ...styles.section,
1209
+ ...style,
593
1210
  ...inlineStyles,
594
1211
  ...getTextAlignment(textAlign)
595
1212
  },
@@ -599,9 +1216,1546 @@ const Section = EmailNode.create({
599
1216
  });
600
1217
 
601
1218
  //#endregion
1219
+ //#region src/extensions/style-attribute.tsx
1220
+ const StyleAttribute = __tiptap_core.Extension.create({
1221
+ name: "styleAttribute",
1222
+ priority: 101,
1223
+ addOptions() {
1224
+ return {
1225
+ types: [],
1226
+ style: []
1227
+ };
1228
+ },
1229
+ addGlobalAttributes() {
1230
+ return [{
1231
+ types: this.options.types,
1232
+ attributes: { style: {
1233
+ default: "",
1234
+ parseHTML: (element) => element.getAttribute("style") || "",
1235
+ renderHTML: (attributes) => {
1236
+ return { style: attributes.style ?? "" };
1237
+ }
1238
+ } }
1239
+ }];
1240
+ },
1241
+ addCommands() {
1242
+ return {
1243
+ unsetStyle: () => ({ commands }) => {
1244
+ return this.options.types.every((type) => commands.resetAttributes(type, "style"));
1245
+ },
1246
+ setStyle: (style) => ({ commands }) => {
1247
+ return this.options.types.every((type) => commands.updateAttributes(type, { style }));
1248
+ }
1249
+ };
1250
+ },
1251
+ addKeyboardShortcuts() {
1252
+ return { Enter: ({ editor }) => {
1253
+ const { state } = editor.view;
1254
+ const { selection } = state;
1255
+ const { $from } = selection;
1256
+ const textBefore = $from.nodeBefore?.text || "";
1257
+ if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
1258
+ requestAnimationFrame(() => {
1259
+ editor.commands.resetAttributes("paragraph", "style");
1260
+ });
1261
+ return false;
1262
+ } };
1263
+ }
1264
+ });
1265
+
1266
+ //#endregion
1267
+ //#region src/extensions/sup.ts
1268
+ /**
1269
+ * This extension allows you to mark text as superscript.
1270
+ * @see https://tiptap.dev/api/marks/superscript
1271
+ */
1272
+ const Sup = __tiptap_core.Mark.create({
1273
+ name: "sup",
1274
+ addOptions() {
1275
+ return { HTMLAttributes: {} };
1276
+ },
1277
+ parseHTML() {
1278
+ return [{ tag: "sup" }];
1279
+ },
1280
+ renderHTML({ HTMLAttributes }) {
1281
+ return [
1282
+ "sup",
1283
+ (0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
1284
+ 0
1285
+ ];
1286
+ },
1287
+ addCommands() {
1288
+ return {
1289
+ setSup: () => ({ commands }) => {
1290
+ return commands.setMark(this.name);
1291
+ },
1292
+ toggleSup: () => ({ commands }) => {
1293
+ return commands.toggleMark(this.name);
1294
+ },
1295
+ unsetSup: () => ({ commands }) => {
1296
+ return commands.unsetMark(this.name);
1297
+ }
1298
+ };
1299
+ }
1300
+ });
1301
+
1302
+ //#endregion
1303
+ //#region src/extensions/table.tsx
1304
+ const Table = EmailNode.create({
1305
+ name: "table",
1306
+ group: "block",
1307
+ content: "tableRow+",
1308
+ isolating: true,
1309
+ tableRole: "table",
1310
+ addAttributes() {
1311
+ return { ...createStandardAttributes([
1312
+ ...TABLE_ATTRIBUTES,
1313
+ ...LAYOUT_ATTRIBUTES,
1314
+ ...COMMON_HTML_ATTRIBUTES
1315
+ ]) };
1316
+ },
1317
+ parseHTML() {
1318
+ return [{
1319
+ tag: "table",
1320
+ getAttrs: (node) => {
1321
+ if (typeof node === "string") return false;
1322
+ const element = node;
1323
+ const attrs = {};
1324
+ Array.from(element.attributes).forEach((attr) => {
1325
+ attrs[attr.name] = attr.value;
1326
+ });
1327
+ return attrs;
1328
+ }
1329
+ }];
1330
+ },
1331
+ renderHTML({ HTMLAttributes }) {
1332
+ return [
1333
+ "table",
1334
+ (0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
1335
+ [
1336
+ "tbody",
1337
+ {},
1338
+ 0
1339
+ ]
1340
+ ];
1341
+ },
1342
+ renderToReactEmail({ children, node, style }) {
1343
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1344
+ const alignment = node.attrs?.align || node.attrs?.alignment;
1345
+ const width = node.attrs?.width;
1346
+ const centeringStyles = alignment === "center" ? {
1347
+ marginLeft: "auto",
1348
+ marginRight: "auto"
1349
+ } : {};
1350
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Section, {
1351
+ className: node.attrs?.class || void 0,
1352
+ align: alignment,
1353
+ style: resolveConflictingStyles(style, {
1354
+ ...inlineStyles,
1355
+ ...centeringStyles
1356
+ }),
1357
+ ...width !== void 0 ? { width } : {},
1358
+ children
1359
+ });
1360
+ }
1361
+ });
1362
+ const TableRow = EmailNode.create({
1363
+ name: "tableRow",
1364
+ group: "tableRow",
1365
+ content: "(tableCell | tableHeader)+",
1366
+ addAttributes() {
1367
+ return { ...createStandardAttributes([
1368
+ ...TABLE_CELL_ATTRIBUTES,
1369
+ ...LAYOUT_ATTRIBUTES,
1370
+ ...COMMON_HTML_ATTRIBUTES
1371
+ ]) };
1372
+ },
1373
+ parseHTML() {
1374
+ return [{
1375
+ tag: "tr",
1376
+ getAttrs: (node) => {
1377
+ if (typeof node === "string") return false;
1378
+ const element = node;
1379
+ const attrs = {};
1380
+ Array.from(element.attributes).forEach((attr) => {
1381
+ attrs[attr.name] = attr.value;
1382
+ });
1383
+ return attrs;
1384
+ }
1385
+ }];
1386
+ },
1387
+ renderHTML({ HTMLAttributes }) {
1388
+ return [
1389
+ "tr",
1390
+ HTMLAttributes,
1391
+ 0
1392
+ ];
1393
+ },
1394
+ renderToReactEmail({ children, node, style }) {
1395
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1396
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
1397
+ className: node.attrs?.class || void 0,
1398
+ style: {
1399
+ ...style,
1400
+ ...inlineStyles
1401
+ },
1402
+ children
1403
+ });
1404
+ }
1405
+ });
1406
+ const TableCell = EmailNode.create({
1407
+ name: "tableCell",
1408
+ group: "tableCell",
1409
+ content: "block+",
1410
+ isolating: true,
1411
+ addAttributes() {
1412
+ return { ...createStandardAttributes([
1413
+ ...TABLE_CELL_ATTRIBUTES,
1414
+ ...LAYOUT_ATTRIBUTES,
1415
+ ...COMMON_HTML_ATTRIBUTES
1416
+ ]) };
1417
+ },
1418
+ parseHTML() {
1419
+ return [{
1420
+ tag: "td",
1421
+ getAttrs: (node) => {
1422
+ if (typeof node === "string") return false;
1423
+ const element = node;
1424
+ const attrs = {};
1425
+ Array.from(element.attributes).forEach((attr) => {
1426
+ attrs[attr.name] = attr.value;
1427
+ });
1428
+ return attrs;
1429
+ }
1430
+ }];
1431
+ },
1432
+ renderHTML({ HTMLAttributes }) {
1433
+ return [
1434
+ "td",
1435
+ HTMLAttributes,
1436
+ 0
1437
+ ];
1438
+ },
1439
+ renderToReactEmail({ children, node, style }) {
1440
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1441
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Column, {
1442
+ className: node.attrs?.class || void 0,
1443
+ align: node.attrs?.align || node.attrs?.alignment,
1444
+ style: {
1445
+ ...style,
1446
+ ...inlineStyles
1447
+ },
1448
+ children
1449
+ });
1450
+ }
1451
+ });
1452
+ const TableHeader = __tiptap_core.Node.create({
1453
+ name: "tableHeader",
1454
+ group: "tableCell",
1455
+ content: "block+",
1456
+ isolating: true,
1457
+ addAttributes() {
1458
+ return { ...createStandardAttributes([
1459
+ ...TABLE_HEADER_ATTRIBUTES,
1460
+ ...TABLE_CELL_ATTRIBUTES,
1461
+ ...LAYOUT_ATTRIBUTES,
1462
+ ...COMMON_HTML_ATTRIBUTES
1463
+ ]) };
1464
+ },
1465
+ parseHTML() {
1466
+ return [{
1467
+ tag: "th",
1468
+ getAttrs: (node) => {
1469
+ if (typeof node === "string") return false;
1470
+ const element = node;
1471
+ const attrs = {};
1472
+ Array.from(element.attributes).forEach((attr) => {
1473
+ attrs[attr.name] = attr.value;
1474
+ });
1475
+ return attrs;
1476
+ }
1477
+ }];
1478
+ },
1479
+ renderHTML({ HTMLAttributes }) {
1480
+ return [
1481
+ "th",
1482
+ HTMLAttributes,
1483
+ 0
1484
+ ];
1485
+ }
1486
+ });
1487
+
1488
+ //#endregion
1489
+ //#region src/extensions/uppercase.ts
1490
+ const Uppercase = __tiptap_core.Mark.create({
1491
+ name: "uppercase",
1492
+ addOptions() {
1493
+ return { HTMLAttributes: {} };
1494
+ },
1495
+ parseHTML() {
1496
+ return [{
1497
+ tag: "span",
1498
+ getAttrs: (node) => {
1499
+ if (node.style.textTransform === "uppercase") return {};
1500
+ return false;
1501
+ }
1502
+ }];
1503
+ },
1504
+ renderHTML({ HTMLAttributes }) {
1505
+ return [
1506
+ "span",
1507
+ (0, __tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1508
+ 0
1509
+ ];
1510
+ },
1511
+ addCommands() {
1512
+ return {
1513
+ setUppercase: () => ({ commands }) => {
1514
+ return commands.setMark(this.name);
1515
+ },
1516
+ toggleUppercase: () => ({ commands }) => {
1517
+ return commands.toggleMark(this.name);
1518
+ },
1519
+ unsetUppercase: () => ({ commands }) => {
1520
+ return commands.unsetMark(this.name);
1521
+ }
1522
+ };
1523
+ }
1524
+ });
1525
+
1526
+ //#endregion
1527
+ //#region src/extensions/columns.tsx
1528
+ const COLUMN_PARENT_TYPES = [
1529
+ "twoColumns",
1530
+ "threeColumns",
1531
+ "fourColumns"
1532
+ ];
1533
+ const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
1534
+ const MAX_COLUMNS_DEPTH = 3;
1535
+ function getColumnsDepth(doc, from) {
1536
+ const $from = doc.resolve(from);
1537
+ let depth = 0;
1538
+ for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
1539
+ return depth;
1540
+ }
1541
+ const VARIANTS = [
1542
+ {
1543
+ name: "twoColumns",
1544
+ columnCount: 2,
1545
+ content: "columnsColumn columnsColumn",
1546
+ dataType: "two-columns"
1547
+ },
1548
+ {
1549
+ name: "threeColumns",
1550
+ columnCount: 3,
1551
+ content: "columnsColumn columnsColumn columnsColumn",
1552
+ dataType: "three-columns"
1553
+ },
1554
+ {
1555
+ name: "fourColumns",
1556
+ columnCount: 4,
1557
+ content: "columnsColumn{4}",
1558
+ dataType: "four-columns"
1559
+ }
1560
+ ];
1561
+ const NODE_TYPE_MAP = {
1562
+ 2: "twoColumns",
1563
+ 3: "threeColumns",
1564
+ 4: "fourColumns"
1565
+ };
1566
+ function createColumnsNode(config, includeCommands) {
1567
+ return EmailNode.create({
1568
+ name: config.name,
1569
+ group: "block",
1570
+ content: config.content,
1571
+ isolating: true,
1572
+ defining: true,
1573
+ addAttributes() {
1574
+ return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
1575
+ },
1576
+ parseHTML() {
1577
+ return [{ tag: `div[data-type="${config.dataType}"]` }];
1578
+ },
1579
+ renderHTML({ HTMLAttributes }) {
1580
+ return [
1581
+ "div",
1582
+ (0, __tiptap_core.mergeAttributes)({
1583
+ "data-type": config.dataType,
1584
+ class: "node-columns"
1585
+ }, HTMLAttributes),
1586
+ 0
1587
+ ];
1588
+ },
1589
+ ...includeCommands && { addCommands() {
1590
+ return { insertColumns: (count) => ({ commands, state }) => {
1591
+ if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
1592
+ const nodeType = NODE_TYPE_MAP[count];
1593
+ const children = Array.from({ length: count }, () => ({
1594
+ type: "columnsColumn",
1595
+ content: [{
1596
+ type: "paragraph",
1597
+ content: []
1598
+ }]
1599
+ }));
1600
+ return commands.insertContent({
1601
+ type: nodeType,
1602
+ content: children
1603
+ });
1604
+ } };
1605
+ } },
1606
+ renderToReactEmail({ children, node, style }) {
1607
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1608
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Row, {
1609
+ className: node.attrs?.class || void 0,
1610
+ style: {
1611
+ ...style,
1612
+ ...inlineStyles
1613
+ },
1614
+ children
1615
+ });
1616
+ }
1617
+ });
1618
+ }
1619
+ const TwoColumns = createColumnsNode(VARIANTS[0], true);
1620
+ const ThreeColumns = createColumnsNode(VARIANTS[1], false);
1621
+ const FourColumns = createColumnsNode(VARIANTS[2], false);
1622
+ const ColumnsColumn = EmailNode.create({
1623
+ name: "columnsColumn",
1624
+ group: "columnsColumn",
1625
+ content: "block+",
1626
+ isolating: true,
1627
+ addAttributes() {
1628
+ return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
1629
+ },
1630
+ parseHTML() {
1631
+ return [{ tag: "div[data-type=\"column\"]" }];
1632
+ },
1633
+ renderHTML({ HTMLAttributes }) {
1634
+ return [
1635
+ "div",
1636
+ (0, __tiptap_core.mergeAttributes)({
1637
+ "data-type": "column",
1638
+ class: "node-column"
1639
+ }, HTMLAttributes),
1640
+ 0
1641
+ ];
1642
+ },
1643
+ addKeyboardShortcuts() {
1644
+ return {
1645
+ Backspace: ({ editor }) => {
1646
+ const { state } = editor;
1647
+ const { selection } = state;
1648
+ const { empty, $from } = selection;
1649
+ if (!empty) return false;
1650
+ for (let depth = $from.depth; depth >= 1; depth--) {
1651
+ if ($from.pos !== $from.start(depth)) break;
1652
+ const indexInParent = $from.index(depth - 1);
1653
+ if (indexInParent === 0) continue;
1654
+ const prevNode = $from.node(depth - 1).child(indexInParent - 1);
1655
+ if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
1656
+ const deleteFrom = $from.before(depth) - prevNode.nodeSize;
1657
+ const deleteTo = $from.before(depth);
1658
+ editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
1659
+ return true;
1660
+ }
1661
+ break;
1662
+ }
1663
+ return false;
1664
+ },
1665
+ "Mod-a": ({ editor }) => {
1666
+ const { state } = editor;
1667
+ const { $from } = state.selection;
1668
+ for (let d = $from.depth; d > 0; d--) {
1669
+ if ($from.node(d).type.name !== "columnsColumn") continue;
1670
+ const columnStart = $from.start(d);
1671
+ const columnEnd = $from.end(d);
1672
+ const { from, to } = state.selection;
1673
+ if (from === columnStart && to === columnEnd) return false;
1674
+ editor.view.dispatch(state.tr.setSelection(__tiptap_pm_state.TextSelection.create(state.doc, columnStart, columnEnd)));
1675
+ return true;
1676
+ }
1677
+ return false;
1678
+ }
1679
+ };
1680
+ },
1681
+ renderToReactEmail({ children, node, style }) {
1682
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1683
+ const width = node.attrs?.width;
1684
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_email_components.Column, {
1685
+ className: node.attrs?.class || void 0,
1686
+ style: {
1687
+ ...style,
1688
+ ...inlineStyles,
1689
+ ...width ? { width } : {}
1690
+ },
1691
+ children
1692
+ });
1693
+ }
1694
+ });
1695
+
1696
+ //#endregion
1697
+ //#region src/extensions/index.ts
1698
+ const coreExtensions = [
1699
+ __tiptap_starter_kit.StarterKit.configure({
1700
+ undoRedo: false,
1701
+ heading: false,
1702
+ link: false,
1703
+ underline: false,
1704
+ trailingNode: false,
1705
+ bold: false,
1706
+ gapcursor: false,
1707
+ listItem: {},
1708
+ bulletList: { HTMLAttributes: { class: "node-bulletList" } },
1709
+ paragraph: { HTMLAttributes: { class: "node-paragraph" } },
1710
+ orderedList: { HTMLAttributes: { class: "node-orderedList" } },
1711
+ blockquote: { HTMLAttributes: { class: "node-blockquote" } },
1712
+ codeBlock: false,
1713
+ code: { HTMLAttributes: {
1714
+ class: "node-inlineCode",
1715
+ spellcheck: "false"
1716
+ } },
1717
+ horizontalRule: false,
1718
+ dropcursor: {
1719
+ color: "#61a8f8",
1720
+ class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
1721
+ width: 4
1722
+ }
1723
+ }),
1724
+ CodeBlockPrism.configure({
1725
+ defaultLanguage: "javascript",
1726
+ HTMLAttributes: { class: "prism node-codeBlock" }
1727
+ }),
1728
+ Placeholder,
1729
+ PreviewText,
1730
+ Bold,
1731
+ Sup,
1732
+ Uppercase,
1733
+ PreservedStyle,
1734
+ Table,
1735
+ TableRow,
1736
+ TableCell,
1737
+ TableHeader,
1738
+ Body,
1739
+ Div,
1740
+ Button,
1741
+ Section,
1742
+ AlignmentAttribute.configure({ types: [
1743
+ "heading",
1744
+ "paragraph",
1745
+ "image",
1746
+ "blockquote",
1747
+ "codeBlock",
1748
+ "bulletList",
1749
+ "orderedList",
1750
+ "listItem",
1751
+ "button",
1752
+ "youtube",
1753
+ "twitter",
1754
+ "table",
1755
+ "tableRow",
1756
+ "tableCell",
1757
+ "tableHeader",
1758
+ "columnsColumn"
1759
+ ] }),
1760
+ StyleAttribute.configure({ types: [
1761
+ "heading",
1762
+ "paragraph",
1763
+ "image",
1764
+ "blockquote",
1765
+ "codeBlock",
1766
+ "bulletList",
1767
+ "orderedList",
1768
+ "listItem",
1769
+ "button",
1770
+ "youtube",
1771
+ "twitter",
1772
+ "horizontalRule",
1773
+ "footer",
1774
+ "section",
1775
+ "div",
1776
+ "body",
1777
+ "table",
1778
+ "tableRow",
1779
+ "tableCell",
1780
+ "tableHeader",
1781
+ "columnsColumn",
1782
+ "link"
1783
+ ] }),
1784
+ ClassAttribute.configure({ types: [
1785
+ "heading",
1786
+ "paragraph",
1787
+ "image",
1788
+ "blockquote",
1789
+ "bulletList",
1790
+ "orderedList",
1791
+ "listItem",
1792
+ "button",
1793
+ "youtube",
1794
+ "twitter",
1795
+ "horizontalRule",
1796
+ "footer",
1797
+ "section",
1798
+ "div",
1799
+ "body",
1800
+ "table",
1801
+ "tableRow",
1802
+ "tableCell",
1803
+ "tableHeader",
1804
+ "columnsColumn",
1805
+ "link"
1806
+ ] }),
1807
+ MaxNesting.configure({
1808
+ maxDepth: 50,
1809
+ nodeTypes: [
1810
+ "section",
1811
+ "bulletList",
1812
+ "orderedList"
1813
+ ]
1814
+ })
1815
+ ];
1816
+
1817
+ //#endregion
1818
+ //#region src/utils/set-text-alignment.ts
1819
+ function setTextAlignment(editor, alignment) {
1820
+ const { from, to } = editor.state.selection;
1821
+ const tr = editor.state.tr;
1822
+ editor.state.doc.nodesBetween(from, to, (node, pos) => {
1823
+ if (node.isTextblock) {
1824
+ const prop = "align" in node.attrs ? "align" : "alignment";
1825
+ tr.setNodeMarkup(pos, null, {
1826
+ ...node.attrs,
1827
+ [prop]: alignment
1828
+ });
1829
+ }
1830
+ });
1831
+ editor.view.dispatch(tr);
1832
+ }
1833
+
1834
+ //#endregion
1835
+ //#region src/ui/bubble-menu/context.tsx
1836
+ const BubbleMenuContext = react.createContext(null);
1837
+ function useBubbleMenuContext() {
1838
+ const context = react.useContext(BubbleMenuContext);
1839
+ if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
1840
+ return context;
1841
+ }
1842
+
1843
+ //#endregion
1844
+ //#region src/ui/bubble-menu/item.tsx
1845
+ function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
1846
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1847
+ type: "button",
1848
+ "aria-label": name,
1849
+ "aria-pressed": isActive,
1850
+ className,
1851
+ "data-re-bubble-menu-item": "",
1852
+ "data-item": name,
1853
+ ...isActive ? { "data-active": "" } : {},
1854
+ onMouseDown: (e) => e.preventDefault(),
1855
+ onClick: onCommand,
1856
+ ...rest,
1857
+ children
1858
+ });
1859
+ }
1860
+
1861
+ //#endregion
1862
+ //#region src/ui/bubble-menu/align-center.tsx
1863
+ function BubbleMenuAlignCenter({ className, children }) {
1864
+ const { editor } = useBubbleMenuContext();
1865
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1866
+ name: "align-center",
1867
+ isActive: (0, __tiptap_react.useEditorState)({
1868
+ editor,
1869
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
1870
+ }),
1871
+ onCommand: () => setTextAlignment(editor, "center"),
1872
+ className,
1873
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignCenterIcon, {})
1874
+ });
1875
+ }
1876
+
1877
+ //#endregion
1878
+ //#region src/ui/bubble-menu/align-left.tsx
1879
+ function BubbleMenuAlignLeft({ className, children }) {
1880
+ const { editor } = useBubbleMenuContext();
1881
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1882
+ name: "align-left",
1883
+ isActive: (0, __tiptap_react.useEditorState)({
1884
+ editor,
1885
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
1886
+ }),
1887
+ onCommand: () => setTextAlignment(editor, "left"),
1888
+ className,
1889
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignLeftIcon, {})
1890
+ });
1891
+ }
1892
+
1893
+ //#endregion
1894
+ //#region src/ui/bubble-menu/align-right.tsx
1895
+ function BubbleMenuAlignRight({ className, children }) {
1896
+ const { editor } = useBubbleMenuContext();
1897
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1898
+ name: "align-right",
1899
+ isActive: (0, __tiptap_react.useEditorState)({
1900
+ editor,
1901
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
1902
+ }),
1903
+ onCommand: () => setTextAlignment(editor, "right"),
1904
+ className,
1905
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignRightIcon, {})
1906
+ });
1907
+ }
1908
+
1909
+ //#endregion
1910
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
1911
+ function createMarkBubbleItem(config) {
1912
+ function MarkBubbleItem({ className, children }) {
1913
+ const { editor } = useBubbleMenuContext();
1914
+ const isActive = (0, __tiptap_react.useEditorState)({
1915
+ editor,
1916
+ selector: ({ editor: editor$1 }) => {
1917
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
1918
+ return editor$1?.isActive(config.activeName) ?? false;
1919
+ }
1920
+ });
1921
+ const handleCommand = () => {
1922
+ const chain = editor.chain().focus();
1923
+ const method = chain[config.command];
1924
+ if (method) method.call(chain).run();
1925
+ };
1926
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
1927
+ name: config.name,
1928
+ isActive,
1929
+ onCommand: handleCommand,
1930
+ className,
1931
+ children: children ?? config.icon
1932
+ });
1933
+ }
1934
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
1935
+ return MarkBubbleItem;
1936
+ }
1937
+
1938
+ //#endregion
1939
+ //#region src/ui/bubble-menu/bold.tsx
1940
+ const BubbleMenuBold = createMarkBubbleItem({
1941
+ name: "bold",
1942
+ activeName: "bold",
1943
+ command: "toggleBold",
1944
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BoldIcon, {})
1945
+ });
1946
+
1947
+ //#endregion
1948
+ //#region src/ui/bubble-menu/code.tsx
1949
+ const BubbleMenuCode = createMarkBubbleItem({
1950
+ name: "code",
1951
+ activeName: "code",
1952
+ command: "toggleCode",
1953
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CodeIcon, {})
1954
+ });
1955
+
1956
+ //#endregion
1957
+ //#region src/ui/bubble-menu/group.tsx
1958
+ function BubbleMenuItemGroup({ className, children }) {
1959
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
1960
+ className,
1961
+ "data-re-bubble-menu-group": "",
1962
+ children
1963
+ });
1964
+ }
1965
+
1966
+ //#endregion
1967
+ //#region src/ui/bubble-menu/italic.tsx
1968
+ const BubbleMenuItalic = createMarkBubbleItem({
1969
+ name: "italic",
1970
+ activeName: "italic",
1971
+ command: "toggleItalic",
1972
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ItalicIcon, {})
1973
+ });
1974
+
1975
+ //#endregion
1976
+ //#region src/ui/bubble-menu/utils.ts
1977
+ const SAFE_PROTOCOLS = new Set([
1978
+ "http:",
1979
+ "https:",
1980
+ "mailto:",
1981
+ "tel:"
1982
+ ]);
1983
+ /**
1984
+ * Basic URL validation and auto-prefixing.
1985
+ * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
1986
+ * Returns the valid URL string or null.
1987
+ */
1988
+ function getUrlFromString(str) {
1989
+ if (str === "#") return str;
1990
+ try {
1991
+ const url = new URL(str);
1992
+ if (SAFE_PROTOCOLS.has(url.protocol)) return str;
1993
+ return null;
1994
+ } catch {}
1995
+ try {
1996
+ if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
1997
+ } catch {}
1998
+ return null;
1999
+ }
2000
+ function setLinkHref(editor, href) {
2001
+ if (href.length === 0) {
2002
+ editor.chain().unsetLink().run();
2003
+ return;
2004
+ }
2005
+ const { from, to } = editor.state.selection;
2006
+ if (from === to) {
2007
+ editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
2008
+ from,
2009
+ to
2010
+ }).run();
2011
+ return;
2012
+ }
2013
+ editor.chain().setLink({ href }).run();
2014
+ }
2015
+ function focusEditor(editor) {
2016
+ setTimeout(() => {
2017
+ editor.commands.focus();
2018
+ }, 0);
2019
+ }
2020
+
2021
+ //#endregion
2022
+ //#region src/ui/bubble-menu/link-selector.tsx
2023
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children, open: controlledOpen, onOpenChange }) {
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 = (0, __tiptap_react.useEditorState)({
2033
+ editor,
2034
+ selector: ({ editor: editor$1 }) => ({
2035
+ isLinkActive: editor$1?.isActive("link") ?? false,
2036
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
2037
+ currentHref: editor$1?.getAttributes("link").href || ""
2038
+ })
2039
+ });
2040
+ const setIsOpenRef = react.useRef(setIsOpen);
2041
+ setIsOpenRef.current = setIsOpen;
2042
+ react.useEffect(() => {
2043
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
2044
+ setIsOpenRef.current(true);
2045
+ });
2046
+ return () => {
2047
+ setIsOpenRef.current(false);
2048
+ subscription.unsubscribe();
2049
+ };
2050
+ }, []);
2051
+ if (!editorState) return null;
2052
+ const handleOpenLink = () => {
2053
+ setIsOpen(!isOpen);
2054
+ };
2055
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2056
+ "data-re-link-selector": "",
2057
+ ...isOpen ? { "data-open": "" } : {},
2058
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
2059
+ className,
2060
+ children: [showToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2061
+ type: "button",
2062
+ "aria-expanded": isOpen,
2063
+ "aria-haspopup": "true",
2064
+ "aria-label": "Add link",
2065
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
2066
+ "data-re-link-selector-trigger": "",
2067
+ onClick: handleOpenLink,
2068
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
2069
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkForm, {
2070
+ editor,
2071
+ currentHref: editorState.currentHref,
2072
+ validateUrl,
2073
+ onLinkApply,
2074
+ onLinkRemove,
2075
+ setIsOpen,
2076
+ children
2077
+ })]
2078
+ });
2079
+ }
2080
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
2081
+ const inputRef = react.useRef(null);
2082
+ const formRef = react.useRef(null);
2083
+ const displayHref = currentHref === "#" ? "" : currentHref;
2084
+ const [inputValue, setInputValue] = react.useState(displayHref);
2085
+ react.useEffect(() => {
2086
+ const timeoutId = setTimeout(() => {
2087
+ inputRef.current?.focus();
2088
+ }, 0);
2089
+ return () => clearTimeout(timeoutId);
2090
+ }, []);
2091
+ react.useEffect(() => {
2092
+ const handleKeyDown = (event) => {
2093
+ if (event.key === "Escape") {
2094
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
2095
+ setIsOpen(false);
2096
+ }
2097
+ };
2098
+ const handleClickOutside = (event) => {
2099
+ if (formRef.current && !formRef.current.contains(event.target)) {
2100
+ const form = formRef.current;
2101
+ const submitEvent = new Event("submit", {
2102
+ bubbles: true,
2103
+ cancelable: true
2104
+ });
2105
+ form.dispatchEvent(submitEvent);
2106
+ setIsOpen(false);
2107
+ }
2108
+ };
2109
+ document.addEventListener("mousedown", handleClickOutside);
2110
+ window.addEventListener("keydown", handleKeyDown);
2111
+ return () => {
2112
+ window.removeEventListener("keydown", handleKeyDown);
2113
+ document.removeEventListener("mousedown", handleClickOutside);
2114
+ };
2115
+ }, [editor, setIsOpen]);
2116
+ function handleSubmit(e) {
2117
+ e.preventDefault();
2118
+ const value = inputValue.trim();
2119
+ if (value === "") {
2120
+ setLinkHref(editor, "");
2121
+ setIsOpen(false);
2122
+ focusEditor(editor);
2123
+ onLinkRemove?.();
2124
+ return;
2125
+ }
2126
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
2127
+ if (!finalValue) {
2128
+ setLinkHref(editor, "");
2129
+ setIsOpen(false);
2130
+ focusEditor(editor);
2131
+ onLinkRemove?.();
2132
+ return;
2133
+ }
2134
+ setLinkHref(editor, finalValue);
2135
+ setIsOpen(false);
2136
+ focusEditor(editor);
2137
+ onLinkApply?.(finalValue);
2138
+ }
2139
+ function handleUnlink(e) {
2140
+ e.stopPropagation();
2141
+ setLinkHref(editor, "");
2142
+ setIsOpen(false);
2143
+ focusEditor(editor);
2144
+ onLinkRemove?.();
2145
+ }
2146
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2147
+ ref: formRef,
2148
+ "data-re-link-selector-form": "",
2149
+ onMouseDown: (e) => e.stopPropagation(),
2150
+ onClick: (e) => e.stopPropagation(),
2151
+ onKeyDown: (e) => e.stopPropagation(),
2152
+ onSubmit: handleSubmit,
2153
+ children: [
2154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2155
+ ref: inputRef,
2156
+ "data-re-link-selector-input": "",
2157
+ value: inputValue,
2158
+ onFocus: (e) => e.stopPropagation(),
2159
+ onChange: (e) => setInputValue(e.target.value),
2160
+ placeholder: "Paste a link",
2161
+ type: "text"
2162
+ }),
2163
+ children,
2164
+ displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2165
+ type: "button",
2166
+ "aria-label": "Remove link",
2167
+ "data-re-link-selector-unlink": "",
2168
+ onClick: handleUnlink,
2169
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
2170
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2171
+ type: "submit",
2172
+ "aria-label": "Apply link",
2173
+ "data-re-link-selector-apply": "",
2174
+ onMouseDown: (e) => e.stopPropagation(),
2175
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2176
+ })
2177
+ ]
2178
+ });
2179
+ }
2180
+
2181
+ //#endregion
2182
+ //#region src/ui/bubble-menu/node-selector.tsx
2183
+ const NodeSelectorContext = react.createContext(null);
2184
+ function useNodeSelectorContext() {
2185
+ const context = react.useContext(NodeSelectorContext);
2186
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
2187
+ return context;
2188
+ }
2189
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
2190
+ const { editor } = useBubbleMenuContext();
2191
+ const [uncontrolledOpen, setUncontrolledOpen] = react.useState(false);
2192
+ const isControlled = controlledOpen !== void 0;
2193
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2194
+ const setIsOpen = react.useCallback((value) => {
2195
+ if (!isControlled) setUncontrolledOpen(value);
2196
+ onOpenChange?.(value);
2197
+ }, [isControlled, onOpenChange]);
2198
+ const editorState = (0, __tiptap_react.useEditorState)({
2199
+ editor,
2200
+ selector: ({ editor: editor$1 }) => ({
2201
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
2202
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
2203
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
2204
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
2205
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
2206
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
2207
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
2208
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
2209
+ })
2210
+ });
2211
+ const allItems = react.useMemo(() => [
2212
+ {
2213
+ name: "Text",
2214
+ icon: lucide_react.TextIcon,
2215
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
2216
+ isActive: editorState?.isParagraphActive ?? false
2217
+ },
2218
+ {
2219
+ name: "Title",
2220
+ icon: lucide_react.Heading1,
2221
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
2222
+ isActive: editorState?.isHeading1Active ?? false
2223
+ },
2224
+ {
2225
+ name: "Subtitle",
2226
+ icon: lucide_react.Heading2,
2227
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
2228
+ isActive: editorState?.isHeading2Active ?? false
2229
+ },
2230
+ {
2231
+ name: "Heading",
2232
+ icon: lucide_react.Heading3,
2233
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
2234
+ isActive: editorState?.isHeading3Active ?? false
2235
+ },
2236
+ {
2237
+ name: "Bullet List",
2238
+ icon: lucide_react.List,
2239
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
2240
+ isActive: editorState?.isBulletListActive ?? false
2241
+ },
2242
+ {
2243
+ name: "Numbered List",
2244
+ icon: lucide_react.ListOrdered,
2245
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
2246
+ isActive: editorState?.isOrderedListActive ?? false
2247
+ },
2248
+ {
2249
+ name: "Quote",
2250
+ icon: lucide_react.TextQuote,
2251
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
2252
+ isActive: editorState?.isBlockquoteActive ?? false
2253
+ },
2254
+ {
2255
+ name: "Code",
2256
+ icon: lucide_react.Code,
2257
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2258
+ isActive: editorState?.isCodeBlockActive ?? false
2259
+ }
2260
+ ], [editor, editorState]);
2261
+ const items = react.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
2262
+ const activeItem = react.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
2263
+ const contextValue = react.useMemo(() => ({
2264
+ items,
2265
+ activeItem,
2266
+ isOpen,
2267
+ setIsOpen
2268
+ }), [
2269
+ items,
2270
+ activeItem,
2271
+ isOpen,
2272
+ setIsOpen
2273
+ ]);
2274
+ if (!editorState || items.length === 0) return null;
2275
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContext.Provider, {
2276
+ value: contextValue,
2277
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Root, {
2278
+ open: isOpen,
2279
+ onOpenChange: setIsOpen,
2280
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2281
+ "data-re-node-selector": "",
2282
+ ...isOpen ? { "data-open": "" } : {},
2283
+ className,
2284
+ children
2285
+ })
2286
+ })
2287
+ });
2288
+ }
2289
+ function NodeSelectorTrigger({ className, children }) {
2290
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
2291
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Trigger, {
2292
+ "data-re-node-selector-trigger": "",
2293
+ className,
2294
+ onClick: () => setIsOpen(!isOpen),
2295
+ 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, {})] })
2296
+ });
2297
+ }
2298
+ function NodeSelectorContent({ className, align = "start", children }) {
2299
+ const { items, setIsOpen } = useNodeSelectorContext();
2300
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__radix_ui_react_popover.Content, {
2301
+ align,
2302
+ "data-re-node-selector-content": "",
2303
+ className,
2304
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
2305
+ const Icon = item.icon;
2306
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2307
+ type: "button",
2308
+ "data-re-node-selector-item": "",
2309
+ ...item.isActive ? { "data-active": "" } : {},
2310
+ onClick: () => {
2311
+ item.command();
2312
+ setIsOpen(false);
2313
+ },
2314
+ children: [
2315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}),
2316
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.name }),
2317
+ item.isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2318
+ ]
2319
+ }, item.name);
2320
+ })
2321
+ });
2322
+ }
2323
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
2324
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(NodeSelectorRoot, {
2325
+ omit,
2326
+ open,
2327
+ onOpenChange,
2328
+ className,
2329
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContent, {})]
2330
+ });
2331
+ }
2332
+
2333
+ //#endregion
2334
+ //#region src/ui/bubble-menu/root.tsx
2335
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
2336
+ const { editor } = (0, __tiptap_react.useCurrentEditor)();
2337
+ if (!editor) return null;
2338
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__tiptap_react_menus.BubbleMenu, {
2339
+ editor,
2340
+ "data-re-bubble-menu": "",
2341
+ shouldShow: ({ editor: editor$1, view }) => {
2342
+ for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
2343
+ if (view.dom.classList.contains("dragging")) return false;
2344
+ return editor$1.view.state.selection.content().size > 0;
2345
+ },
2346
+ options: {
2347
+ placement,
2348
+ offset,
2349
+ onHide
2350
+ },
2351
+ className,
2352
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuContext.Provider, {
2353
+ value: { editor },
2354
+ children
2355
+ })
2356
+ });
2357
+ }
2358
+
2359
+ //#endregion
2360
+ //#region src/ui/bubble-menu/strike.tsx
2361
+ const BubbleMenuStrike = createMarkBubbleItem({
2362
+ name: "strike",
2363
+ activeName: "strike",
2364
+ command: "toggleStrike",
2365
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.StrikethroughIcon, {})
2366
+ });
2367
+
2368
+ //#endregion
2369
+ //#region src/ui/bubble-menu/underline.tsx
2370
+ const BubbleMenuUnderline = createMarkBubbleItem({
2371
+ name: "underline",
2372
+ activeName: "underline",
2373
+ command: "toggleUnderline",
2374
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnderlineIcon, {})
2375
+ });
2376
+
2377
+ //#endregion
2378
+ //#region src/ui/bubble-menu/uppercase.tsx
2379
+ const BubbleMenuUppercase = createMarkBubbleItem({
2380
+ name: "uppercase",
2381
+ activeName: "uppercase",
2382
+ command: "toggleUppercase",
2383
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CaseUpperIcon, {})
2384
+ });
2385
+
2386
+ //#endregion
2387
+ //#region src/ui/bubble-menu/default.tsx
2388
+ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset, onHide, className }) {
2389
+ const [isNodeSelectorOpen, setIsNodeSelectorOpen] = react.useState(false);
2390
+ const [isLinkSelectorOpen, setIsLinkSelectorOpen] = react.useState(false);
2391
+ const has = (item) => !excludeItems.includes(item);
2392
+ const handleNodeSelectorOpenChange = react.useCallback((open) => {
2393
+ setIsNodeSelectorOpen(open);
2394
+ if (open) setIsLinkSelectorOpen(false);
2395
+ }, []);
2396
+ const handleLinkSelectorOpenChange = react.useCallback((open) => {
2397
+ setIsLinkSelectorOpen(open);
2398
+ if (open) setIsNodeSelectorOpen(false);
2399
+ }, []);
2400
+ const handleHide = react.useCallback(() => {
2401
+ setIsNodeSelectorOpen(false);
2402
+ setIsLinkSelectorOpen(false);
2403
+ onHide?.();
2404
+ }, [onHide]);
2405
+ const hasFormattingItems = has("bold") || has("italic") || has("underline") || has("strike") || has("code") || has("uppercase");
2406
+ const hasAlignmentItems = has("align-left") || has("align-center") || has("align-right");
2407
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuRoot, {
2408
+ excludeNodes,
2409
+ placement,
2410
+ offset,
2411
+ onHide: handleHide,
2412
+ className,
2413
+ children: [
2414
+ has("node-selector") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuNodeSelector, {
2415
+ open: isNodeSelectorOpen,
2416
+ onOpenChange: handleNodeSelectorOpenChange
2417
+ }),
2418
+ has("link-selector") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuLinkSelector, {
2419
+ open: isLinkSelectorOpen,
2420
+ onOpenChange: handleLinkSelectorOpenChange
2421
+ }),
2422
+ hasFormattingItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuItemGroup, { children: [
2423
+ has("bold") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuBold, {}),
2424
+ has("italic") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItalic, {}),
2425
+ has("underline") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuUnderline, {}),
2426
+ has("strike") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuStrike, {}),
2427
+ has("code") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuCode, {}),
2428
+ has("uppercase") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuUppercase, {})
2429
+ ] }),
2430
+ hasAlignmentItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuItemGroup, { children: [
2431
+ has("align-left") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignLeft, {}),
2432
+ has("align-center") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignCenter, {}),
2433
+ has("align-right") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignRight, {})
2434
+ ] })
2435
+ ]
2436
+ });
2437
+ }
2438
+
2439
+ //#endregion
2440
+ //#region src/ui/bubble-menu/separator.tsx
2441
+ function BubbleMenuSeparator({ className }) {
2442
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
2443
+ className,
2444
+ "data-re-bubble-menu-separator": ""
2445
+ });
2446
+ }
2447
+
2448
+ //#endregion
2449
+ //#region src/ui/bubble-menu/index.ts
2450
+ const BubbleMenu = {
2451
+ Root: BubbleMenuRoot,
2452
+ ItemGroup: BubbleMenuItemGroup,
2453
+ Separator: BubbleMenuSeparator,
2454
+ Item: BubbleMenuItem,
2455
+ Bold: BubbleMenuBold,
2456
+ Italic: BubbleMenuItalic,
2457
+ Underline: BubbleMenuUnderline,
2458
+ Strike: BubbleMenuStrike,
2459
+ Code: BubbleMenuCode,
2460
+ Uppercase: BubbleMenuUppercase,
2461
+ AlignLeft: BubbleMenuAlignLeft,
2462
+ AlignCenter: BubbleMenuAlignCenter,
2463
+ AlignRight: BubbleMenuAlignRight,
2464
+ NodeSelector: Object.assign(BubbleMenuNodeSelector, {
2465
+ Root: NodeSelectorRoot,
2466
+ Trigger: NodeSelectorTrigger,
2467
+ Content: NodeSelectorContent
2468
+ }),
2469
+ LinkSelector: BubbleMenuLinkSelector,
2470
+ Default: BubbleMenuDefault
2471
+ };
2472
+
2473
+ //#endregion
2474
+ //#region src/ui/link-bubble-menu/context.tsx
2475
+ const LinkBubbleMenuContext = react.createContext(null);
2476
+ function useLinkBubbleMenuContext() {
2477
+ const context = react.useContext(LinkBubbleMenuContext);
2478
+ if (!context) throw new Error("LinkBubbleMenu compound components must be used within <LinkBubbleMenu.Root>");
2479
+ return context;
2480
+ }
2481
+
2482
+ //#endregion
2483
+ //#region src/ui/link-bubble-menu/edit-link.tsx
2484
+ function LinkBubbleMenuEditLink({ className, children, onClick,...rest }) {
2485
+ const { setIsEditing } = useLinkBubbleMenuContext();
2486
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2487
+ type: "button",
2488
+ "aria-label": "Edit link",
2489
+ "data-re-link-bm-item": "",
2490
+ "data-item": "edit-link",
2491
+ className,
2492
+ onMouseDown: (e) => e.preventDefault(),
2493
+ onClick: (e) => {
2494
+ onClick?.(e);
2495
+ setIsEditing(true);
2496
+ },
2497
+ ...rest,
2498
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PencilIcon, {})
2499
+ });
2500
+ }
2501
+
2502
+ //#endregion
2503
+ //#region src/ui/link-bubble-menu/form.tsx
2504
+ function LinkBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove, children }) {
2505
+ const { editor, linkHref, isEditing, setIsEditing } = useLinkBubbleMenuContext();
2506
+ const inputRef = react.useRef(null);
2507
+ const formRef = react.useRef(null);
2508
+ const displayHref = linkHref === "#" ? "" : linkHref;
2509
+ const [inputValue, setInputValue] = react.useState(displayHref);
2510
+ react.useEffect(() => {
2511
+ if (!isEditing) return;
2512
+ const timeoutId = setTimeout(() => {
2513
+ inputRef.current?.focus();
2514
+ }, 0);
2515
+ return () => clearTimeout(timeoutId);
2516
+ }, [isEditing]);
2517
+ react.useEffect(() => {
2518
+ if (!isEditing) return;
2519
+ const handleKeyDown = (event) => {
2520
+ if (event.key === "Escape") setIsEditing(false);
2521
+ };
2522
+ const handleClickOutside = (event) => {
2523
+ if (formRef.current && !formRef.current.contains(event.target)) {
2524
+ const form = formRef.current;
2525
+ const submitEvent = new Event("submit", {
2526
+ bubbles: true,
2527
+ cancelable: true
2528
+ });
2529
+ form.dispatchEvent(submitEvent);
2530
+ setIsEditing(false);
2531
+ }
2532
+ };
2533
+ document.addEventListener("mousedown", handleClickOutside);
2534
+ window.addEventListener("keydown", handleKeyDown);
2535
+ return () => {
2536
+ window.removeEventListener("keydown", handleKeyDown);
2537
+ document.removeEventListener("mousedown", handleClickOutside);
2538
+ };
2539
+ }, [isEditing, setIsEditing]);
2540
+ if (!isEditing) return null;
2541
+ function handleSubmit(e) {
2542
+ e.preventDefault();
2543
+ const value = inputValue.trim();
2544
+ if (value === "") {
2545
+ setLinkHref(editor, "");
2546
+ setIsEditing(false);
2547
+ focusEditor(editor);
2548
+ onLinkRemove?.();
2549
+ return;
2550
+ }
2551
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
2552
+ if (!finalValue) {
2553
+ setLinkHref(editor, "");
2554
+ setIsEditing(false);
2555
+ focusEditor(editor);
2556
+ onLinkRemove?.();
2557
+ return;
2558
+ }
2559
+ setLinkHref(editor, finalValue);
2560
+ setIsEditing(false);
2561
+ focusEditor(editor);
2562
+ onLinkApply?.(finalValue);
2563
+ }
2564
+ function handleUnlink(e) {
2565
+ e.stopPropagation();
2566
+ setLinkHref(editor, "");
2567
+ setIsEditing(false);
2568
+ focusEditor(editor);
2569
+ onLinkRemove?.();
2570
+ }
2571
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2572
+ ref: formRef,
2573
+ "data-re-link-bm-form": "",
2574
+ className,
2575
+ onMouseDown: (e) => e.stopPropagation(),
2576
+ onClick: (e) => e.stopPropagation(),
2577
+ onKeyDown: (e) => e.stopPropagation(),
2578
+ onSubmit: handleSubmit,
2579
+ children: [
2580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2581
+ ref: inputRef,
2582
+ "data-re-link-bm-input": "",
2583
+ value: inputValue,
2584
+ onFocus: (e) => e.stopPropagation(),
2585
+ onChange: (e) => setInputValue(e.target.value),
2586
+ placeholder: "Paste a link",
2587
+ type: "text"
2588
+ }),
2589
+ children,
2590
+ displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2591
+ type: "button",
2592
+ "aria-label": "Remove link",
2593
+ "data-re-link-bm-unlink": "",
2594
+ onClick: handleUnlink,
2595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
2596
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2597
+ type: "submit",
2598
+ "aria-label": "Apply link",
2599
+ "data-re-link-bm-apply": "",
2600
+ onMouseDown: (e) => e.stopPropagation(),
2601
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2602
+ })
2603
+ ]
2604
+ });
2605
+ }
2606
+
2607
+ //#endregion
2608
+ //#region src/ui/link-bubble-menu/open-link.tsx
2609
+ function LinkBubbleMenuOpenLink({ className, children,...rest }) {
2610
+ const { linkHref } = useLinkBubbleMenuContext();
2611
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2612
+ ...rest,
2613
+ href: linkHref,
2614
+ target: "_blank",
2615
+ rel: "noopener noreferrer",
2616
+ "aria-label": "Open link",
2617
+ "data-re-link-bm-item": "",
2618
+ "data-item": "open-link",
2619
+ className,
2620
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ExternalLinkIcon, {})
2621
+ });
2622
+ }
2623
+
2624
+ //#endregion
2625
+ //#region src/ui/link-bubble-menu/root.tsx
2626
+ function LinkBubbleMenuRoot({ onHide, placement = "bottom", offset = 8, className, children }) {
2627
+ const { editor } = (0, __tiptap_react.useCurrentEditor)();
2628
+ const [isEditing, setIsEditing] = react.useState(false);
2629
+ const linkHref = (0, __tiptap_react.useEditorState)({
2630
+ editor,
2631
+ selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
2632
+ });
2633
+ if (!editor) return null;
2634
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__tiptap_react_menus.BubbleMenu, {
2635
+ editor,
2636
+ "data-re-link-bm": "",
2637
+ shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
2638
+ options: {
2639
+ placement,
2640
+ offset,
2641
+ onHide: () => {
2642
+ setIsEditing(false);
2643
+ onHide?.();
2644
+ }
2645
+ },
2646
+ className,
2647
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuContext.Provider, {
2648
+ value: {
2649
+ editor,
2650
+ linkHref: linkHref ?? "",
2651
+ isEditing,
2652
+ setIsEditing
2653
+ },
2654
+ children
2655
+ })
2656
+ });
2657
+ }
2658
+
2659
+ //#endregion
2660
+ //#region src/ui/link-bubble-menu/toolbar.tsx
2661
+ function LinkBubbleMenuToolbar({ className, children }) {
2662
+ const { isEditing } = useLinkBubbleMenuContext();
2663
+ if (isEditing) return null;
2664
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2665
+ "data-re-link-bm-toolbar": "",
2666
+ className,
2667
+ children
2668
+ });
2669
+ }
2670
+
2671
+ //#endregion
2672
+ //#region src/ui/link-bubble-menu/unlink.tsx
2673
+ function LinkBubbleMenuUnlink({ className, children, onClick,...rest }) {
2674
+ const { editor } = useLinkBubbleMenuContext();
2675
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2676
+ type: "button",
2677
+ "aria-label": "Remove link",
2678
+ "data-re-link-bm-item": "",
2679
+ "data-item": "unlink",
2680
+ className,
2681
+ onMouseDown: (e) => e.preventDefault(),
2682
+ onClick: (e) => {
2683
+ onClick?.(e);
2684
+ editor.chain().focus().unsetLink().run();
2685
+ },
2686
+ ...rest,
2687
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
2688
+ });
2689
+ }
2690
+
2691
+ //#endregion
2692
+ //#region src/ui/link-bubble-menu/index.ts
2693
+ const LinkBubbleMenu = {
2694
+ Root: LinkBubbleMenuRoot,
2695
+ Toolbar: LinkBubbleMenuToolbar,
2696
+ Form: LinkBubbleMenuForm,
2697
+ EditLink: LinkBubbleMenuEditLink,
2698
+ Unlink: LinkBubbleMenuUnlink,
2699
+ OpenLink: LinkBubbleMenuOpenLink
2700
+ };
2701
+
2702
+ //#endregion
2703
+ exports.AlignmentAttribute = AlignmentAttribute;
602
2704
  exports.Body = Body;
2705
+ exports.Bold = Bold;
2706
+ exports.BubbleMenu = BubbleMenu;
2707
+ exports.BubbleMenuAlignCenter = BubbleMenuAlignCenter;
2708
+ exports.BubbleMenuAlignLeft = BubbleMenuAlignLeft;
2709
+ exports.BubbleMenuAlignRight = BubbleMenuAlignRight;
2710
+ exports.BubbleMenuBold = BubbleMenuBold;
2711
+ exports.BubbleMenuCode = BubbleMenuCode;
2712
+ exports.BubbleMenuDefault = BubbleMenuDefault;
2713
+ exports.BubbleMenuItalic = BubbleMenuItalic;
2714
+ exports.BubbleMenuItem = BubbleMenuItem;
2715
+ exports.BubbleMenuItemGroup = BubbleMenuItemGroup;
2716
+ exports.BubbleMenuLinkSelector = BubbleMenuLinkSelector;
2717
+ exports.BubbleMenuNodeSelector = BubbleMenuNodeSelector;
2718
+ exports.BubbleMenuRoot = BubbleMenuRoot;
2719
+ exports.BubbleMenuSeparator = BubbleMenuSeparator;
2720
+ exports.BubbleMenuStrike = BubbleMenuStrike;
2721
+ exports.BubbleMenuUnderline = BubbleMenuUnderline;
2722
+ exports.BubbleMenuUppercase = BubbleMenuUppercase;
603
2723
  exports.Button = Button;
2724
+ exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
2725
+ exports.ClassAttribute = ClassAttribute;
604
2726
  exports.CodeBlockPrism = CodeBlockPrism;
2727
+ exports.ColumnsColumn = ColumnsColumn;
605
2728
  exports.Div = Div;
606
2729
  exports.EmailNode = EmailNode;
607
- exports.Section = Section;
2730
+ exports.FourColumns = FourColumns;
2731
+ exports.LinkBubbleMenu = LinkBubbleMenu;
2732
+ exports.LinkBubbleMenuEditLink = LinkBubbleMenuEditLink;
2733
+ exports.LinkBubbleMenuForm = LinkBubbleMenuForm;
2734
+ exports.LinkBubbleMenuOpenLink = LinkBubbleMenuOpenLink;
2735
+ exports.LinkBubbleMenuRoot = LinkBubbleMenuRoot;
2736
+ exports.LinkBubbleMenuToolbar = LinkBubbleMenuToolbar;
2737
+ exports.LinkBubbleMenuUnlink = LinkBubbleMenuUnlink;
2738
+ exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
2739
+ exports.MaxNesting = MaxNesting;
2740
+ exports.NodeSelectorContent = NodeSelectorContent;
2741
+ exports.NodeSelectorRoot = NodeSelectorRoot;
2742
+ exports.NodeSelectorTrigger = NodeSelectorTrigger;
2743
+ exports.Placeholder = Placeholder;
2744
+ exports.PreservedStyle = PreservedStyle;
2745
+ exports.PreviewText = PreviewText;
2746
+ exports.Section = Section;
2747
+ exports.StyleAttribute = StyleAttribute;
2748
+ exports.Sup = Sup;
2749
+ exports.Table = Table;
2750
+ exports.TableCell = TableCell;
2751
+ exports.TableHeader = TableHeader;
2752
+ exports.TableRow = TableRow;
2753
+ exports.ThreeColumns = ThreeColumns;
2754
+ exports.TwoColumns = TwoColumns;
2755
+ exports.Uppercase = Uppercase;
2756
+ exports.coreExtensions = coreExtensions;
2757
+ exports.editorEventBus = editorEventBus;
2758
+ exports.getColumnsDepth = getColumnsDepth;
2759
+ exports.processStylesForUnlink = processStylesForUnlink;
2760
+ exports.setTextAlignment = setTextAlignment;
2761
+ exports.useLinkBubbleMenuContext = useLinkBubbleMenuContext;