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

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,4 +1,4 @@
1
- import { Node, findChildren, mergeAttributes } from "@tiptap/core";
1
+ import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as ReactEmailComponents from "@react-email/components";
4
4
  import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
@@ -7,6 +7,7 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
7
7
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
8
8
  import { fromHtml } from "hast-util-from-html";
9
9
  import Prism from "prismjs";
10
+ import Placeholder from "@tiptap/extension-placeholder";
10
11
 
11
12
  //#region src/core/email-node.ts
12
13
  var EmailNode = class EmailNode extends Node {
@@ -38,6 +39,61 @@ var EmailNode = class EmailNode extends Node {
38
39
  }
39
40
  };
40
41
 
42
+ //#endregion
43
+ //#region src/extensions/alignment-attribute.tsx
44
+ const AlignmentAttribute = Extension.create({
45
+ name: "alignmentAttribute",
46
+ addOptions() {
47
+ return {
48
+ types: [],
49
+ alignments: [
50
+ "left",
51
+ "center",
52
+ "right",
53
+ "justify"
54
+ ]
55
+ };
56
+ },
57
+ addGlobalAttributes() {
58
+ return [{
59
+ types: this.options.types,
60
+ attributes: { alignment: {
61
+ parseHTML: (element) => {
62
+ const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
63
+ if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
64
+ return null;
65
+ },
66
+ renderHTML: (attributes) => {
67
+ if (attributes.alignment === "left") return {};
68
+ return { alignment: attributes.alignment };
69
+ }
70
+ } }
71
+ }];
72
+ },
73
+ addCommands() {
74
+ return { setAlignment: (alignment) => ({ commands }) => {
75
+ if (!this.options.alignments.includes(alignment)) return false;
76
+ return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
77
+ } };
78
+ },
79
+ addKeyboardShortcuts() {
80
+ return {
81
+ Enter: () => {
82
+ const { from } = this.editor.state.selection;
83
+ const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
84
+ if (currentAlignment) requestAnimationFrame(() => {
85
+ this.editor.commands.setAlignment(currentAlignment);
86
+ });
87
+ return false;
88
+ },
89
+ "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
90
+ "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
91
+ "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
92
+ "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
93
+ };
94
+ }
95
+ });
96
+
41
97
  //#endregion
42
98
  //#region src/utils/attribute-helpers.ts
43
99
  /**
@@ -94,6 +150,14 @@ const LAYOUT_ATTRIBUTES = [
94
150
  "height"
95
151
  ];
96
152
  /**
153
+ * Table-specific HTML attributes used for table layout and styling.
154
+ */
155
+ const TABLE_ATTRIBUTES = [
156
+ "border",
157
+ "cellpadding",
158
+ "cellspacing"
159
+ ];
160
+ /**
97
161
  * Table cell-specific HTML attributes.
98
162
  */
99
163
  const TABLE_CELL_ATTRIBUTES = [
@@ -110,6 +174,7 @@ const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
110
174
 
111
175
  //#endregion
112
176
  //#region src/utils/styles.ts
177
+ const WHITE_SPACE_REGEX = /\s+/;
113
178
  const inlineCssToJs = (inlineStyle, options = {}) => {
114
179
  const styleObject = {};
115
180
  if (!inlineStyle || inlineStyle === "" || typeof inlineStyle === "object") return styleObject;
@@ -124,6 +189,157 @@ const inlineCssToJs = (inlineStyle, options = {}) => {
124
189
  });
125
190
  return styleObject;
126
191
  };
