@react-email/editor 0.0.0-experimental.21 → 0.0.0-experimental.22

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
@@ -78,10 +78,10 @@ let lucide_react = require("lucide-react");
78
78
  let _radix_ui_react_popover = require("@radix-ui/react-popover");
79
79
  _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
80
80
  let _tiptap_react_menus = require("@tiptap/react/menus");
81
+ let _floating_ui_react_dom = require("@floating-ui/react-dom");
81
82
  let _tiptap_suggestion = require("@tiptap/suggestion");
82
83
  _tiptap_suggestion = __toESM(_tiptap_suggestion);
83
- let tippy_js = require("tippy.js");
84
- tippy_js = __toESM(tippy_js);
84
+ let react_dom = require("react-dom");
85
85
 
86
86
  //#region src/core/event-bus.ts
87
87
  const EVENT_PREFIX = "@react-email/editor:";
@@ -3047,7 +3047,7 @@ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, on
3047
3047
 
3048
3048
  //#endregion
3049
3049
  //#region src/ui/bubble-menu/root.tsx
3050
- function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
3050
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children }) {
3051
3051
  const { editor } = (0, _tiptap_react.useCurrentEditor)();
3052
3052
  if (!editor) return null;
3053
3053
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react_menus.BubbleMenu, {
@@ -3060,7 +3060,7 @@ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, o
3060
3060
  },
3061
3061
  options: {
3062
3062
  placement,
3063
- offset,
3063
+ offset: offset$1,
3064
3064
  onHide
3065
3065
  },
3066
3066
  className,
@@ -3100,7 +3100,7 @@ const BubbleMenuUppercase = createMarkBubbleItem({
3100
3100
 
3101
3101
  //#endregion
3102
3102
  //#region src/ui/bubble-menu/default.tsx
3103
- function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset, onHide, className }) {
3103
+ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset: offset$1, onHide, className }) {
3104
3104
  const [isNodeSelectorOpen, setIsNodeSelectorOpen] = react.useState(false);
3105
3105
  const [isLinkSelectorOpen, setIsLinkSelectorOpen] = react.useState(false);
3106
3106
  const has = (item) => !excludeItems.includes(item);
@@ -3122,7 +3122,7 @@ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset,
3122
3122
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuRoot, {
3123
3123
  excludeNodes,
3124
3124
  placement,
3125
- offset,
3125
+ offset: offset$1,
3126
3126
  onHide: handleHide,
3127
3127
  className,
3128
3128
  children: [
@@ -3219,7 +3219,7 @@ function ButtonBubbleMenuEditLink({ className, children, onClick, onMouseDown, .
3219
3219
 
3220
3220
  //#endregion
3221
3221
  //#region src/ui/button-bubble-menu/root.tsx
3222
- function ButtonBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3222
+ function ButtonBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3223
3223
  const { editor } = (0, _tiptap_react.useCurrentEditor)();
3224
3224
  const [isEditing, setIsEditing] = react.useState(false);
3225
3225
  if (!editor) return null;
@@ -3229,7 +3229,7 @@ function ButtonBubbleMenuRoot({ onHide, placement = "top", offset = 8, className
3229
3229
  shouldShow: ({ editor: e, view }) => e.isActive("button") && !view.dom.classList.contains("dragging"),
3230
3230
  options: {
3231
3231
  placement,
3232
- offset,
3232
+ offset: offset$1,
3233
3233
  onHide: () => {
3234
3234
  setIsEditing(false);
3235
3235
  onHide?.();
@@ -3261,10 +3261,10 @@ function ButtonBubbleMenuToolbar({ children, ...rest }) {
3261
3261
 
3262
3262
  //#endregion
3263
3263
  //#region src/ui/button-bubble-menu/default.tsx
3264
- function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className }) {
3264
+ function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3265
3265
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuRoot, {
3266
3266
  placement,
3267
- offset,
3267
+ offset: offset$1,
3268
3268
  onHide,
3269
3269
  className,
3270
3270
  children: !excludeItems.includes("edit-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuToolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuEditLink, {}) })
@@ -3314,7 +3314,7 @@ function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ..
3314
3314
 
3315
3315
  //#endregion
3316
3316
  //#region src/ui/image-bubble-menu/root.tsx
3317
- function ImageBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3317
+ function ImageBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3318
3318
  const { editor } = (0, _tiptap_react.useCurrentEditor)();
3319
3319
  const [isEditing, setIsEditing] = react.useState(false);
3320
3320
  if (!editor) return null;
@@ -3324,7 +3324,7 @@ function ImageBubbleMenuRoot({ onHide, placement = "top", offset = 8, className,
3324
3324
  shouldShow: ({ editor: e, view }) => e.isActive("image") && !view.dom.classList.contains("dragging"),
3325
3325
  options: {
3326
3326
  placement,
3327
- offset,
3327
+ offset: offset$1,
3328
3328
  onHide: () => {
3329
3329
  setIsEditing(false);
3330
3330
  onHide?.();
@@ -3356,10 +3356,10 @@ function ImageBubbleMenuToolbar({ children, ...rest }) {
3356
3356
 
3357
3357
  //#endregion
3358
3358
  //#region src/ui/image-bubble-menu/default.tsx
3359
- function ImageBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className }) {
3359
+ function ImageBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3360
3360
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuRoot, {
3361
3361
  placement,
3362
- offset,
3362
+ offset: offset$1,
3363
3363
  onHide,
3364
3364
  className,
3365
3365
  children: !excludeItems.includes("edit-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuToolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuEditLink, {}) })
@@ -3531,7 +3531,7 @@ function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
3531
3531
 
3532
3532
  //#endregion
3533
3533
  //#region src/ui/link-bubble-menu/root.tsx
3534
- function LinkBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3534
+ function LinkBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3535
3535
  const { editor } = (0, _tiptap_react.useCurrentEditor)();
3536
3536
  const [isEditing, setIsEditing] = react.useState(false);
3537
3537
  const linkHref = (0, _tiptap_react.useEditorState)({
@@ -3545,7 +3545,7 @@ function LinkBubbleMenuRoot({ onHide, placement = "top", offset = 8, className,
3545
3545
  shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
3546
3546
  options: {
3547
3547
  placement,
3548
- offset,
3548
+ offset: offset$1,
3549
3549
  onHide: () => {
3550
3550
  setIsEditing(false);
3551
3551
  onHide?.();
@@ -3601,11 +3601,11 @@ function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...re
3601
3601
 
3602
3602
  //#endregion
3603
3603
  //#region src/ui/link-bubble-menu/default.tsx
3604
- function LinkBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
3604
+ function LinkBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
3605
3605
  const has = (item) => !excludeItems.includes(item);
3606
3606
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LinkBubbleMenuRoot, {
3607
3607
  placement,
3608
- offset,
3608
+ offset: offset$1,
3609
3609
  onHide,
3610
3610
  className,
3611
3611
  children: [(has("edit-link") || has("open-link") || has("unlink")) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LinkBubbleMenuToolbar, { children: [
@@ -3691,42 +3691,14 @@ function CommandItem({ item, selected, onSelect }) {
3691
3691
  children: [item.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.title })]
3692
3692
  });
3693
3693
  }
3694
- function CommandList({ items, command, query, ref }) {
3695
- const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
3694
+ function CommandList({ items, query, selectedIndex, onSelect }) {
3696
3695
  const containerRef = (0, react.useRef)(null);
3697
- (0, react.useEffect)(() => {
3698
- setSelectedIndex(0);
3699
- }, [items]);
3700
3696
  (0, react.useLayoutEffect)(() => {
3701
3697
  const container = containerRef.current;
3702
3698
  if (!container) return;
3703
3699
  const selected = container.querySelector("[data-selected]");
3704
3700
  if (selected) updateScrollView(container, selected);
3705
3701
  }, [selectedIndex]);
3706
- const selectItem = (0, react.useCallback)((index) => {
3707
- const item = items[index];
3708
- if (item) command(item);
3709
- }, [items, command]);
3710
- (0, react.useImperativeHandle)(ref, () => ({ onKeyDown: ({ event }) => {
3711
- if (items.length === 0) return false;
3712
- if (event.key === "ArrowUp") {
3713
- setSelectedIndex((i) => (i + items.length - 1) % items.length);
3714
- return true;
3715
- }
3716
- if (event.key === "ArrowDown") {
3717
- setSelectedIndex((i) => (i + 1) % items.length);
3718
- return true;
3719
- }
3720
- if (event.key === "Enter") {
3721
- selectItem(selectedIndex);
3722
- return true;
3723
- }
3724
- return false;
3725
- } }), [
3726
- items.length,
3727
- selectItem,
3728
- selectedIndex
3729
- ]);
3730
3702
  if (items.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3731
3703
  "data-re-slash-command": "",
3732
3704
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -3739,7 +3711,7 @@ function CommandList({ items, command, query, ref }) {
3739
3711
  ref: containerRef,
3740
3712
  children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3741
3713
  item,
3742
- onSelect: () => selectItem(index),
3714
+ onSelect: () => onSelect(index),
3743
3715
  selected: index === selectedIndex
3744
3716
  }, item.title))
3745
3717
  });
@@ -3755,7 +3727,7 @@ function CommandList({ items, command, query, ref }) {
3755
3727
  const currentIndex = flatIndex++;
3756
3728
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3757
3729
  item,
3758
- onSelect: () => selectItem(currentIndex),
3730
+ onSelect: () => onSelect(currentIndex),
3759
3731
  selected: currentIndex === selectedIndex
3760
3732
  }, item.title);
3761
3733
  })] }, group.category))
@@ -3973,76 +3945,6 @@ const defaultSlashCommands = [
3973
3945
  FOUR_COLUMNS
3974
3946
  ];
3975
3947
 
3976
- //#endregion
3977
- //#region src/ui/slash-command/extension.ts
3978
- const SlashCommandExtension = _tiptap_core.Extension.create({
3979
- name: "slash-command",
3980
- addOptions() {
3981
- return { suggestion: {
3982
- char: "/",
3983
- allow: ({ editor }) => !editor.isActive("codeBlock"),
3984
- command: ({ editor, range, props }) => {
3985
- props.command({
3986
- editor,
3987
- range
3988
- });
3989
- }
3990
- } };
3991
- },
3992
- addProseMirrorPlugins() {
3993
- return [(0, _tiptap_suggestion.default)({
3994
- pluginKey: new _tiptap_pm_state.PluginKey("slash-command"),
3995
- editor: this.editor,
3996
- ...this.options.suggestion
3997
- })];
3998
- }
3999
- });
4000
-
4001
- //#endregion
4002
- //#region src/ui/slash-command/render.tsx
4003
- function createRenderItems(component = CommandList) {
4004
- return () => {
4005
- let renderer = null;
4006
- let popup = null;
4007
- return {
4008
- onStart: (props) => {
4009
- renderer = new _tiptap_react.ReactRenderer(component, {
4010
- props,
4011
- editor: props.editor
4012
- });
4013
- if (!props.clientRect) return;
4014
- popup = (0, tippy_js.default)("body", {
4015
- getReferenceClientRect: props.clientRect,
4016
- appendTo: () => document.body,
4017
- content: renderer.element,
4018
- showOnCreate: true,
4019
- interactive: true,
4020
- trigger: "manual",
4021
- placement: "bottom-start"
4022
- });
4023
- },
4024
- onUpdate: (props) => {
4025
- if (!renderer) return;
4026
- renderer.updateProps(props);
4027
- if (popup?.[0] && props.clientRect) popup[0].setProps({ getReferenceClientRect: props.clientRect });
4028
- },
4029
- onKeyDown: (props) => {
4030
- if (props.event.key === "Escape") {
4031
- popup?.[0]?.hide();
4032
- return true;
4033
- }
4034
- return renderer?.ref?.onKeyDown(props) ?? false;
4035
- },
4036
- onExit: () => {
4037
- popup?.[0]?.destroy();
4038
- renderer?.destroy();
4039
- popup = null;
4040
- renderer = null;
4041
- }
4042
- };
4043
- };
4044
- }
4045
-
4046
3948
  //#endregion
4047
3949
  //#region src/ui/slash-command/search.ts
4048
3950
  function scoreItem(item, query) {
@@ -4073,22 +3975,144 @@ function filterAndRankItems(items, query) {
4073
3975
  }
4074
3976
 
4075
3977
  //#endregion
4076
- //#region src/ui/slash-command/create-slash-command.ts
3978
+ //#region src/ui/slash-command/root.tsx
3979
+ const pluginKey = new _tiptap_pm_state.PluginKey("slash-command");
3980
+ const INITIAL_STATE = {
3981
+ active: false,
3982
+ query: "",
3983
+ items: [],
3984
+ clientRect: null
3985
+ };
4077
3986
  function defaultFilterItems(items, query, editor) {
4078
3987
  return filterAndRankItems(isAtMaxColumnsDepth(editor) ? items.filter((item) => item.category !== "Layout" || !item.title.includes("column")) : items, query);
4079
3988
  }
4080
- function createSlashCommand(options) {
4081
- const items = options?.items ?? defaultSlashCommands;
4082
- const filterFn = options?.filterItems ?? defaultFilterItems;
4083
- return SlashCommandExtension.configure({ suggestion: {
4084
- items: ({ query, editor }) => filterFn(items, query, editor),
4085
- render: createRenderItems(options?.component)
4086
- } });
3989
+ function SlashCommandRoot({ items: itemsProp, filterItems: filterItemsProp, char = "/", allow: allowProp, children }) {
3990
+ const { editor } = (0, _tiptap_react.useCurrentEditor)();
3991
+ const [state, setState] = (0, react.useState)(INITIAL_STATE);
3992
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
3993
+ const itemsRef = (0, react.useRef)(itemsProp ?? defaultSlashCommands);
3994
+ const filterRef = (0, react.useRef)(filterItemsProp ?? defaultFilterItems);
3995
+ const allowRef = (0, react.useRef)(allowProp ?? (({ editor: e }) => !e.isActive("codeBlock")));
3996
+ itemsRef.current = itemsProp ?? defaultSlashCommands;
3997
+ filterRef.current = filterItemsProp ?? defaultFilterItems;
3998
+ allowRef.current = allowProp ?? (({ editor: e }) => !e.isActive("codeBlock"));
3999
+ const commandRef = (0, react.useRef)(null);
4000
+ const suggestionItemsRef = (0, react.useRef)([]);
4001
+ const selectedIndexRef = (0, react.useRef)(0);
4002
+ suggestionItemsRef.current = state.items;
4003
+ selectedIndexRef.current = selectedIndex;
4004
+ const { refs, floatingStyles } = (0, _floating_ui_react_dom.useFloating)({
4005
+ open: state.active,
4006
+ placement: "bottom-start",
4007
+ middleware: [
4008
+ (0, _floating_ui_react_dom.offset)(8),
4009
+ (0, _floating_ui_react_dom.flip)(),
4010
+ (0, _floating_ui_react_dom.shift)({ padding: 8 })
4011
+ ],
4012
+ whileElementsMounted: _floating_ui_react_dom.autoUpdate
4013
+ });
4014
+ (0, react.useEffect)(() => {
4015
+ if (!state.clientRect) return;
4016
+ refs.setReference({ getBoundingClientRect: state.clientRect });
4017
+ }, [state.clientRect, refs]);
4018
+ (0, react.useEffect)(() => {
4019
+ setSelectedIndex(0);
4020
+ }, [state.items]);
4021
+ const onSelect = (0, react.useCallback)((index) => {
4022
+ const item = suggestionItemsRef.current[index];
4023
+ if (item && commandRef.current) commandRef.current(item);
4024
+ }, []);
4025
+ (0, react.useEffect)(() => {
4026
+ if (!editor) return;
4027
+ const plugin = (0, _tiptap_suggestion.default)({
4028
+ pluginKey,
4029
+ editor,
4030
+ char,
4031
+ allow: ({ editor: e }) => allowRef.current({ editor: e }),
4032
+ command: ({ editor: e, range, props }) => {
4033
+ props.command({
4034
+ editor: e,
4035
+ range
4036
+ });
4037
+ },
4038
+ items: ({ query, editor: e }) => filterRef.current(itemsRef.current, query, e),
4039
+ render: () => ({
4040
+ onStart: (props) => {
4041
+ commandRef.current = props.command;
4042
+ setState({
4043
+ active: true,
4044
+ query: props.query,
4045
+ items: props.items,
4046
+ clientRect: props.clientRect ?? null
4047
+ });
4048
+ },
4049
+ onUpdate: (props) => {
4050
+ commandRef.current = props.command;
4051
+ setState({
4052
+ active: true,
4053
+ query: props.query,
4054
+ items: props.items,
4055
+ clientRect: props.clientRect ?? null
4056
+ });
4057
+ },
4058
+ onKeyDown: ({ event }) => {
4059
+ if (event.key === "Escape") {
4060
+ setState(INITIAL_STATE);
4061
+ return true;
4062
+ }
4063
+ const items = suggestionItemsRef.current;
4064
+ if (items.length === 0) return false;
4065
+ if (event.key === "ArrowUp") {
4066
+ setSelectedIndex((i) => (i + items.length - 1) % items.length);
4067
+ return true;
4068
+ }
4069
+ if (event.key === "ArrowDown") {
4070
+ setSelectedIndex((i) => (i + 1) % items.length);
4071
+ return true;
4072
+ }
4073
+ if (event.key === "Enter") {
4074
+ const item = items[selectedIndexRef.current];
4075
+ if (item && commandRef.current) commandRef.current(item);
4076
+ return true;
4077
+ }
4078
+ return false;
4079
+ },
4080
+ onExit: () => {
4081
+ setState(INITIAL_STATE);
4082
+ requestAnimationFrame(() => {
4083
+ commandRef.current = null;
4084
+ });
4085
+ }
4086
+ })
4087
+ });
4088
+ editor.registerPlugin(plugin, (newPlugin, plugins) => [newPlugin, ...plugins]);
4089
+ return () => {
4090
+ editor.unregisterPlugin(pluginKey);
4091
+ };
4092
+ }, [editor, char]);
4093
+ if (!editor || !state.active) return null;
4094
+ const renderProps = {
4095
+ items: state.items,
4096
+ query: state.query,
4097
+ selectedIndex,
4098
+ onSelect
4099
+ };
4100
+ let content;
4101
+ if (children) content = children(renderProps);
4102
+ else content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandList, { ...renderProps });
4103
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4104
+ ref: refs.setFloating,
4105
+ style: floatingStyles,
4106
+ children: content
4107
+ }), document.body);
4087
4108
  }
4088
4109
 
4089
4110
  //#endregion
4090
4111
  //#region src/ui/slash-command/index.ts
4091
- const SlashCommand = createSlashCommand();
4112
+ const SlashCommand = {
4113
+ Root: SlashCommandRoot,
4114
+ CommandList
4115
+ };
4092
4116
 
4093
4117
  //#endregion
4094
4118
  exports.AlignmentAttribute = AlignmentAttribute;
@@ -4187,7 +4211,6 @@ exports.TwoColumns = TwoColumns;
4187
4211
  exports.Underline = Underline;
4188
4212
  exports.Uppercase = Uppercase;
4189
4213
  exports.composeReactEmail = composeReactEmail;
4190
- exports.createSlashCommand = createSlashCommand;
4191
4214
  exports.defaultSlashCommands = defaultSlashCommands;
4192
4215
  exports.editorEventBus = editorEventBus;
4193
4216
  exports.filterAndRankItems = filterAndRankItems;