@react-email/editor 0.0.0-experimental.14 → 0.0.0-experimental.15

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.cjs CHANGED
@@ -46,6 +46,10 @@ react = __toESM(react);
46
46
  let _radix_ui_react_popover = require("@radix-ui/react-popover");
47
47
  _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
48
48
  let _tiptap_react_menus = require("@tiptap/react/menus");
49
+ let _tiptap_suggestion = require("@tiptap/suggestion");
50
+ _tiptap_suggestion = __toESM(_tiptap_suggestion);
51
+ let tippy_js = require("tippy.js");
52
+ tippy_js = __toESM(tippy_js);
49
53
 
50
54
  //#region src/core/email-node.ts
51
55
  var EmailNode = class EmailNode extends _tiptap_core.Node {
@@ -2912,8 +2916,469 @@ const LinkBubbleMenu = {
2912
2916
  Default: LinkBubbleMenuDefault
2913
2917
  };
2914
2918
 
2919
+ //#endregion
2920
+ //#region src/ui/slash-command/utils.ts
2921
+ function isInsideNode(editor, type) {
2922
+ const { $from } = editor.state.selection;
2923
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === type) return true;
2924
+ return false;
2925
+ }
2926
+ function isAtMaxColumnsDepth(editor) {
2927
+ const { from } = editor.state.selection;
2928
+ return getColumnsDepth(editor.state.doc, from) >= MAX_COLUMNS_DEPTH;
2929
+ }
2930
+ function updateScrollView(container, item) {
2931
+ const containerRect = container.getBoundingClientRect();
2932
+ const itemRect = item.getBoundingClientRect();
2933
+ if (itemRect.top < containerRect.top) container.scrollTop -= containerRect.top - itemRect.top;
2934
+ else if (itemRect.bottom > containerRect.bottom) container.scrollTop += itemRect.bottom - containerRect.bottom;
2935
+ }
2936
+
2937
+ //#endregion
2938
+ //#region src/ui/slash-command/command-list.tsx
2939
+ const CATEGORY_ORDER = [
2940
+ "Text",
2941
+ "Media",
2942
+ "Layout",
2943
+ "Utility"
2944
+ ];
2945
+ function groupByCategory(items) {
2946
+ const seen = /* @__PURE__ */ new Map();
2947
+ for (const item of items) {
2948
+ const existing = seen.get(item.category);
2949
+ if (existing) existing.push(item);
2950
+ else seen.set(item.category, [item]);
2951
+ }
2952
+ const ordered = [];
2953
+ for (const cat of CATEGORY_ORDER) {
2954
+ const group = seen.get(cat);
2955
+ if (group) {
2956
+ ordered.push({
2957
+ category: cat,
2958
+ items: group
2959
+ });
2960
+ seen.delete(cat);
2961
+ }
2962
+ }
2963
+ for (const [category, group] of seen) ordered.push({
2964
+ category,
2965
+ items: group
2966
+ });
2967
+ return ordered;
2968
+ }
2969
+ function CommandItem({ item, selected, onSelect }) {
2970
+ const Icon = item.icon;
2971
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2972
+ "data-re-slash-command-item": "",
2973
+ "data-selected": selected || void 0,
2974
+ onClick: onSelect,
2975
+ type: "button",
2976
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 20 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.title })]
2977
+ });
2978
+ }
2979
+ function CommandList({ items, command, query, ref }) {
2980
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
2981
+ const containerRef = (0, react.useRef)(null);
2982
+ (0, react.useEffect)(() => {
2983
+ setSelectedIndex(0);
2984
+ }, [items]);
2985
+ (0, react.useLayoutEffect)(() => {
2986
+ const container = containerRef.current;
2987
+ if (!container) return;
2988
+ const selected = container.querySelector("[data-selected]");
2989
+ if (selected) updateScrollView(container, selected);
2990
+ }, [selectedIndex]);
2991
+ const selectItem = (0, react.useCallback)((index) => {
2992
+ const item = items[index];
2993
+ if (item) command(item);
2994
+ }, [items, command]);
2995
+ (0, react.useImperativeHandle)(ref, () => ({ onKeyDown: ({ event }) => {
2996
+ if (items.length === 0) return false;
2997
+ if (event.key === "ArrowUp") {
2998
+ setSelectedIndex((i) => (i + items.length - 1) % items.length);
2999
+ return true;
3000
+ }
3001
+ if (event.key === "ArrowDown") {
3002
+ setSelectedIndex((i) => (i + 1) % items.length);
3003
+ return true;
3004
+ }
3005
+ if (event.key === "Enter") {
3006
+ selectItem(selectedIndex);
3007
+ return true;
3008
+ }
3009
+ return false;
3010
+ } }), [
3011
+ items.length,
3012
+ selectItem,
3013
+ selectedIndex
3014
+ ]);
3015
+ if (items.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3016
+ "data-re-slash-command": "",
3017
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3018
+ "data-re-slash-command-empty": "",
3019
+ children: "No results"
3020
+ })
3021
+ });
3022
+ if (query.trim().length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3023
+ "data-re-slash-command": "",
3024
+ ref: containerRef,
3025
+ children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3026
+ item,
3027
+ onSelect: () => selectItem(index),
3028
+ selected: index === selectedIndex
3029
+ }, item.title))
3030
+ });
3031
+ const groups = groupByCategory(items);
3032
+ let flatIndex = 0;
3033
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3034
+ "data-re-slash-command": "",
3035
+ ref: containerRef,
3036
+ children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3037
+ "data-re-slash-command-category": "",
3038
+ children: group.category
3039
+ }), group.items.map((item) => {
3040
+ const currentIndex = flatIndex++;
3041
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3042
+ item,
3043
+ onSelect: () => selectItem(currentIndex),
3044
+ selected: currentIndex === selectedIndex
3045
+ }, item.title);
3046
+ })] }, group.category))
3047
+ });
3048
+ }
3049
+
3050
+ //#endregion
3051
+ //#region src/ui/slash-command/commands.ts
3052
+ const TEXT = {
3053
+ title: "Text",
3054
+ description: "Plain text block",
3055
+ icon: lucide_react.Text,
3056
+ category: "Text",
3057
+ searchTerms: ["p", "paragraph"],
3058
+ command: ({ editor, range }) => {
3059
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
3060
+ }
3061
+ };
3062
+ const H1 = {
3063
+ title: "Title",
3064
+ description: "Large heading",
3065
+ icon: lucide_react.Heading1,
3066
+ category: "Text",
3067
+ searchTerms: [
3068
+ "title",
3069
+ "big",
3070
+ "large",
3071
+ "h1"
3072
+ ],
3073
+ command: ({ editor, range }) => {
3074
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
3075
+ }
3076
+ };
3077
+ const H2 = {
3078
+ title: "Subtitle",
3079
+ description: "Medium heading",
3080
+ icon: lucide_react.Heading2,
3081
+ category: "Text",
3082
+ searchTerms: [
3083
+ "subtitle",
3084
+ "medium",
3085
+ "h2"
3086
+ ],
3087
+ command: ({ editor, range }) => {
3088
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
3089
+ }
3090
+ };
3091
+ const H3 = {
3092
+ title: "Heading",
3093
+ description: "Small heading",
3094
+ icon: lucide_react.Heading3,
3095
+ category: "Text",
3096
+ searchTerms: [
3097
+ "subtitle",
3098
+ "small",
3099
+ "h3"
3100
+ ],
3101
+ command: ({ editor, range }) => {
3102
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
3103
+ }
3104
+ };
3105
+ const BULLET_LIST = {
3106
+ title: "Bullet list",
3107
+ description: "Unordered list",
3108
+ icon: lucide_react.List,
3109
+ category: "Text",
3110
+ searchTerms: ["unordered", "point"],
3111
+ command: ({ editor, range }) => {
3112
+ editor.chain().focus().deleteRange(range).toggleBulletList().run();
3113
+ }
3114
+ };
3115
+ const NUMBERED_LIST = {
3116
+ title: "Numbered list",
3117
+ description: "Ordered list",
3118
+ icon: lucide_react.ListOrdered,
3119
+ category: "Text",
3120
+ searchTerms: ["ordered"],
3121
+ command: ({ editor, range }) => {
3122
+ editor.chain().focus().deleteRange(range).toggleOrderedList().run();
3123
+ }
3124
+ };
3125
+ const QUOTE = {
3126
+ title: "Quote",
3127
+ description: "Block quote",
3128
+ icon: lucide_react.TextQuote,
3129
+ category: "Text",
3130
+ searchTerms: ["blockquote"],
3131
+ command: ({ editor, range }) => {
3132
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run();
3133
+ }
3134
+ };
3135
+ const CODE = {
3136
+ title: "Code block",
3137
+ description: "Code snippet",
3138
+ icon: lucide_react.SquareCode,
3139
+ category: "Text",
3140
+ searchTerms: ["codeblock"],
3141
+ command: ({ editor, range }) => {
3142
+ editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
3143
+ }
3144
+ };
3145
+ const BUTTON = {
3146
+ title: "Button",
3147
+ description: "Clickable button",
3148
+ icon: lucide_react.MousePointer,
3149
+ category: "Layout",
3150
+ searchTerms: ["button"],
3151
+ command: ({ editor, range }) => {
3152
+ editor.chain().focus().deleteRange(range).setButton().run();
3153
+ }
3154
+ };
3155
+ const DIVIDER = {
3156
+ title: "Divider",
3157
+ description: "Horizontal separator",
3158
+ icon: lucide_react.SplitSquareVertical,
3159
+ category: "Layout",
3160
+ searchTerms: [
3161
+ "hr",
3162
+ "divider",
3163
+ "separator"
3164
+ ],
3165
+ command: ({ editor, range }) => {
3166
+ editor.chain().focus().deleteRange(range).setHorizontalRule().run();
3167
+ }
3168
+ };
3169
+ const SECTION = {
3170
+ title: "Section",
3171
+ description: "Content section",
3172
+ icon: lucide_react.Rows2,
3173
+ category: "Layout",
3174
+ searchTerms: [
3175
+ "section",
3176
+ "row",
3177
+ "container"
3178
+ ],
3179
+ command: ({ editor, range }) => {
3180
+ editor.chain().focus().deleteRange(range).insertSection().run();
3181
+ }
3182
+ };
3183
+ const TWO_COLUMNS = {
3184
+ title: "2 columns",
3185
+ description: "Two column layout",
3186
+ icon: lucide_react.Columns2,
3187
+ category: "Layout",
3188
+ searchTerms: [
3189
+ "columns",
3190
+ "column",
3191
+ "layout",
3192
+ "grid",
3193
+ "split",
3194
+ "side-by-side",
3195
+ "multi-column",
3196
+ "row",
3197
+ "two",
3198
+ "2"
3199
+ ],
3200
+ command: ({ editor, range }) => {
3201
+ editor.chain().focus().deleteRange(range).insertColumns(2).run();
3202
+ }
3203
+ };
3204
+ const THREE_COLUMNS = {
3205
+ title: "3 columns",
3206
+ description: "Three column layout",
3207
+ icon: lucide_react.Columns3,
3208
+ category: "Layout",
3209
+ searchTerms: [
3210
+ "columns",
3211
+ "column",
3212
+ "layout",
3213
+ "grid",
3214
+ "split",
3215
+ "multi-column",
3216
+ "row",
3217
+ "three",
3218
+ "3"
3219
+ ],
3220
+ command: ({ editor, range }) => {
3221
+ editor.chain().focus().deleteRange(range).insertColumns(3).run();
3222
+ }
3223
+ };
3224
+ const FOUR_COLUMNS = {
3225
+ title: "4 columns",
3226
+ description: "Four column layout",
3227
+ icon: lucide_react.Columns4,
3228
+ category: "Layout",
3229
+ searchTerms: [
3230
+ "columns",
3231
+ "column",
3232
+ "layout",
3233
+ "grid",
3234
+ "split",
3235
+ "multi-column",
3236
+ "row",
3237
+ "four",
3238
+ "4"
3239
+ ],
3240
+ command: ({ editor, range }) => {
3241
+ editor.chain().focus().deleteRange(range).insertColumns(4).run();
3242
+ }
3243
+ };
3244
+ const defaultSlashCommands = [
3245
+ TEXT,
3246
+ H1,
3247
+ H2,
3248
+ H3,
3249
+ BULLET_LIST,
3250
+ NUMBERED_LIST,
3251
+ QUOTE,
3252
+ CODE,
3253
+ BUTTON,
3254
+ DIVIDER,
3255
+ SECTION,
3256
+ TWO_COLUMNS,
3257
+ THREE_COLUMNS,
3258
+ FOUR_COLUMNS
3259
+ ];
3260
+
3261
+ //#endregion
3262
+ //#region src/ui/slash-command/extension.ts
3263
+ const SlashCommandExtension = _tiptap_core.Extension.create({
3264
+ name: "slash-command",
3265
+ addOptions() {
3266
+ return { suggestion: {
3267
+ char: "/",
3268
+ allow: ({ editor }) => !editor.isActive("codeBlock"),
3269
+ command: ({ editor, range, props }) => {
3270
+ props.command({
3271
+ editor,
3272
+ range
3273
+ });
3274
+ }
3275
+ } };
3276
+ },
3277
+ addProseMirrorPlugins() {
3278
+ return [(0, _tiptap_suggestion.default)({
3279
+ pluginKey: new _tiptap_pm_state.PluginKey("slash-command"),
3280
+ editor: this.editor,
3281
+ ...this.options.suggestion
3282
+ })];
3283
+ }
3284
+ });
3285
+
3286
+ //#endregion
3287
+ //#region src/ui/slash-command/render.tsx
3288
+ function createRenderItems(component = CommandList) {
3289
+ return () => {
3290
+ let renderer = null;
3291
+ let popup = null;
3292
+ return {
3293
+ onStart: (props) => {
3294
+ renderer = new _tiptap_react.ReactRenderer(component, {
3295
+ props,
3296
+ editor: props.editor
3297
+ });
3298
+ if (!props.clientRect) return;
3299
+ popup = (0, tippy_js.default)("body", {
3300
+ getReferenceClientRect: props.clientRect,
3301
+ appendTo: () => document.body,
3302
+ content: renderer.element,
3303
+ showOnCreate: true,
3304
+ interactive: true,
3305
+ trigger: "manual",
3306
+ placement: "bottom-start"
3307
+ });
3308
+ },
3309
+ onUpdate: (props) => {
3310
+ if (!renderer) return;
3311
+ renderer.updateProps(props);
3312
+ if (popup?.[0] && props.clientRect) popup[0].setProps({ getReferenceClientRect: props.clientRect });
3313
+ },
3314
+ onKeyDown: (props) => {
3315
+ if (props.event.key === "Escape") {
3316
+ popup?.[0]?.hide();
3317
+ return true;
3318
+ }
3319
+ return renderer?.ref?.onKeyDown(props) ?? false;
3320
+ },
3321
+ onExit: () => {
3322
+ popup?.[0]?.destroy();
3323
+ renderer?.destroy();
3324
+ popup = null;
3325
+ renderer = null;
3326
+ }
3327
+ };
3328
+ };
3329
+ }
3330
+
3331
+ //#endregion
3332
+ //#region src/ui/slash-command/search.ts
3333
+ function scoreItem(item, query) {
3334
+ if (!query) return 100;
3335
+ const q = query.toLowerCase();
3336
+ const title = item.title.toLowerCase();
3337
+ const description = item.description.toLowerCase();
3338
+ const terms = item.searchTerms?.map((t) => t.toLowerCase()) ?? [];
3339
+ if (title === q) return 100;
3340
+ if (title.startsWith(q)) return 90;
3341
+ if (title.split(/\s+/).some((w) => w.startsWith(q))) return 80;
3342
+ if (terms.some((t) => t === q)) return 70;
3343
+ if (terms.some((t) => t.startsWith(q))) return 60;
3344
+ if (title.includes(q)) return 40;
3345
+ if (terms.some((t) => t.includes(q))) return 30;
3346
+ if (description.includes(q)) return 20;
3347
+ return 0;
3348
+ }
3349
+ function filterAndRankItems(items, query) {
3350
+ const trimmed = query.trim();
3351
+ if (!trimmed) return items;
3352
+ const scored = items.map((item) => ({
3353
+ item,
3354
+ score: scoreItem(item, trimmed)
3355
+ })).filter(({ score }) => score > 0);
3356
+ scored.sort((a, b) => b.score - a.score);
3357
+ return scored.map(({ item }) => item);
3358
+ }
3359
+
3360
+ //#endregion
3361
+ //#region src/ui/slash-command/create-slash-command.ts
3362
+ function defaultFilterItems(items, query, editor) {
3363
+ return filterAndRankItems(isAtMaxColumnsDepth(editor) ? items.filter((item) => item.category !== "Layout" || !item.title.includes("column")) : items, query);
3364
+ }
3365
+ function createSlashCommand(options) {
3366
+ const items = options?.items ?? defaultSlashCommands;
3367
+ const filterFn = options?.filterItems ?? defaultFilterItems;
3368
+ return SlashCommandExtension.configure({ suggestion: {
3369
+ items: ({ query, editor }) => filterFn(items, query, editor),
3370
+ render: createRenderItems(options?.component)
3371
+ } });
3372
+ }
3373
+
3374
+ //#endregion
3375
+ //#region src/ui/slash-command/index.ts
3376
+ const SlashCommand = createSlashCommand();
3377
+
2915
3378
  //#endregion
