@react-email/editor 0.0.0-experimental.24 → 0.0.0-experimental.26

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.
@@ -1 +1 @@
1
- {"version":3,"file":"columns-CUxUEHje.mjs","names":[],"sources":["../src/core/event-bus.ts","../src/utils/styles.ts","../src/core/serializer/email-node.ts","../src/utils/attribute-helpers.ts","../src/extensions/columns.tsx"],"sourcesContent":["const EVENT_PREFIX = '@react-email/editor:';\n\n/**\n * Base event map interface for the editor event bus.\n *\n * Components extend this via TypeScript module augmentation:\n * ```ts\n * declare module '@react-email/editor' {\n * interface EditorEventMap {\n * 'my-component:custom-event': { data: string };\n * }\n * }\n * ```\n */\nexport interface EditorEventMap {\n 'bubble-menu:add-link': undefined;\n}\n\nexport type EditorEventName = keyof EditorEventMap;\n\nexport type EditorEventHandler<T extends EditorEventName> = (\n payload: EditorEventMap[T],\n) => void | Promise<void>;\n\nexport interface EditorEventSubscription {\n unsubscribe: () => void;\n}\n\nclass EditorEventBus {\n private prefixEventName(eventName: EditorEventName): string {\n return `${EVENT_PREFIX}${String(eventName)}`;\n }\n\n dispatch<T extends EditorEventName>(\n eventName: T,\n payload: EditorEventMap[T],\n options?: { target?: EventTarget },\n ): void {\n const target = options?.target ?? window;\n const prefixedEventName = this.prefixEventName(eventName);\n const event = new CustomEvent(prefixedEventName, {\n detail: payload,\n bubbles: false,\n cancelable: false,\n });\n target.dispatchEvent(event);\n }\n\n on<T extends EditorEventName>(\n eventName: T,\n handler: EditorEventHandler<T>,\n options?: AddEventListenerOptions & { target?: EventTarget },\n ): EditorEventSubscription {\n const target = options?.target ?? window;\n const prefixedEventName = this.prefixEventName(eventName);\n const abortController = new AbortController();\n\n const wrappedHandler = (event: Event) => {\n const customEvent = event as CustomEvent<EditorEventMap[T]>;\n const result = handler(customEvent.detail);\n\n if (result instanceof Promise) {\n result.catch((error) => {\n console.error(\n `Error in async event handler for ${prefixedEventName}:`,\n { event: customEvent.detail, error },\n );\n });\n }\n };\n\n target.addEventListener(prefixedEventName, wrappedHandler, {\n ...options,\n signal: abortController.signal,\n });\n\n return {\n unsubscribe: () => {\n abortController.abort();\n },\n };\n }\n}\n\nexport const editorEventBus = new EditorEventBus();\n","import type { CssJs } from './types';\n\nconst WHITE_SPACE_REGEX = /\\s+/;\n\nexport const jsToInlineCss = (styleObject: { [key: string]: any }) => {\n const parts: string[] = [];\n\n for (const key in styleObject) {\n const value = styleObject[key];\n if (value !== 0 && value !== undefined && value !== null && value !== '') {\n const KEBAB_CASE_REGEX = /[A-Z]/g;\n const formattedKey = key.replace(\n KEBAB_CASE_REGEX,\n (match) => `-${match.toLowerCase()}`,\n );\n parts.push(`${formattedKey}:${value}`);\n }\n }\n\n return parts.join(';') + (parts.length ? ';' : '');\n};\n\nexport const inlineCssToJs = (\n inlineStyle: string,\n options: { removeUnit?: boolean } = {},\n) => {\n const styleObject: { [key: string]: string } = {};\n\n if (!inlineStyle || inlineStyle === '' || typeof inlineStyle === 'object') {\n return styleObject;\n }\n\n inlineStyle.split(';').forEach((style: string) => {\n if (style.trim()) {\n const [key, value] = style.split(':');\n const valueTrimmed = value?.trim();\n\n if (!valueTrimmed) {\n return;\n }\n\n const formattedKey = key\n .trim()\n .replace(/-\\w/g, (match) => match[1].toUpperCase());\n\n const UNIT_REGEX = /px|%/g;\n const sanitizedValue = options?.removeUnit\n ? valueTrimmed.replace(UNIT_REGEX, '')\n : valueTrimmed;\n\n styleObject[formattedKey] = sanitizedValue;\n }\n });\n\n return styleObject;\n};\n\n/**\n * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.\n * This prevents shorthand properties from overriding specific longhand properties in email clients.\n *\n * @param styles - Style object that may contain shorthand properties\n * @returns New style object with shorthand properties expanded to longhand\n *\n * @example\n * expandShorthandProperties({ margin: '0', paddingTop: '10px' })\n * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }\n */\nexport function expandShorthandProperties(\n styles: Record<string, string>,\n): Record<string, string> {\n if (!styles || typeof styles !== 'object') {\n return {};\n }\n\n const expanded: Record<string, any> = {};\n\n for (const key in styles) {\n const value = styles[key];\n if (value === undefined || value === null || value === '') {\n continue;\n }\n\n switch (key) {\n case 'margin': {\n const values = parseShorthandValue(value);\n expanded.marginTop = values.top;\n expanded.marginRight = values.right;\n expanded.marginBottom = values.bottom;\n expanded.marginLeft = values.left;\n break;\n }\n case 'padding': {\n const values = parseShorthandValue(value);\n expanded.paddingTop = values.top;\n expanded.paddingRight = values.right;\n expanded.paddingBottom = values.bottom;\n expanded.paddingLeft = values.left;\n break;\n }\n case 'border': {\n const values = convertBorderValue(value);\n expanded.borderStyle = values.style;\n expanded.borderWidth = values.width;\n expanded.borderColor = values.color;\n break;\n }\n case 'borderTopLeftRadius':\n case 'borderTopRightRadius':\n case 'borderBottomLeftRadius':\n case 'borderBottomRightRadius': {\n // Always preserve the longhand property\n expanded[key] = value;\n\n // When all four corners are present and identical, also add the shorthand\n if (\n styles.borderTopLeftRadius &&\n styles.borderTopRightRadius &&\n styles.borderBottomLeftRadius &&\n styles.borderBottomRightRadius\n ) {\n const values = [\n styles.borderTopLeftRadius,\n styles.borderTopRightRadius,\n styles.borderBottomLeftRadius,\n styles.borderBottomRightRadius,\n ];\n\n if (new Set(values).size === 1) {\n expanded.borderRadius = values[0];\n }\n }\n\n break;\n }\n\n default: {\n // Keep all other properties as-is\n expanded[key] = value;\n }\n }\n }\n\n return expanded;\n}\n\n/**\n * Parses CSS shorthand value (1-4 values) into individual side values.\n * Follows CSS specification for shorthand property value parsing.\n *\n * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')\n * @returns Object with top, right, bottom, left values\n */\nfunction parseShorthandValue(value: string | number): {\n top: string;\n right: string;\n bottom: string;\n left: string;\n} {\n const stringValue = String(value).trim();\n const parts = stringValue.split(WHITE_SPACE_REGEX);\n const len = parts.length;\n\n if (len === 1) {\n return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };\n }\n if (len === 2) {\n return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };\n }\n if (len === 3) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[1] };\n }\n if (len === 4) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };\n }\n\n return {\n top: stringValue,\n right: stringValue,\n bottom: stringValue,\n left: stringValue,\n };\n}\n\nfunction convertBorderValue(value: string | number): {\n style: string;\n width: string;\n color: string;\n} {\n const stringValue = String(value).trim();\n const parts = stringValue.split(WHITE_SPACE_REGEX);\n\n switch (parts.length) {\n case 1:\n // border: 1px → all sides\n return {\n style: 'solid',\n width: parts[0],\n color: 'black',\n };\n case 2:\n // border: 1px solid → top/bottom, left/right\n return {\n style: parts[1],\n width: parts[0],\n color: 'black',\n };\n case 3:\n // border: 1px solid #000 → top, left/right, bottom\n return {\n style: parts[1],\n width: parts[0],\n color: parts[2],\n };\n case 4:\n // border: 1px solid #000 #fff → top, right, bottom, left\n return {\n style: parts[1],\n width: parts[0],\n color: parts[2],\n };\n default:\n // Invalid format, return the original value for all sides\n return {\n style: 'solid',\n width: stringValue,\n color: 'black',\n };\n }\n}\n\n/**\n * Resolves conflicts between reset styles and inline styles by expanding\n * shorthand properties (margin, padding) to longhand before merging.\n * This prevents shorthand properties from overriding specific longhand properties.\n *\n * @param resetStyles - Base reset styles that may contain shorthand properties\n * @param inlineStyles - Inline styles that should override reset styles\n * @returns Merged styles with inline styles taking precedence\n */\nexport function resolveConflictingStyles(\n resetStyles: CssJs['reset'],\n inlineStyles: Record<string, string>,\n) {\n const expandedResetStyles = expandShorthandProperties(\n resetStyles as Record<string, string>,\n );\n const expandedInlineStyles = expandShorthandProperties(inlineStyles);\n\n return {\n ...expandedResetStyles,\n ...expandedInlineStyles,\n };\n}\n","import {\n type Editor,\n type JSONContent,\n Node,\n type NodeConfig,\n type NodeType,\n} from '@tiptap/core';\n\nexport type RendererComponent = (props: {\n node: JSONContent;\n style: React.CSSProperties;\n children?: React.ReactNode;\n}) => React.ReactNode;\n\nexport interface EmailNodeConfig<Options, Storage>\n extends NodeConfig<Options, Storage> {\n renderToReactEmail: RendererComponent;\n}\n\ntype ConfigParameter<Options, Storage> = Partial<\n Omit<EmailNodeConfig<Options, Storage>, 'renderToReactEmail'>\n> &\n Pick<EmailNodeConfig<Options, Storage>, 'renderToReactEmail'> &\n ThisType<{\n name: string;\n options: Options;\n storage: Storage;\n editor: Editor;\n type: NodeType;\n parent: (...args: any[]) => any;\n }>;\n\nexport class EmailNode<\n Options = Record<string, never>,\n Storage = Record<string, never>,\n> extends Node<Options, Storage> {\n declare config: EmailNodeConfig<Options, Storage>;\n\n // biome-ignore lint/complexity/noUselessConstructor: This is only meant to change the types for config, hence why we keep it\n constructor(config: ConfigParameter<Options, Storage>) {\n super(config);\n }\n\n /**\n * Create a new Node instance\n * @param config - Node configuration object or a function that returns a configuration object\n */\n static create<O = Record<string, never>, S = Record<string, never>>(\n config: ConfigParameter<O, S> | (() => ConfigParameter<O, S>),\n ) {\n // If the config is a function, execute it to get the configuration object\n const resolvedConfig = typeof config === 'function' ? config() : config;\n return new EmailNode<O, S>(resolvedConfig);\n }\n\n static from<O, S>(\n node: Node<O, S>,\n renderToReactEmail: RendererComponent,\n ): EmailNode<O, S> {\n const customNode = EmailNode.create({} as ConfigParameter<O, S>);\n // This only makes a shallow copy, so if there's nested objects here mutating things will be dangerous\n Object.assign(customNode, { ...node });\n customNode.config = { ...node.config, renderToReactEmail };\n return customNode;\n }\n\n // Subclass return types for configure/extend; safe at runtime. TipTap's Node typings cause TS2416 when returning EmailNode.\n // @ts-expect-error - EmailNode is a valid Node subclass; base typings don't support subclass return types\n configure(options?: Partial<Options>) {\n return super.configure(options) as EmailNode<Options, Storage>;\n }\n\n // @ts-expect-error - same as configure: extend returns EmailNode for chaining; base typings are incompatible\n extend<\n ExtendedOptions = Options,\n ExtendedStorage = Storage,\n ExtendedConfig extends NodeConfig<\n ExtendedOptions,\n ExtendedStorage\n > = EmailNodeConfig<ExtendedOptions, ExtendedStorage>,\n >(\n extendedConfig?:\n | (() => Partial<ExtendedConfig>)\n | (Partial<ExtendedConfig> &\n ThisType<{\n name: string;\n options: ExtendedOptions;\n storage: ExtendedStorage;\n editor: Editor;\n type: NodeType;\n }>),\n ): EmailNode<ExtendedOptions, ExtendedStorage> {\n // If the extended config is a function, execute it to get the configuration object\n const resolvedConfig =\n typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig;\n return super.extend(resolvedConfig) as EmailNode<\n ExtendedOptions,\n ExtendedStorage\n >;\n }\n}\n","/**\n * Creates TipTap attribute definitions for a list of HTML attributes.\n * Each attribute will have the same pattern:\n * - default: null\n * - parseHTML: extracts the attribute from the element\n * - renderHTML: conditionally renders the attribute if it has a value\n *\n * @param attributeNames - Array of HTML attribute names to create definitions for\n * @returns Object with TipTap attribute definitions\n *\n * @example\n * const attrs = createStandardAttributes(['class', 'id', 'title']);\n * // Returns:\n * // {\n * // class: {\n * // default: null,\n * // parseHTML: (element) => element.getAttribute('class'),\n * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}\n * // },\n * // ...\n * // }\n */\nexport function createStandardAttributes(attributeNames: readonly string[]) {\n return Object.fromEntries(\n attributeNames.map((attr) => [\n attr,\n {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute(attr),\n renderHTML: (attributes: Record<string, unknown>) => {\n if (!attributes[attr]) {\n return {};\n }\n\n return {\n [attr]: attributes[attr],\n };\n },\n },\n ]),\n );\n}\n\n/**\n * Common HTML attributes used across multiple extensions.\n * These preserve attributes during HTML import and editing for better\n * fidelity when importing existing email templates.\n */\nexport const COMMON_HTML_ATTRIBUTES = [\n 'id',\n 'class',\n 'title',\n 'lang',\n 'dir',\n 'data-id',\n] as const;\n\n/**\n * Layout-specific HTML attributes used for positioning and sizing.\n */\nexport const LAYOUT_ATTRIBUTES = ['align', 'width', 'height'] as const;\n\n/**\n * Table-specific HTML attributes used for table layout and styling.\n */\nexport const TABLE_ATTRIBUTES = [\n 'border',\n 'cellpadding',\n 'cellspacing',\n] as const;\n\n/**\n * Table cell-specific HTML attributes.\n */\nexport const TABLE_CELL_ATTRIBUTES = [\n 'valign',\n 'bgcolor',\n 'colspan',\n 'rowspan',\n] as const;\n\n/**\n * Table header cell-specific HTML attributes.\n * These are additional attributes that only apply to <th> elements.\n */\nexport const TABLE_HEADER_ATTRIBUTES = [\n ...TABLE_CELL_ATTRIBUTES,\n 'scope',\n] as const;\n","import { Column, Row } from '@react-email/components';\nimport { type CommandProps, mergeAttributes } from '@tiptap/core';\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport { TextSelection } from '@tiptap/pm/state';\nimport { EmailNode } from '../core/serializer/email-node';\nimport {\n COMMON_HTML_ATTRIBUTES,\n createStandardAttributes,\n LAYOUT_ATTRIBUTES,\n} from '../utils/attribute-helpers';\nimport { inlineCssToJs } from '../utils/styles';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n columns: {\n insertColumns: (count: 2 | 3 | 4) => ReturnType;\n };\n }\n}\n\nexport const COLUMN_PARENT_TYPES = [\n 'twoColumns',\n 'threeColumns',\n 'fourColumns',\n] as const;\n\nconst COLUMN_PARENT_SET = new Set<string>(COLUMN_PARENT_TYPES);\n\nexport const MAX_COLUMNS_DEPTH = 3;\n\nexport function getColumnsDepth(doc: ProseMirrorNode, from: number): number {\n const $from = doc.resolve(from);\n let depth = 0;\n for (let d = $from.depth; d > 0; d--) {\n if (COLUMN_PARENT_SET.has($from.node(d).type.name)) {\n depth++;\n }\n }\n return depth;\n}\n\ninterface ColumnsVariantConfig {\n name: (typeof COLUMN_PARENT_TYPES)[number];\n columnCount: number;\n content: string;\n dataType: string;\n}\n\nconst VARIANTS: ColumnsVariantConfig[] = [\n {\n name: 'twoColumns',\n columnCount: 2,\n content: 'columnsColumn columnsColumn',\n dataType: 'two-columns',\n },\n {\n name: 'threeColumns',\n columnCount: 3,\n content: 'columnsColumn columnsColumn columnsColumn',\n dataType: 'three-columns',\n },\n {\n name: 'fourColumns',\n columnCount: 4,\n content: 'columnsColumn{4}',\n dataType: 'four-columns',\n },\n];\n\nconst NODE_TYPE_MAP: Record<number, (typeof COLUMN_PARENT_TYPES)[number]> = {\n 2: 'twoColumns',\n 3: 'threeColumns',\n 4: 'fourColumns',\n};\n\nfunction createColumnsNode(\n config: ColumnsVariantConfig,\n includeCommands: boolean,\n) {\n return EmailNode.create({\n name: config.name,\n group: 'block',\n content: config.content,\n isolating: true,\n defining: true,\n\n addAttributes() {\n return createStandardAttributes([\n ...LAYOUT_ATTRIBUTES,\n ...COMMON_HTML_ATTRIBUTES,\n ]);\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${config.dataType}\"]` }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'div',\n mergeAttributes(\n { 'data-type': config.dataType, class: 'node-columns' },\n HTMLAttributes,\n ),\n 0,\n ];\n },\n\n ...(includeCommands && {\n addCommands() {\n return {\n insertColumns:\n (count: 2 | 3 | 4) =>\n ({\n commands,\n state,\n }: CommandProps & {\n state: { doc: ProseMirrorNode; selection: { from: number } };\n }) => {\n if (\n getColumnsDepth(state.doc, state.selection.from) >=\n MAX_COLUMNS_DEPTH\n ) {\n return false;\n }\n const nodeType = NODE_TYPE_MAP[count];\n const children = Array.from({ length: count }, () => ({\n type: 'columnsColumn',\n content: [{ type: 'paragraph', content: [] }],\n }));\n return commands.insertContent({\n type: nodeType,\n content: children,\n });\n },\n };\n },\n }),\n\n renderToReactEmail({ children, node, style }) {\n const inlineStyles = inlineCssToJs(node.attrs?.style);\n return (\n <Row\n className={node.attrs?.class || undefined}\n style={{ ...style, ...inlineStyles }}\n >\n {children}\n </Row>\n );\n },\n });\n}\n\nexport const TwoColumns = createColumnsNode(VARIANTS[0], true);\nexport const ThreeColumns = createColumnsNode(VARIANTS[1], false);\nexport const FourColumns = createColumnsNode(VARIANTS[2], false);\n\nexport const ColumnsColumn = EmailNode.create({\n name: 'columnsColumn',\n group: 'columnsColumn',\n content: 'block+',\n isolating: true,\n\n addAttributes() {\n return {\n ...createStandardAttributes([\n ...LAYOUT_ATTRIBUTES,\n ...COMMON_HTML_ATTRIBUTES,\n ]),\n };\n },\n\n parseHTML() {\n return [{ tag: 'div[data-type=\"column\"]' }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'div',\n mergeAttributes(\n { 'data-type': 'column', class: 'node-column' },\n HTMLAttributes,\n ),\n 0,\n ];\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: ({ editor }) => {\n const { state } = editor;\n const { selection } = state;\n const { empty, $from } = selection;\n\n if (!empty) return false;\n\n for (let depth = $from.depth; depth >= 1; depth--) {\n if ($from.pos !== $from.start(depth)) break;\n\n const indexInParent = $from.index(depth - 1);\n\n if (indexInParent === 0) continue;\n\n const parent = $from.node(depth - 1);\n const prevNode = parent.child(indexInParent - 1);\n\n if (COLUMN_PARENT_SET.has(prevNode.type.name)) {\n const deleteFrom = $from.before(depth) - prevNode.nodeSize;\n const deleteTo = $from.before(depth);\n editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));\n return true;\n }\n\n break;\n }\n\n return false;\n },\n 'Mod-a': ({ editor }) => {\n const { state } = editor;\n const { $from } = state.selection;\n\n for (let d = $from.depth; d > 0; d--) {\n if ($from.node(d).type.name !== 'columnsColumn') {\n continue;\n }\n\n const columnStart = $from.start(d);\n const columnEnd = $from.end(d);\n const { from, to } = state.selection;\n\n if (from === columnStart && to === columnEnd) {\n return false;\n }\n\n editor.view.dispatch(\n state.tr.setSelection(\n TextSelection.create(state.doc, columnStart, columnEnd),\n ),\n );\n return true;\n }\n\n return false;\n },\n };\n },\n\n renderToReactEmail({ children, node, style }) {\n const inlineStyles = inlineCssToJs(node.attrs?.style);\n const width = node.attrs?.width;\n return (\n <Column\n className={node.attrs?.class || undefined}\n style={{\n ...style,\n ...inlineStyles,\n ...(width ? { width } : {}),\n }}\n >\n {children}\n </Column>\n );\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"columns-CUxUEHje.mjs","names":[],"sources":["../src/core/event-bus.ts","../src/utils/styles.ts","../src/core/serializer/email-node.ts","../src/utils/attribute-helpers.ts","../src/extensions/columns.tsx"],"sourcesContent":["const EVENT_PREFIX = '@react-email/editor:';\n\n/**\n * Base event map interface for the editor event bus.\n *\n * Components extend this via TypeScript module augmentation:\n * ```ts\n * declare module '@react-email/editor' {\n * interface EditorEventMap {\n * 'my-component:custom-event': { data: string };\n * }\n * }\n * ```\n */\nexport interface EditorEventMap {\n 'bubble-menu:add-link': undefined;\n}\n\nexport type EditorEventName = keyof EditorEventMap;\n\nexport type EditorEventHandler<T extends EditorEventName> = (\n payload: EditorEventMap[T],\n) => void | Promise<void>;\n\nexport interface EditorEventSubscription {\n unsubscribe: () => void;\n}\n\nclass EditorEventBus {\n private prefixEventName(eventName: EditorEventName): string {\n return `${EVENT_PREFIX}${String(eventName)}`;\n }\n\n dispatch<T extends EditorEventName>(\n eventName: T,\n payload: EditorEventMap[T],\n options?: { target?: EventTarget },\n ): void {\n const target = options?.target ?? window;\n const prefixedEventName = this.prefixEventName(eventName);\n const event = new CustomEvent(prefixedEventName, {\n detail: payload,\n bubbles: false,\n cancelable: false,\n });\n target.dispatchEvent(event);\n }\n\n on<T extends EditorEventName>(\n eventName: T,\n handler: EditorEventHandler<T>,\n options?: AddEventListenerOptions & { target?: EventTarget },\n ): EditorEventSubscription {\n const target = options?.target ?? window;\n const prefixedEventName = this.prefixEventName(eventName);\n const abortController = new AbortController();\n\n const wrappedHandler = (event: Event) => {\n const customEvent = event as CustomEvent<EditorEventMap[T]>;\n const result = handler(customEvent.detail);\n\n if (result instanceof Promise) {\n result.catch((error) => {\n console.error(\n `Error in async event handler for ${prefixedEventName}:`,\n { event: customEvent.detail, error },\n );\n });\n }\n };\n\n target.addEventListener(prefixedEventName, wrappedHandler, {\n ...options,\n signal: abortController.signal,\n });\n\n return {\n unsubscribe: () => {\n abortController.abort();\n },\n };\n }\n}\n\nexport const editorEventBus = new EditorEventBus();\n","import type { CssJs } from './types';\n\nconst WHITE_SPACE_REGEX = /\\s+/;\n\nexport const jsToInlineCss = (styleObject: { [key: string]: any }) => {\n const parts: string[] = [];\n\n for (const key in styleObject) {\n const value = styleObject[key];\n if (value !== 0 && value !== undefined && value !== null && value !== '') {\n const KEBAB_CASE_REGEX = /[A-Z]/g;\n const formattedKey = key.replace(\n KEBAB_CASE_REGEX,\n (match) => `-${match.toLowerCase()}`,\n );\n parts.push(`${formattedKey}:${value}`);\n }\n }\n\n return parts.join(';') + (parts.length ? ';' : '');\n};\n\nexport const inlineCssToJs = (\n inlineStyle: string,\n options: { removeUnit?: boolean } = {},\n) => {\n const styleObject: { [key: string]: string } = {};\n\n if (!inlineStyle || inlineStyle === '' || typeof inlineStyle === 'object') {\n return styleObject;\n }\n\n inlineStyle.split(';').forEach((style: string) => {\n if (style.trim()) {\n const [key, value] = style.split(':');\n const valueTrimmed = value?.trim();\n\n if (!valueTrimmed) {\n return;\n }\n\n const formattedKey = key\n .trim()\n .replace(/-\\w/g, (match) => match[1].toUpperCase());\n\n const UNIT_REGEX = /px|%/g;\n const sanitizedValue = options?.removeUnit\n ? valueTrimmed.replace(UNIT_REGEX, '')\n : valueTrimmed;\n\n styleObject[formattedKey] = sanitizedValue;\n }\n });\n\n return styleObject;\n};\n\n/**\n * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.\n * This prevents shorthand properties from overriding specific longhand properties in email clients.\n *\n * @param styles - Style object that may contain shorthand properties\n * @returns New style object with shorthand properties expanded to longhand\n *\n * @example\n * expandShorthandProperties({ margin: '0', paddingTop: '10px' })\n * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }\n */\nexport function expandShorthandProperties(\n styles: Record<string, string>,\n): Record<string, string> {\n if (!styles || typeof styles !== 'object') {\n return {};\n }\n\n const expanded: Record<string, any> = {};\n\n for (const key in styles) {\n const value = styles[key];\n if (value === undefined || value === null || value === '') {\n continue;\n }\n\n switch (key) {\n case 'margin': {\n const values = parseShorthandValue(value);\n expanded.marginTop = values.top;\n expanded.marginRight = values.right;\n expanded.marginBottom = values.bottom;\n expanded.marginLeft = values.left;\n break;\n }\n case 'padding': {\n const values = parseShorthandValue(value);\n expanded.paddingTop = values.top;\n expanded.paddingRight = values.right;\n expanded.paddingBottom = values.bottom;\n expanded.paddingLeft = values.left;\n break;\n }\n case 'border': {\n const values = convertBorderValue(value);\n expanded.borderStyle = values.style;\n expanded.borderWidth = values.width;\n expanded.borderColor = values.color;\n break;\n }\n case 'borderTopLeftRadius':\n case 'borderTopRightRadius':\n case 'borderBottomLeftRadius':\n case 'borderBottomRightRadius': {\n // Always preserve the longhand property\n expanded[key] = value;\n\n // When all four corners are present and identical, also add the shorthand\n if (\n styles.borderTopLeftRadius &&\n styles.borderTopRightRadius &&\n styles.borderBottomLeftRadius &&\n styles.borderBottomRightRadius\n ) {\n const values = [\n styles.borderTopLeftRadius,\n styles.borderTopRightRadius,\n styles.borderBottomLeftRadius,\n styles.borderBottomRightRadius,\n ];\n\n if (new Set(values).size === 1) {\n expanded.borderRadius = values[0];\n }\n }\n\n break;\n }\n\n default: {\n // Keep all other properties as-is\n expanded[key] = value;\n }\n }\n }\n\n return expanded;\n}\n\n/**\n * Parses CSS shorthand value (1-4 values) into individual side values.\n * Follows CSS specification for shorthand property value parsing.\n *\n * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')\n * @returns Object with top, right, bottom, left values\n */\nfunction parseShorthandValue(value: string | number): {\n top: string;\n right: string;\n bottom: string;\n left: string;\n} {\n const stringValue = String(value).trim();\n const parts = stringValue.split(WHITE_SPACE_REGEX);\n const len = parts.length;\n\n if (len === 1) {\n return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };\n }\n if (len === 2) {\n return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };\n }\n if (len === 3) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[1] };\n }\n if (len === 4) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };\n }\n\n return {\n top: stringValue,\n right: stringValue,\n bottom: stringValue,\n left: stringValue,\n };\n}\n\nfunction convertBorderValue(value: string | number): {\n style: string;\n width: string;\n color: string;\n} {\n const stringValue = String(value).trim();\n const parts = stringValue.split(WHITE_SPACE_REGEX);\n\n switch (parts.length) {\n case 1:\n // border: 1px → all sides\n return {\n style: 'solid',\n width: parts[0],\n color: 'black',\n };\n case 2:\n // border: 1px solid → top/bottom, left/right\n return {\n style: parts[1],\n width: parts[0],\n color: 'black',\n };\n case 3:\n // border: 1px solid #000 → top, left/right, bottom\n return {\n style: parts[1],\n width: parts[0],\n color: parts[2],\n };\n case 4:\n // border: 1px solid #000 #fff → top, right, bottom, left\n return {\n style: parts[1],\n width: parts[0],\n color: parts[2],\n };\n default:\n // Invalid format, return the original value for all sides\n return {\n style: 'solid',\n width: stringValue,\n color: 'black',\n };\n }\n}\n\n/**\n * Resolves conflicts between reset styles and inline styles by expanding\n * shorthand properties (margin, padding) to longhand before merging.\n * This prevents shorthand properties from overriding specific longhand properties.\n *\n * @param resetStyles - Base reset styles that may contain shorthand properties\n * @param inlineStyles - Inline styles that should override reset styles\n * @returns Merged styles with inline styles taking precedence\n */\nexport function resolveConflictingStyles(\n resetStyles: CssJs['reset'],\n inlineStyles: Record<string, string>,\n) {\n const expandedResetStyles = expandShorthandProperties(\n resetStyles as Record<string, string>,\n );\n const expandedInlineStyles = expandShorthandProperties(inlineStyles);\n\n return {\n ...expandedResetStyles,\n ...expandedInlineStyles,\n };\n}\n","import {\n type Editor,\n type JSONContent,\n Node,\n type NodeConfig,\n type NodeType,\n} from '@tiptap/core';\n\nexport type NodeRendererComponent = (props: {\n node: JSONContent;\n style: React.CSSProperties;\n children?: React.ReactNode;\n\n extension: EmailNode<any, any>;\n}) => React.ReactNode;\n\nexport interface EmailNodeConfig<Options, Storage>\n extends NodeConfig<Options, Storage> {\n renderToReactEmail: NodeRendererComponent;\n}\n\ntype ConfigParameter<Options, Storage> = Partial<\n Omit<EmailNodeConfig<Options, Storage>, 'renderToReactEmail'>\n> &\n Pick<EmailNodeConfig<Options, Storage>, 'renderToReactEmail'> &\n ThisType<{\n name: string;\n options: Options;\n storage: Storage;\n editor: Editor;\n type: NodeType;\n parent: (...args: any[]) => any;\n }>;\n\nexport class EmailNode<\n Options = Record<string, never>,\n Storage = Record<string, never>,\n> extends Node<Options, Storage> {\n declare config: EmailNodeConfig<Options, Storage>;\n\n // biome-ignore lint/complexity/noUselessConstructor: This is only meant to change the types for config, hence why we keep it\n constructor(config: ConfigParameter<Options, Storage>) {\n super(config);\n }\n\n /**\n * Create a new Node instance\n * @param config - Node configuration object or a function that returns a configuration object\n */\n static create<O = Record<string, never>, S = Record<string, never>>(\n config: ConfigParameter<O, S> | (() => ConfigParameter<O, S>),\n ) {\n // If the config is a function, execute it to get the configuration object\n const resolvedConfig = typeof config === 'function' ? config() : config;\n return new EmailNode<O, S>(resolvedConfig);\n }\n\n static from<O, S>(\n node: Node<O, S>,\n renderToReactEmail: NodeRendererComponent,\n ): EmailNode<O, S> {\n const customNode = EmailNode.create({} as ConfigParameter<O, S>);\n // This only makes a shallow copy, so if there's nested objects here mutating things will be dangerous\n Object.assign(customNode, { ...node });\n customNode.config = { ...node.config, renderToReactEmail };\n return customNode;\n }\n\n // Subclass return types for configure/extend; safe at runtime. TipTap's Node typings cause TS2416 when returning EmailNode.\n // @ts-expect-error - EmailNode is a valid Node subclass; base typings don't support subclass return types\n configure(options?: Partial<Options>) {\n return super.configure(options) as EmailNode<Options, Storage>;\n }\n\n // @ts-expect-error - same as configure: extend returns EmailNode for chaining; base typings are incompatible\n extend<\n ExtendedOptions = Options,\n ExtendedStorage = Storage,\n ExtendedConfig extends NodeConfig<\n ExtendedOptions,\n ExtendedStorage\n > = EmailNodeConfig<ExtendedOptions, ExtendedStorage>,\n >(\n extendedConfig?:\n | (() => Partial<ExtendedConfig>)\n | (Partial<ExtendedConfig> &\n ThisType<{\n name: string;\n options: ExtendedOptions;\n storage: ExtendedStorage;\n editor: Editor;\n type: NodeType;\n }>),\n ): EmailNode<ExtendedOptions, ExtendedStorage> {\n // If the extended config is a function, execute it to get the configuration object\n const resolvedConfig =\n typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig;\n return super.extend(resolvedConfig) as EmailNode<\n ExtendedOptions,\n ExtendedStorage\n >;\n }\n}\n","/**\n * Creates TipTap attribute definitions for a list of HTML attributes.\n * Each attribute will have the same pattern:\n * - default: null\n * - parseHTML: extracts the attribute from the element\n * - renderHTML: conditionally renders the attribute if it has a value\n *\n * @param attributeNames - Array of HTML attribute names to create definitions for\n * @returns Object with TipTap attribute definitions\n *\n * @example\n * const attrs = createStandardAttributes(['class', 'id', 'title']);\n * // Returns:\n * // {\n * // class: {\n * // default: null,\n * // parseHTML: (element) => element.getAttribute('class'),\n * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}\n * // },\n * // ...\n * // }\n */\nexport function createStandardAttributes(attributeNames: readonly string[]) {\n return Object.fromEntries(\n attributeNames.map((attr) => [\n attr,\n {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute(attr),\n renderHTML: (attributes: Record<string, unknown>) => {\n if (!attributes[attr]) {\n return {};\n }\n\n return {\n [attr]: attributes[attr],\n };\n },\n },\n ]),\n );\n}\n\n/**\n * Common HTML attributes used across multiple extensions.\n * These preserve attributes during HTML import and editing for better\n * fidelity when importing existing email templates.\n */\nexport const COMMON_HTML_ATTRIBUTES = [\n 'id',\n 'class',\n 'title',\n 'lang',\n 'dir',\n 'data-id',\n] as const;\n\n/**\n * Layout-specific HTML attributes used for positioning and sizing.\n */\nexport const LAYOUT_ATTRIBUTES = ['align', 'width', 'height'] as const;\n\n/**\n * Table-specific HTML attributes used for table layout and styling.\n */\nexport const TABLE_ATTRIBUTES = [\n 'border',\n 'cellpadding',\n 'cellspacing',\n] as const;\n\n/**\n * Table cell-specific HTML attributes.\n */\nexport const TABLE_CELL_ATTRIBUTES = [\n 'valign',\n 'bgcolor',\n 'colspan',\n 'rowspan',\n] as const;\n\n/**\n * Table header cell-specific HTML attributes.\n * These are additional attributes that only apply to <th> elements.\n */\nexport const TABLE_HEADER_ATTRIBUTES = [\n ...TABLE_CELL_ATTRIBUTES,\n 'scope',\n] as const;\n","import { Column, Row } from '@react-email/components';\nimport { type CommandProps, mergeAttributes } from '@tiptap/core';\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport { TextSelection } from '@tiptap/pm/state';\nimport { EmailNode } from '../core/serializer/email-node';\nimport {\n COMMON_HTML_ATTRIBUTES,\n createStandardAttributes,\n LAYOUT_ATTRIBUTES,\n} from '../utils/attribute-helpers';\nimport { inlineCssToJs } from '../utils/styles';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n columns: {\n insertColumns: (count: 2 | 3 | 4) => ReturnType;\n };\n }\n}\n\nexport const COLUMN_PARENT_TYPES = [\n 'twoColumns',\n 'threeColumns',\n 'fourColumns',\n] as const;\n\nconst COLUMN_PARENT_SET = new Set<string>(COLUMN_PARENT_TYPES);\n\nexport const MAX_COLUMNS_DEPTH = 3;\n\nexport function getColumnsDepth(doc: ProseMirrorNode, from: number): number {\n const $from = doc.resolve(from);\n let depth = 0;\n for (let d = $from.depth; d > 0; d--) {\n if (COLUMN_PARENT_SET.has($from.node(d).type.name)) {\n depth++;\n }\n }\n return depth;\n}\n\ninterface ColumnsVariantConfig {\n name: (typeof COLUMN_PARENT_TYPES)[number];\n columnCount: number;\n content: string;\n dataType: string;\n}\n\nconst VARIANTS: ColumnsVariantConfig[] = [\n {\n name: 'twoColumns',\n columnCount: 2,\n content: 'columnsColumn columnsColumn',\n dataType: 'two-columns',\n },\n {\n name: 'threeColumns',\n columnCount: 3,\n content: 'columnsColumn columnsColumn columnsColumn',\n dataType: 'three-columns',\n },\n {\n name: 'fourColumns',\n columnCount: 4,\n content: 'columnsColumn{4}',\n dataType: 'four-columns',\n },\n];\n\nconst NODE_TYPE_MAP: Record<number, (typeof COLUMN_PARENT_TYPES)[number]> = {\n 2: 'twoColumns',\n 3: 'threeColumns',\n 4: 'fourColumns',\n};\n\nfunction createColumnsNode(\n config: ColumnsVariantConfig,\n includeCommands: boolean,\n) {\n return EmailNode.create({\n name: config.name,\n group: 'block',\n content: config.content,\n isolating: true,\n defining: true,\n\n addAttributes() {\n return createStandardAttributes([\n ...LAYOUT_ATTRIBUTES,\n ...COMMON_HTML_ATTRIBUTES,\n ]);\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${config.dataType}\"]` }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'div',\n mergeAttributes(\n { 'data-type': config.dataType, class: 'node-columns' },\n HTMLAttributes,\n ),\n 0,\n ];\n },\n\n ...(includeCommands && {\n addCommands() {\n return {\n insertColumns:\n (count: 2 | 3 | 4) =>\n ({\n commands,\n state,\n }: CommandProps & {\n state: { doc: ProseMirrorNode; selection: { from: number } };\n }) => {\n if (\n getColumnsDepth(state.doc, state.selection.from) >=\n MAX_COLUMNS_DEPTH\n ) {\n return false;\n }\n const nodeType = NODE_TYPE_MAP[count];\n const children = Array.from({ length: count }, () => ({\n type: 'columnsColumn',\n content: [{ type: 'paragraph', content: [] }],\n }));\n return commands.insertContent({\n type: nodeType,\n content: children,\n });\n },\n };\n },\n }),\n\n renderToReactEmail({ children, node, style }) {\n const inlineStyles = inlineCssToJs(node.attrs?.style);\n return (\n <Row\n className={node.attrs?.class || undefined}\n style={{ ...style, ...inlineStyles }}\n >\n {children}\n </Row>\n );\n },\n });\n}\n\nexport const TwoColumns = createColumnsNode(VARIANTS[0], true);\nexport const ThreeColumns = createColumnsNode(VARIANTS[1], false);\nexport const FourColumns = createColumnsNode(VARIANTS[2], false);\n\nexport const ColumnsColumn = EmailNode.create({\n name: 'columnsColumn',\n group: 'columnsColumn',\n content: 'block+',\n isolating: true,\n\n addAttributes() {\n return {\n ...createStandardAttributes([\n ...LAYOUT_ATTRIBUTES,\n ...COMMON_HTML_ATTRIBUTES,\n ]),\n };\n },\n\n parseHTML() {\n return [{ tag: 'div[data-type=\"column\"]' }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'div',\n mergeAttributes(\n { 'data-type': 'column', class: 'node-column' },\n HTMLAttributes,\n ),\n 0,\n ];\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: ({ editor }) => {\n const { state } = editor;\n const { selection } = state;\n const { empty, $from } = selection;\n\n if (!empty) return false;\n\n for (let depth = $from.depth; depth >= 1; depth--) {\n if ($from.pos !== $from.start(depth)) break;\n\n const indexInParent = $from.index(depth - 1);\n\n if (indexInParent === 0) continue;\n\n const parent = $from.node(depth - 1);\n const prevNode = parent.child(indexInParent - 1);\n\n if (COLUMN_PARENT_SET.has(prevNode.type.name)) {\n const deleteFrom = $from.before(depth) - prevNode.nodeSize;\n const deleteTo = $from.before(depth);\n editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));\n return true;\n }\n\n break;\n }\n\n return false;\n },\n 'Mod-a': ({ editor }) => {\n const { state } = editor;\n const { $from } = state.selection;\n\n for (let d = $from.depth; d > 0; d--) {\n if ($from.node(d).type.name !== 'columnsColumn') {\n continue;\n }\n\n const columnStart = $from.start(d);\n const columnEnd = $from.end(d);\n const { from, to } = state.selection;\n\n if (from === columnStart && to === columnEnd) {\n return false;\n }\n\n editor.view.dispatch(\n state.tr.setSelection(\n TextSelection.create(state.doc, columnStart, columnEnd),\n ),\n );\n return true;\n }\n\n return false;\n },\n };\n },\n\n renderToReactEmail({ children, node, style }) {\n const inlineStyles = inlineCssToJs(node.attrs?.style);\n const width = node.attrs?.width;\n return (\n <Column\n className={node.attrs?.class || undefined}\n style={{\n ...style,\n ...inlineStyles,\n ...(width ? { width } : {}),\n }}\n >\n {children}\n </Column>\n );\n },\n});\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_columns = require('../columns-ZSaLdkg9.cjs');
2
- const require_core = require('../core-iuG1UrYN.cjs');
2
+ const require_core = require('../core-DpfUR_iP.cjs');
3
3
 
4
4
  exports.EmailNode = require_columns.EmailNode;
5
5
  exports.composeReactEmail = require_core.composeReactEmail;
@@ -1,2 +1,2 @@
1
- import { a as composeReactEmail, c as EditorEventMap, d as editorEventBus, i as RendererComponent, l as EditorEventName, n as EmailNode, o as isDocumentVisuallyEmpty, r as EmailNodeConfig, s as EditorEventHandler, t as useEditor, u as EditorEventSubscription } from "../index-CfslA7KT.cjs";
2
- export { EditorEventHandler, EditorEventMap, EditorEventName, EditorEventSubscription, EmailNode, EmailNodeConfig, RendererComponent, composeReactEmail, editorEventBus, isDocumentVisuallyEmpty, useEditor };
1
+ import { a as composeReactEmail, c as EditorEventMap, d as editorEventBus, i as NodeRendererComponent, l as EditorEventName, n as EmailNode, o as isDocumentVisuallyEmpty, r as EmailNodeConfig, s as EditorEventHandler, t as useEditor, u as EditorEventSubscription } from "../index-BPjgJSQh.cjs";
2
+ export { EditorEventHandler, EditorEventMap, EditorEventName, EditorEventSubscription, EmailNode, EmailNodeConfig, NodeRendererComponent, composeReactEmail, editorEventBus, isDocumentVisuallyEmpty, useEditor };
@@ -1,2 +1,2 @@
1
- import { a as composeReactEmail, c as EditorEventMap, d as editorEventBus, i as RendererComponent, l as EditorEventName, n as EmailNode, o as isDocumentVisuallyEmpty, r as EmailNodeConfig, s as EditorEventHandler, t as useEditor, u as EditorEventSubscription } from "../index-hbHRR7oB.mjs";
2
- export { EditorEventHandler, EditorEventMap, EditorEventName, EditorEventSubscription, EmailNode, EmailNodeConfig, RendererComponent, composeReactEmail, editorEventBus, isDocumentVisuallyEmpty, useEditor };
1
+ import { a as composeReactEmail, c as EditorEventMap, d as editorEventBus, i as NodeRendererComponent, l as EditorEventName, n as EmailNode, o as isDocumentVisuallyEmpty, r as EmailNodeConfig, s as EditorEventHandler, t as useEditor, u as EditorEventSubscription } from "../index-4EvOA-Ll.mjs";
2
+ export { EditorEventHandler, EditorEventMap, EditorEventName, EditorEventSubscription, EmailNode, EmailNodeConfig, NodeRendererComponent, composeReactEmail, editorEventBus, isDocumentVisuallyEmpty, useEditor };
@@ -1,4 +1,4 @@
1
1
  import { _ as editorEventBus, m as EmailNode } from "../columns-CUxUEHje.mjs";
2
- import { R as composeReactEmail, t as useEditor, z as isDocumentVisuallyEmpty } from "../core-BjmRceVw.mjs";
2
+ import { B as isDocumentVisuallyEmpty, t as useEditor, z as composeReactEmail } from "../core-C0EkaM-K.mjs";
3
3
 
4
4
  export { EmailNode, composeReactEmail, editorEventBus, isDocumentVisuallyEmpty, useEditor };
@@ -1,7 +1,7 @@
1
1
  import { _ as editorEventBus, a as ThreeColumns, c as COMMON_HTML_ATTRIBUTES, d as TABLE_CELL_ATTRIBUTES, f as TABLE_HEADER_ATTRIBUTES, g as resolveConflictingStyles, h as inlineCssToJs, l as LAYOUT_ATTRIBUTES, m as EmailNode, n as ColumnsColumn, o as TwoColumns, p as createStandardAttributes, r as FourColumns, u as TABLE_ATTRIBUTES } from "./columns-CUxUEHje.mjs";
2
2
  import * as ReactEmailComponents from "@react-email/components";
3
3
  import { Body, Button, CodeBlock, Column, Head, Heading, Hr, Html, Link, Preview, Row, Section, pretty, render, toPlainText } from "@react-email/components";
4
- import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Extension, InputRule, Mark, Node as Node$1, findChildren, mergeAttributes } from "@tiptap/core";
6
6
  import { UndoRedo } from "@tiptap/extensions";
7
7
  import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useEditor, useEditorState } from "@tiptap/react";
@@ -27,6 +27,7 @@ import ParagraphBase from "@tiptap/extension-paragraph";
27
27
  import TipTapPlaceholder from "@tiptap/extension-placeholder";
28
28
  import StrikeBase from "@tiptap/extension-strike";
29
29
  import SuperscriptBase from "@tiptap/extension-superscript";
30
+ import { Text } from "@tiptap/extension-text";
30
31
  import UnderlineBase from "@tiptap/extension-underline";
31
32
  import { generateJSON } from "@tiptap/html";
32
33
 
@@ -113,77 +114,51 @@ var EmailMark = class EmailMark extends Mark {
113
114
 
114
115
  //#endregion
115
116
  //#region src/core/serializer/compose-react-email.tsx
116
- const MARK_ORDER = {
117
- preservedStyle: 0,
118
- italic: 1,
119
- strike: 2,
120
- underline: 3,
121
- link: 4,
122
- bold: 5,
123
- code: 6
124
- };
125
117
  const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
126
- function getOrderedMarks(marks) {
127
- if (!marks) return [];
128
- return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
129
- }
130
118
  const composeReactEmail = async ({ editor, preview }) => {
131
119
  const data = editor.getJSON();
132
120
  const extensions = editor.extensionManager.extensions;
133
121
  const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
134
- const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
135
- const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
136
- function renderMark(mark, node, children, depth) {
137
- const markStyle = serializerPlugin?.getNodeStyles({
138
- type: mark.type,
139
- attrs: mark.attrs ?? {}
140
- }, depth, editor) ?? {};
141
- const markRenderer = emailMarkComponentRegistry[mark.type];
142
- if (markRenderer) return markRenderer({
143
- mark,
144
- node,
145
- style: markStyle,
146
- children
147
- });
148
- return children;
149
- }
122
+ const typeToExtensionMap = Object.fromEntries(extensions.map((extension) => [extension.name, extension]));
150
123
  function parseContent(content, depth = 0) {
151
124
  if (!content) return;
152
125
  return content.map((node, index) => {
153
126
  const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
154
127
  const inlineStyles = inlineCssToJs(node.attrs?.style);
155
- if (node.type && emailNodeComponentRegistry[node.type]) {
156
- const Component = emailNodeComponentRegistry[node.type];
157
- const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
158
- return /* @__PURE__ */ jsx(Component, {
159
- node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
160
- ...node,
161
- attrs: {
162
- ...node.attrs,
163
- width: inlineStyles.width
164
- }
165
- } : node,
166
- style,
167
- children: parseContent(node.content, childDepth)
168
- }, index);
169
- }
170
- switch (node.type) {
171
- case "text": {
172
- let wrappedText = node.text;
173
- getOrderedMarks(node.marks).forEach((mark) => {
174
- wrappedText = renderMark(mark, node, wrappedText, depth);
128
+ if (!node.type) return null;
129
+ const emailNode = typeToExtensionMap[node.type];
130
+ if (!emailNode || !(emailNode instanceof EmailNode)) return null;
131
+ const NodeComponent = emailNode.config.renderToReactEmail;
132
+ const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
133
+ let children = node.text ? node.text : parseContent(node.content, childDepth);
134
+ if (node.marks) for (const mark of node.marks) {
135
+ const emailMark = typeToExtensionMap[mark.type];
136
+ if (emailMark instanceof EmailMark) {
137
+ const MarkComponent = emailMark.config.renderToReactEmail;
138
+ children = /* @__PURE__ */ jsx(MarkComponent, {
139
+ mark,
140
+ node,
141
+ style: serializerPlugin?.getNodeStyles({
142
+ type: mark.type,
143
+ attrs: mark.attrs ?? {}
144
+ }, depth, editor) ?? {},
145
+ extension: emailMark,
146
+ children
175
147
  });
176
- const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
177
- return /* @__PURE__ */ jsx("span", {
178
- style: {
179
- ...textAttributes,
180
- ...style
181
- },
182
- children: wrappedText
183
- }, index);
184
148
  }
185
- default: return null;
186
149
  }
150
+ return /* @__PURE__ */ jsx(NodeComponent, {
151
+ node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
152
+ ...node,
153
+ attrs: {
154
+ ...node.attrs,
155
+ width: inlineStyles.width
156
+ }
157
+ } : node,
158
+ style,
159
+ extension: emailNode,
160
+ children
161
+ }, index);
187
162
  });
188
163
  }
189
164
  const unformattedHtml = await render(/* @__PURE__ */ jsx(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
@@ -1547,6 +1522,12 @@ const TableHeader = Node$1.create({
1547
1522
  }
1548
1523
  });
1549
1524
 
1525
+ //#endregion
1526
+ //#region src/extensions/text.tsx
1527
+ const Text$1 = EmailNode.from(Text, ({ children }) => {
1528
+ return /* @__PURE__ */ jsx(Fragment, { children });
1529
+ });
1530
+
1550
1531
  //#endregion
1551
1532
  //#region src/extensions/underline.tsx
1552
1533
  const Underline = EmailMark.from(UnderlineBase, ({ children, style }) => /* @__PURE__ */ jsx("u", {
@@ -1637,6 +1618,7 @@ const starterKitExtensions = {
1637
1618
  Button: Button$1,
1638
1619
  Section: Section$1,
1639
1620
  GlobalContent,
1621
+ Text: Text$1,
1640
1622
  AlignmentAttribute,
1641
1623
  StyleAttribute,
1642
1624
  ClassAttribute,
@@ -1728,6 +1710,7 @@ const StarterKit = Extension.create({
1728
1710
  "columnsColumn",
1729
1711
  "link"
1730
1712
  ] },
1713
+ Text: {},
1731
1714
  ClassAttribute: { types: [
1732
1715
  "heading",
1733
1716
  "paragraph",
@@ -1781,6 +1764,7 @@ const StarterKit = Extension.create({
1781
1764
  hardBreak: false,
1782
1765
  gapcursor: false,
1783
1766
  codeBlock: false,
1767
+ text: false,
1784
1768
  horizontalRule: false,
1785
1769
  dropcursor: {
1786
1770
  color: "#61a8f8",
@@ -1995,5 +1979,5 @@ function useEditor$1({ content, extensions = [], onUpdate, onPaste, onUploadImag
1995
1979
  }
1996
1980
 
1997
1981
  //#endregion
1998
- export { Code as A, Heading$2 as C, Divider as D, getGlobalContent as E, Body$1 as F, Blockquote as I, AlignmentAttribute as L, Button$1 as M, BulletList as N, Div as O, Bold as P, composeReactEmail as R, Italic as S, GlobalContent as T, MaxNesting as _, Table as a, PreservedStyle as b, TableRow as c, Strike as d, Section$1 as f, OrderedList as g, Paragraph as h, Underline as i, ClassAttribute as j, CodeBlockPrism as k, Sup as l, Placeholder as m, StarterKit as n, TableCell as o, PreviewText as p, Uppercase as r, TableHeader as s, useEditor$1 as t, StyleAttribute as u, ListItem as v, HardBreak as w, processStylesForUnlink as x, Link$1 as y, isDocumentVisuallyEmpty as z };
1999
- //# sourceMappingURL=core-BjmRceVw.mjs.map
1982
+ export { CodeBlockPrism as A, isDocumentVisuallyEmpty as B, Italic as C, getGlobalContent as D, GlobalContent as E, Bold as F, Body$1 as I, Blockquote as L, ClassAttribute as M, Button$1 as N, Divider as O, BulletList as P, AlignmentAttribute as R, processStylesForUnlink as S, HardBreak as T, OrderedList as _, Text$1 as a, Link$1 as b, TableHeader as c, StyleAttribute as d, Strike as f, Paragraph as g, Placeholder as h, Underline as i, Code as j, Div as k, TableRow as l, PreviewText as m, StarterKit as n, Table as o, Section$1 as p, Uppercase as r, TableCell as s, useEditor$1 as t, Sup as u, MaxNesting as v, Heading$2 as w, PreservedStyle as x, ListItem as y, composeReactEmail as z };
1983
+ //# sourceMappingURL=core-C0EkaM-K.mjs.map