192
+ /**
193
+ * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.
194
+ * This prevents shorthand properties from overriding specific longhand properties in email clients.
195
+ *
196
+ * @param styles - Style object that may contain shorthand properties
197
+ * @returns New style object with shorthand properties expanded to longhand
198
+ *
199
+ * @example
200
+ * expandShorthandProperties({ margin: '0', paddingTop: '10px' })
201
+ * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }
202
+ */
203
+ function expandShorthandProperties(styles) {
204
+ if (!styles || typeof styles !== "object") return {};
205
+ const expanded = {};
206
+ for (const key in styles) {
207
+ const value = styles[key];
208
+ if (value === void 0 || value === null || value === "") continue;
209
+ switch (key) {
210
+ case "margin": {
211
+ const values = parseShorthandValue(value);
212
+ expanded.marginTop = values.top;
213
+ expanded.marginRight = values.right;
214
+ expanded.marginBottom = values.bottom;
215
+ expanded.marginLeft = values.left;
216
+ break;
217
+ }
218
+ case "padding": {
219
+ const values = parseShorthandValue(value);
220
+ expanded.paddingTop = values.top;
221
+ expanded.paddingRight = values.right;
222
+ expanded.paddingBottom = values.bottom;
223
+ expanded.paddingLeft = values.left;
224
+ break;
225
+ }
226
+ case "border": {
227
+ const values = convertBorderValue(value);
228
+ expanded.borderStyle = values.style;
229
+ expanded.borderWidth = values.width;
230
+ expanded.borderColor = values.color;
231
+ break;
232
+ }
233
+ case "borderTopLeftRadius":
234
+ case "borderTopRightRadius":
235
+ case "borderBottomLeftRadius":
236
+ case "borderBottomRightRadius":
237
+ expanded[key] = value;
238
+ if (styles.borderTopLeftRadius && styles.borderTopRightRadius && styles.borderBottomLeftRadius && styles.borderBottomRightRadius) {
239
+ const values = [
240
+ styles.borderTopLeftRadius,
241
+ styles.borderTopRightRadius,
242
+ styles.borderBottomLeftRadius,
243
+ styles.borderBottomRightRadius
244
+ ];
245
+ if (new Set(values).size === 1) expanded.borderRadius = values[0];
246
+ }
247
+ break;
248
+ default: expanded[key] = value;
249
+ }
250
+ }
251
+ return expanded;
252
+ }
253
+ /**
254
+ * Parses CSS shorthand value (1-4 values) into individual side values.
255
+ * Follows CSS specification for shorthand property value parsing.
256
+ *
257
+ * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')
258
+ * @returns Object with top, right, bottom, left values
259
+ */
260
+ function parseShorthandValue(value) {
261
+ const stringValue = String(value).trim();
262
+ const parts = stringValue.split(WHITE_SPACE_REGEX);
263
+ const len = parts.length;
264
+ if (len === 1) return {
265
+ top: parts[0],
266
+ right: parts[0],
267
+ bottom: parts[0],
268
+ left: parts[0]
269
+ };
270
+ if (len === 2) return {
271
+ top: parts[0],
272
+ right: parts[1],
273
+ bottom: parts[0],
274
+ left: parts[1]
275
+ };
276
+ if (len === 3) return {
277
+ top: parts[0],
278
+ right: parts[1],
279
+ bottom: parts[2],
280
+ left: parts[1]
281
+ };
282
+ if (len === 4) return {
283
+ top: parts[0],
284
+ right: parts[1],
285
+ bottom: parts[2],
286
+ left: parts[3]
287
+ };
288
+ return {
289
+ top: stringValue,
290
+ right: stringValue,
291
+ bottom: stringValue,
292
+ left: stringValue
293
+ };
294
+ }
295
+ function convertBorderValue(value) {
296
+ const stringValue = String(value).trim();
297
+ const parts = stringValue.split(WHITE_SPACE_REGEX);
298
+ switch (parts.length) {
299
+ case 1: return {
300
+ style: "solid",
301
+ width: parts[0],
302
+ color: "black"
303
+ };
304
+ case 2: return {
305
+ style: parts[1],
306
+ width: parts[0],
307
+ color: "black"
308
+ };
309
+ case 3: return {
310
+ style: parts[1],
311
+ width: parts[0],
312
+ color: parts[2]
313
+ };
314
+ case 4: return {
315
+ style: parts[1],
316
+ width: parts[0],
317
+ color: parts[2]
318
+ };
319
+ default: return {
320
+ style: "solid",
321
+ width: stringValue,
322
+ color: "black"
323
+ };
324
+ }
325
+ }
326
+ /**
327
+ * Resolves conflicts between reset styles and inline styles by expanding
328
+ * shorthand properties (margin, padding) to longhand before merging.
329
+ * This prevents shorthand properties from overriding specific longhand properties.
330
+ *
331
+ * @param resetStyles - Base reset styles that may contain shorthand properties
332
+ * @param inlineStyles - Inline styles that should override reset styles
333
+ * @returns Merged styles with inline styles taking precedence
334
+ */
335
+ function resolveConflictingStyles(resetStyles, inlineStyles) {
336
+ const expandedResetStyles = expandShorthandProperties(resetStyles);
337
+ const expandedInlineStyles = expandShorthandProperties(inlineStyles);
338
+ return {
339
+ ...expandedResetStyles,
340
+ ...expandedInlineStyles
341
+ };
342
+ }
127
343
 