2916
3379
  exports.AlignmentAttribute = AlignmentAttribute;
3380
+ exports.BULLET_LIST = BULLET_LIST;
3381
+ exports.BUTTON = BUTTON;
2917
3382
  exports.Body = Body;
2918
3383
  exports.Bold = Bold;
2919
3384
  exports.BubbleMenu = BubbleMenu;
@@ -2939,13 +3404,20 @@ exports.ButtonBubbleMenuDefault = ButtonBubbleMenuDefault;
2939
3404
  exports.ButtonBubbleMenuEditLink = ButtonBubbleMenuEditLink;
2940
3405
  exports.ButtonBubbleMenuRoot = ButtonBubbleMenuRoot;
2941
3406
  exports.ButtonBubbleMenuToolbar = ButtonBubbleMenuToolbar;
3407
+ exports.CODE = CODE;
2942
3408
  exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
2943
3409
  exports.ClassAttribute = ClassAttribute;
2944
3410
  exports.CodeBlockPrism = CodeBlockPrism;
2945
3411
  exports.ColumnsColumn = ColumnsColumn;
3412
+ exports.CommandList = CommandList;
3413
+ exports.DIVIDER = DIVIDER;
2946
3414
  exports.Div = Div;
2947
3415
  exports.EmailNode = EmailNode;
3416
+ exports.FOUR_COLUMNS = FOUR_COLUMNS;
2948
3417
  exports.FourColumns = FourColumns;
3418
+ exports.H1 = H1;
3419
+ exports.H2 = H2;
3420
+ exports.H3 = H3;
2949
3421
  exports.ImageBubbleMenu = ImageBubbleMenu;
2950
3422
  exports.ImageBubbleMenuDefault = ImageBubbleMenuDefault;
2951
3423
  exports.ImageBubbleMenuEditLink = ImageBubbleMenuEditLink;
@@ -2961,15 +3433,22 @@ exports.LinkBubbleMenuToolbar = LinkBubbleMenuToolbar;
2961
3433
  exports.LinkBubbleMenuUnlink = LinkBubbleMenuUnlink;
2962
3434
  exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
2963
3435
  exports.MaxNesting = MaxNesting;
3436
+ exports.NUMBERED_LIST = NUMBERED_LIST;
2964
3437
  exports.NodeSelectorContent = NodeSelectorContent;
2965
3438
  exports.NodeSelectorRoot = NodeSelectorRoot;
2966
3439
  exports.NodeSelectorTrigger = NodeSelectorTrigger;
2967
3440
  exports.Placeholder = Placeholder;
2968
3441
  exports.PreservedStyle = PreservedStyle;