128
344
  //#endregion
129
345
  //#region src/extensions/body.tsx
@@ -170,6 +386,92 @@ const Body = EmailNode.create({
170
386
  }
171
387
  });
172
388
 
389
+ //#endregion
390
+ //#region src/extensions/bold.ts
391
+ /**
392
+ * Matches bold text via `**` as input.
393
+ */
394
+ const starInputRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
395
+ /**
396
+ * Matches bold text via `**` while pasting.
397
+ */
398
+ const starPasteRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
399
+ /**
400
+ * Matches bold text via `__` as input.
401
+ */
402
+ const underscoreInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
403
+ /**
404
+ * Matches bold text via `__` while pasting.
405
+ */
406
+ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
407
+ /**
408
+ * This extension allows you to mark text as bold.
409
+ * @see https://tiptap.dev/api/marks/bold
410
+ */
411
+ const Bold = Mark.create({
412
+ name: "bold",
413
+ addOptions() {
414
+ return { HTMLAttributes: {} };
415
+ },
416
+ parseHTML() {
417
+ return [
418
+ { tag: "strong" },
419
+ {
420
+ tag: "b",
421
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
422
+ },
423
+ {
424
+ style: "font-weight=400",
425
+ clearMark: (mark) => mark.type.name === this.name
426
+ }
427
+ ];
428
+ },
429
+ renderHTML({ HTMLAttributes }) {
430
+ return [
431
+ "strong",
432
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
433
+ 0
434
+ ];
435
+ },
436
+ addCommands() {
437
+ return {
438
+ setBold: () => ({ commands }) => {
439
+ return commands.setMark(this.name);
440
+ },
441
+ toggleBold: () => ({ commands }) => {
442
+ return commands.toggleMark(this.name);
443
+ },
444
+ unsetBold: () => ({ commands }) => {
445
+ return commands.unsetMark(this.name);
446
+ }
447
+ };
448
+ },
449
+ addKeyboardShortcuts() {
450
+ return {
451
+ "Mod-b": () => this.editor.commands.toggleBold(),
452
+ "Mod-B": () => this.editor.commands.toggleBold()
453
+ };
454
+ },
455
+ addInputRules() {
456
+ return [markInputRule({
457
+ find: starInputRegex,
458
+ type: this.type
459
+ }), markInputRule({
460
+ find: underscoreInputRegex,
461
+ type: this.type
462
+ })];
463
+ },
464
+ addPasteRules() {
465
+ return [markPasteRule({
466
+ find: starPasteRegex,
467
+ type: this.type
468
+ }), markPasteRule({
469
+ find: underscorePasteRegex,
470
+ type: this.type
471
+ })];
472
+ }
473
+ });
474
+
173
475
  //#endregion
174
476
  //#region src/extensions/button.tsx
175
477
  const Button = EmailNode.create({
@@ -250,6 +552,48 @@ const Button = EmailNode.create({
250
552
  }
251
553
  });
252
554
 
555
+ //#endregion
556
+ //#region src/extensions/class-attribute.tsx
557
+ const ClassAttribute = Extension.create({
558
+ name: "classAttribute",
559
+ addOptions() {
560
+ return {
561
+ types: [],
562
+ class: []
563
+ };
564
+ },
565
+ addGlobalAttributes() {
566
+ return [{
567
+ types: this.options.types,
568
+ attributes: { class: {
569
+ default: "",
570
+ parseHTML: (element) => element.className || "",
571
+ renderHTML: (attributes) => {
572
+ return attributes.class ? { class: attributes.class } : {};
573
+ }
574
+ } }
575
+ }];
576
+ },
577
+ addCommands() {
578
+ return {
579
+ unsetClass: () => ({ commands }) => {
580
+ return this.options.types.every((type) => commands.resetAttributes(type, "class"));
581
+ },
582
+ setClass: (classList) => ({ commands }) => {
583
+ return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
584
+ }
585
+ };
586
+ },
587
+ addKeyboardShortcuts() {
588
+ return { Enter: ({ editor }) => {
589
+ requestAnimationFrame(() => {
590
+ editor.commands.resetAttributes("paragraph", "class");
591
+ });
592
+ return false;
593
+ } };
594
+ }
595
+ });
596
+
253
597
  //#endregion
254
598
  //#region src/utils/prism-utils.ts
255
599
  const publicURL = "/styles/prism";
@@ -509,6 +853,169 @@ const Div = EmailNode.create({
509
853
  }
510
854
  });
511
855
 
856
+ //#endregion
857
+ //#region src/extensions/max-nesting.ts
858
+ const MaxNesting = Extension.create({
859
+ name: "maxNesting",
860
+ addOptions() {
861
+ return {
862
+ maxDepth: 3,
863
+ nodeTypes: void 0
864
+ };
865
+ },
866
+ addProseMirrorPlugins() {
867
+ const { maxDepth, nodeTypes } = this.options;
868
+ if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
869
+ return [new Plugin({
870
+ key: new PluginKey("maxNesting"),
871
+ appendTransaction(transactions, _oldState, newState) {
872
+ if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
873
+ const rangesToLift = [];
874
+ newState.doc.descendants((node, pos) => {
875
+ let depth = 0;
876
+ let currentPos = pos;
877
+ let currentNode = node;
878
+ while (currentNode && depth <= maxDepth) {
879
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
880
+ const $pos = newState.doc.resolve(currentPos);
881
+ if ($pos.depth === 0) break;
882
+ currentPos = $pos.before($pos.depth);
883
+ currentNode = newState.doc.nodeAt(currentPos);
884
+ }
885
+ if (depth > maxDepth) {
886
+ const $pos = newState.doc.resolve(pos);
887
+ if ($pos.depth > 0) {
888
+ const range = $pos.blockRange();
889
+ 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({
890
+ range,
891
+ target: range.start - 1
892
+ });
893
+ }
894
+ }
895
+ });
896
+ if (rangesToLift.length === 0) return null;
897
+ const tr = newState.tr;
898
+ for (let i = rangesToLift.length - 1; i >= 0; i--) {
899
+ const { range, target } = rangesToLift[i];
900
+ tr.lift(range, target);
901
+ }
902
+ return tr;
903
+ },
904
+ filterTransaction(tr) {
905
+ if (!tr.docChanged) return true;
906
+ let wouldCreateDeepNesting = false;
907
+ const newDoc = tr.doc;
908
+ newDoc.descendants((node, pos) => {
909
+ if (wouldCreateDeepNesting) return false;
910
+ let depth = 0;
911
+ let currentPos = pos;
912
+ let currentNode = node;
913
+ while (currentNode && depth <= maxDepth) {
914
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
915
+ const $pos = newDoc.resolve(currentPos);
916
+ if ($pos.depth === 0) break;
917
+ currentPos = $pos.before($pos.depth);
918
+ currentNode = newDoc.nodeAt(currentPos);
919
+ }
920
+ if (depth > maxDepth) {
921
+ wouldCreateDeepNesting = true;
922
+ return false;
923
+ }
924
+ });
925
+ return !wouldCreateDeepNesting;
926
+ }
927
+ })];
928
+ }
929
+ });
930
+
931
+ //#endregion
932
+ //#region src/extensions/placeholder.ts
933
+ const createPlaceholderExtension = (options) => {
934
+ return Placeholder.configure({
935
+ placeholder: ({ node }) => {
936
+ if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
937
+ return "Press '/' for commands";
938
+ },
939
+ includeChildren: true,
940
+ ...options
941
+ });
942
+ };
943
+ const placeholder = createPlaceholderExtension();
944
+
945
+ //#endregion
946
+ //#region src/extensions/preserved-style.ts
947
+ const PreservedStyle = Mark.create({
948
+ name: "preservedStyle",
949
+ addAttributes() {
950
+ return { style: {
951
+ default: null,
952
+ parseHTML: (element) => element.getAttribute("style"),
953
+ renderHTML: (attributes) => {
954
+ if (!attributes.style) return {};
955
+ return { style: attributes.style };
956
+ }
957
+ } };
958
+ },
959
+ parseHTML() {
960
+ return [{
961
+ tag: "span[style]",
962
+ getAttrs: (element) => {
963
+ if (typeof element === "string") return false;
964
+ const style = element.getAttribute("style");
965
+ if (style && hasPreservableStyles(style)) return { style };
966
+ return false;
967
+ }
968
+ }];
969
+ },
970
+ renderHTML({ HTMLAttributes }) {
971
+ return [
972
+ "span",
973
+ mergeAttributes(HTMLAttributes),
974
+ 0
975
+ ];
976
+ }
977
+ });
978
+ const LINK_INDICATOR_STYLES = [
979
+ "color",
980
+ "text-decoration",
981
+ "text-decoration-line",
982
+ "text-decoration-color",
983
+ "text-decoration-style"
984
+ ];
985
+ function parseStyleString(styleString) {
986
+ const temp = document.createElement("div");
987
+ temp.style.cssText = styleString;
988
+ return temp.style;
989
+ }
990
+ function hasBackground(style) {
991
+ const bgColor = style.backgroundColor;
992
+ const bg = style.background;
993
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
994
+ if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
995
+ return false;
996
+ }
997
+ function hasPreservableStyles(styleString) {
998
+ return processStylesForUnlink(styleString) !== null;
999
+ }
1000
+ /**
1001
+ * Processes styles when unlinking:
1002
+ * - Has background (button-like): preserve all styles
1003
+ * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1004
+ */
1005
+ function processStylesForUnlink(styleString) {
1006
+ if (!styleString) return null;
1007
+ const style = parseStyleString(styleString);
1008
+ if (hasBackground(style)) return styleString;
1009
+ const filtered = [];
1010
+ for (let i = 0; i < style.length; i++) {
1011
+ const prop = style[i];
1012
+ if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1013
+ const value = style.getPropertyValue(prop);
1014
+ if (value) filtered.push(`${prop}: ${value}`);
1015
+ }
1016
+ return filtered.length > 0 ? filtered.join("; ") : null;
1017
+ }
1018
+
512
1019
  //#endregion