2969
3442
  exports.PreviewText = PreviewText;
3443
+ exports.QUOTE = QUOTE;
3444
+ exports.SECTION = SECTION;
2970
3445
  exports.Section = Section;
3446
+ exports.SlashCommand = SlashCommand;
2971
3447
  exports.StyleAttribute = StyleAttribute;
2972
3448
  exports.Sup = Sup;
3449
+ exports.TEXT = TEXT;
3450
+ exports.THREE_COLUMNS = THREE_COLUMNS;
3451
+ exports.TWO_COLUMNS = TWO_COLUMNS;
2973
3452
  exports.Table = Table;
2974
3453
  exports.TableCell = TableCell;
2975
3454
  exports.TableHeader = TableHeader;
@@ -2978,9 +3457,15 @@ exports.ThreeColumns = ThreeColumns;
2978
3457
  exports.TwoColumns = TwoColumns;
2979
3458
  exports.Uppercase = Uppercase;
2980
3459
  exports.coreExtensions = coreExtensions;
3460
+ exports.createSlashCommand = createSlashCommand;
3461
+ exports.defaultSlashCommands = defaultSlashCommands;
2981
3462
  exports.editorEventBus = editorEventBus;
3463
+ exports.filterAndRankItems = filterAndRankItems;
2982
3464
  exports.getColumnsDepth = getColumnsDepth;
3465
+ exports.isAtMaxColumnsDepth = isAtMaxColumnsDepth;
3466
+ exports.isInsideNode = isInsideNode;
2983
3467
  exports.processStylesForUnlink = processStylesForUnlink;
3468
+ exports.scoreItem = scoreItem;
2984
3469
  exports.setTextAlignment = setTextAlignment;
2985
3470
  exports.useButtonBubbleMenuContext = useButtonBubbleMenuContext;
2986
3471
  exports.useImageBubbleMenuContext = useImageBubbleMenuContext;