513
1020
  //#region src/utils/get-text-alignment.ts
514
1021
  function getTextAlignment(alignment) {
@@ -569,5 +1076,275 @@ const Section = EmailNode.create({
569
1076
  });
570
1077
 
571
1078
  //#endregion
572
- export { Body, Button, CodeBlockPrism, Div, EmailNode, Section };
1079
+ //#region src/extensions/style-attribute.tsx
1080
+ const StyleAttribute = Extension.create({
1081
+ name: "styleAttribute",
1082
+ priority: 101,
1083
+ addOptions() {
1084
+ return {
1085
+ types: [],
1086
+ style: []
1087
+ };
1088
+ },
1089
+ addGlobalAttributes() {
1090
+ return [{
1091
+ types: this.options.types,
1092
+ attributes: { style: {
1093
+ default: "",
1094
+ parseHTML: (element) => element.getAttribute("style") || "",
1095
+ renderHTML: (attributes) => {
1096
+ return { style: attributes.style ?? "" };
1097
+ }
1098
+ } }
1099
+ }];
1100
+ },
1101
+ addCommands() {
1102
+ return {
1103
+ unsetStyle: () => ({ commands }) => {
1104
+ return this.options.types.every((type) => commands.resetAttributes(type, "style"));
1105
+ },
1106
+ setStyle: (style) => ({ commands }) => {
1107
+ return this.options.types.every((type) => commands.updateAttributes(type, { style }));
1108
+ }
1109
+ };
1110
+ },
1111
+ addKeyboardShortcuts() {
1112
+ return { Enter: ({ editor }) => {
1113
+ const { state } = editor.view;
1114
+ const { selection } = state;
1115
+ const { $from } = selection;
1116
+ const textBefore = $from.nodeBefore?.text || "";
1117
+ if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
1118
+ requestAnimationFrame(() => {
1119
+ editor.commands.resetAttributes("paragraph", "style");
1120
+ });
1121
+ return false;
1122
+ } };
1123
+ }
1124
+ });
1125
+
1126
+ //#endregion
1127
+ //#region src/extensions/sup.ts
1128
+ /**
1129
+ * This extension allows you to mark text as superscript.
1130
+ * @see https://tiptap.dev/api/marks/superscript
1131
+ */
1132
+ const Sup = Mark.create({
1133
+ name: "sup",
1134
+ addOptions() {
1135
+ return { HTMLAttributes: {} };
1136
+ },
1137
+ parseHTML() {
1138
+ return [{ tag: "sup" }];
1139
+ },
1140
+ renderHTML({ HTMLAttributes }) {
1141
+ return [
1142
+ "sup",
1143
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1144
+ 0
1145
+ ];
1146
+ },
1147
+ addCommands() {
1148
+ return {
1149
+ setSup: () => ({ commands }) => {
1150
+ return commands.setMark(this.name);
1151
+ },
1152
+ toggleSup: () => ({ commands }) => {
1153
+ return commands.toggleMark(this.name);
1154
+ },
1155
+ unsetSup: () => ({ commands }) => {
1156
+ return commands.unsetMark(this.name);
1157
+ }
1158
+ };
1159
+ }
1160
+ });
1161
+
1162
+ //#endregion
1163
+ //#region src/extensions/table.tsx
1164
+ const Table = EmailNode.create({
1165
+ name: "table",
1166
+ group: "block",
1167
+ content: "tableRow+",
1168
+ isolating: true,
1169
+ tableRole: "table",
1170
+ addAttributes() {
1171
+ return { ...createStandardAttributes([
1172
+ ...TABLE_ATTRIBUTES,
1173
+ ...LAYOUT_ATTRIBUTES,
1174
+ ...COMMON_HTML_ATTRIBUTES
1175
+ ]) };
1176
+ },
1177
+ parseHTML() {
1178
+ return [{
1179
+ tag: "table",
1180
+ getAttrs: (node) => {
1181
+ if (typeof node === "string") return false;
1182
+ const element = node;
1183
+ const attrs = {};
1184
+ Array.from(element.attributes).forEach((attr) => {
1185
+ attrs[attr.name] = attr.value;
1186
+ });
1187
+ return attrs;
1188
+ }
1189
+ }];
1190
+ },
1191
+ renderHTML({ HTMLAttributes }) {
1192
+ return [
1193
+ "table",
1194
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1195
+ [
1196
+ "tbody",
1197
+ {},
1198
+ 0
1199
+ ]
1200
+ ];
1201
+ },
1202
+ renderToReactEmail({ children, node, styles }) {
1203
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1204
+ const alignment = node.attrs?.align || node.attrs?.alignment;
1205
+ const width = node.attrs?.width;
1206
+ const centeringStyles = alignment === "center" ? {
1207
+ marginLeft: "auto",
1208
+ marginRight: "auto"
1209
+ } : {};
1210
+ return /* @__PURE__ */ jsx(Section$1, {
1211
+ className: node.attrs?.class || void 0,
1212
+ align: alignment,
1213
+ style: resolveConflictingStyles(styles.reset, {
1214
+ ...inlineStyles,
1215
+ ...centeringStyles
1216
+ }),
1217
+ ...width !== void 0 ? { width } : {},
1218
+ children
1219
+ });
1220
+ }
1221
+ });
1222
+ const TableRow = EmailNode.create({
1223
+ name: "tableRow",
1224
+ group: "tableRow",
1225
+ content: "(tableCell | tableHeader)+",
1226
+ addAttributes() {
1227
+ return { ...createStandardAttributes([
1228
+ ...TABLE_CELL_ATTRIBUTES,
1229
+ ...LAYOUT_ATTRIBUTES,
1230
+ ...COMMON_HTML_ATTRIBUTES
1231
+ ]) };
1232
+ },
1233
+ parseHTML() {
1234
+ return [{
1235
+ tag: "tr",
1236
+ getAttrs: (node) => {
1237
+ if (typeof node === "string") return false;
1238
+ const element = node;
1239
+ const attrs = {};
1240
+ Array.from(element.attributes).forEach((attr) => {
1241
+ attrs[attr.name] = attr.value;
1242
+ });
1243
+ return attrs;
1244
+ }
1245
+ }];
1246
+ },
1247
+ renderHTML({ HTMLAttributes }) {
1248
+ return [
1249
+ "tr",
1250
+ HTMLAttributes,
1251
+ 0
1252
+ ];
1253
+ },
1254
+ renderToReactEmail({ children, node, styles }) {
1255
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1256
+ return /* @__PURE__ */ jsx("tr", {
1257
+ className: node.attrs?.class || void 0,
1258
+ style: {
1259
+ ...styles.reset,
1260
+ ...inlineStyles
1261
+ },
1262
+ children
1263
+ });
1264
+ }
1265
+ });
1266
+ const TableCell = EmailNode.create({
1267
+ name: "tableCell",
1268
+ group: "tableCell",
1269
+ content: "block+",
1270
+ isolating: true,
1271
+ addAttributes() {
1272
+ return { ...createStandardAttributes([
1273
+ ...TABLE_CELL_ATTRIBUTES,
1274
+ ...LAYOUT_ATTRIBUTES,
1275
+ ...COMMON_HTML_ATTRIBUTES
1276
+ ]) };
1277
+ },
1278
+ parseHTML() {
1279
+ return [{
1280
+ tag: "td",
1281
+ getAttrs: (node) => {
1282
+ if (typeof node === "string") return false;
1283
+ const element = node;
1284
+ const attrs = {};
1285
+ Array.from(element.attributes).forEach((attr) => {
1286
+ attrs[attr.name] = attr.value;
1287
+ });
1288
+ return attrs;
1289
+ }
1290
+ }];
1291
+ },
1292
+ renderHTML({ HTMLAttributes }) {
1293
+ return [
1294
+ "td",
1295
+ HTMLAttributes,
1296
+ 0
1297
+ ];
1298
+ },
1299
+ renderToReactEmail({ children, node, styles }) {
1300
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1301
+ return /* @__PURE__ */ jsx(Column, {
1302
+ className: node.attrs?.class || void 0,
1303
+ align: node.attrs?.align || node.attrs?.alignment,
1304
+ style: {
1305
+ ...styles.reset,
1306
+ ...inlineStyles
1307
+ },
1308
+ children
1309
+ });
1310
+ }
1311
+ });
1312
+ const TableHeader = Node.create({
1313
+ name: "tableHeader",
1314
+ group: "tableCell",
1315
+ content: "block+",
1316
+ isolating: true,
1317
+ addAttributes() {
1318
+ return { ...createStandardAttributes([
1319
+ ...TABLE_HEADER_ATTRIBUTES,
1320
+ ...TABLE_CELL_ATTRIBUTES,
1321
+ ...LAYOUT_ATTRIBUTES,
1322
+ ...COMMON_HTML_ATTRIBUTES
1323
+ ]) };
1324
+ },
1325
+ parseHTML() {
1326
+ return [{
1327
+ tag: "th",
1328
+ getAttrs: (node) => {
1329
+ if (typeof node === "string") return false;
1330
+ const element = node;
1331
+ const attrs = {};
1332
+ Array.from(element.attributes).forEach((attr) => {
1333
+ attrs[attr.name] = attr.value;
1334
+ });
1335
+ return attrs;
1336
+ }
1337
+ }];
1338
+ },
1339
+ renderHTML({ HTMLAttributes }) {
1340
+ return [
1341
+ "th",
1342
+ HTMLAttributes,
1343
+ 0
1344
+ ];
1345
+ }
1346
+ });
1347
+
1348
+ //#endregion
1349
+ export { AlignmentAttribute, Body, Bold, Button, ClassAttribute, CodeBlockPrism, Div, EmailNode, MaxNesting, PreservedStyle, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, createPlaceholderExtension, placeholder, processStylesForUnlink };
573
1350
  //# sourceMappingURL=index.mjs.map