@react-email/editor 0.0.0-experimental.20 → 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.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  import * as ReactEmailComponents from "@react-email/components";
2
2
  import { Body as Body$1, Button as Button$1, CodeBlock, Column, Head, Heading as Heading$1, Hr, Html, Link as Link$1, Preview, Row, Section as Section$1, pretty, render, toPlainText } from "@react-email/components";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
- import { Extension, InputRule, Mark, Node as Node$1, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
4
+ import { Extension, InputRule, Mark, Node as Node$1, findChildren, mergeAttributes } from "@tiptap/core";
5
5
  import { UndoRedo } from "@tiptap/extensions";
6
- import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, ReactRenderer, useCurrentEditor, useEditor as useEditor$1, useEditorState } from "@tiptap/react";
6
+ import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useCurrentEditor, useEditor as useEditor$1, useEditorState } from "@tiptap/react";
7
7
  import * as React from "react";
8
- import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
8
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
9
9
  import TipTapStarterKit from "@tiptap/starter-kit";
10
10
  import BlockquoteBase from "@tiptap/extension-blockquote";
11
+ import BoldBase from "@tiptap/extension-bold";
11
12
  import BulletListBase from "@tiptap/extension-bullet-list";
12
13
  import CodeBase from "@tiptap/extension-code";
13
14
  import CodeBlock$1 from "@tiptap/extension-code-block";
@@ -25,12 +26,15 @@ import OrderedListBase from "@tiptap/extension-ordered-list";
25
26
  import ParagraphBase from "@tiptap/extension-paragraph";
26
27
  import TipTapPlaceholder from "@tiptap/extension-placeholder";
27
28
  import StrikeBase from "@tiptap/extension-strike";
29
+ import SuperscriptBase from "@tiptap/extension-superscript";
30
+ import UnderlineBase from "@tiptap/extension-underline";
28
31
  import { generateJSON } from "@tiptap/html";
29
32
  import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code as Code$1, CodeIcon, Columns2, Columns3, Columns4, ExternalLinkIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, MousePointer, PencilIcon, Rows2, SplitSquareVertical, SquareCode, StrikethroughIcon, Text, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
30
33
  import * as Popover from "@radix-ui/react-popover";
31
34
  import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
35
+ import { autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react-dom";
32
36
  import Suggestion from "@tiptap/suggestion";
33
- import tippy from "tippy.js";
37
+ import { createPortal } from "react-dom";
34
38
 
35
39
  //#region src/core/event-bus.ts
36
40
  const EVENT_PREFIX = "@react-email/editor:";
@@ -645,95 +649,23 @@ const Body = EmailNode.create({
645
649
 
646
650
  //#endregion
647
651
  //#region src/extensions/bold.tsx
648
- /**
649
- * Matches bold text via `**` as input.
650
- */
651
- const starInputRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
652
- /**
653
- * Matches bold text via `**` while pasting.
654
- */
655
- const starPasteRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
656
- /**
657
- * Matches bold text via `__` as input.
658
- */
659
- const underscoreInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
660
- /**
661
- * Matches bold text via `__` while pasting.
662
- */
663
- const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
664
- /**
665
- * This extension allows you to mark text as bold.
666
- * @see https://tiptap.dev/api/marks/bold
667
- */
668
- const Bold = EmailMark.create({
669
- name: "bold",
670
- addOptions() {
671
- return { HTMLAttributes: {} };
672
- },
673
- parseHTML() {
674
- return [
675
- { tag: "strong" },
676
- {
677
- tag: "b",
678
- getAttrs: (node) => node.style.fontWeight !== "normal" && null
679
- },
680
- {
681
- style: "font-weight=400",
682
- clearMark: (mark) => mark.type.name === this.name
683
- }
684
- ];
685
- },
686
- renderHTML({ HTMLAttributes }) {
687
- return [
688
- "strong",
689
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
690
- 0
691
- ];
692
- },
693
- renderToReactEmail({ children, style }) {
694
- return /* @__PURE__ */ jsx("strong", {
695
- style,
696
- children
697
- });
698
- },
699
- addCommands() {
700
- return {
701
- setBold: () => ({ commands }) => {
702
- return commands.setMark(this.name);
703
- },
704
- toggleBold: () => ({ commands }) => {
705
- return commands.toggleMark(this.name);
706
- },
707
- unsetBold: () => ({ commands }) => {
708
- return commands.unsetMark(this.name);
709
- }
710
- };
711
- },
712
- addKeyboardShortcuts() {
713
- return {
714
- "Mod-b": () => this.editor.commands.toggleBold(),
715
- "Mod-B": () => this.editor.commands.toggleBold()
716
- };
717
- },
718
- addInputRules() {
719
- return [markInputRule({
720
- find: starInputRegex,
721
- type: this.type
722
- }), markInputRule({
723
- find: underscoreInputRegex,
724
- type: this.type
725
- })];
726
- },
727
- addPasteRules() {
728
- return [markPasteRule({
729
- find: starPasteRegex,
730
- type: this.type
731
- }), markPasteRule({
732
- find: underscorePasteRegex,
733
- type: this.type
734
- })];
735
- }
736
- });
652
+ const BoldWithoutFontWeightInference = BoldBase.extend({ parseHTML() {
653
+ return [
654
+ { tag: "strong" },
655
+ {
656
+ tag: "b",
657
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
658
+ },
659
+ {
660
+ style: "font-weight=400",
661
+ clearMark: (mark) => mark.type.name === this.name
662
+ }
663
+ ];
664
+ } });
665
+ const Bold = EmailMark.from(BoldWithoutFontWeightInference, ({ children, style }) => /* @__PURE__ */ jsx("strong", {
666
+ style,
667
+ children
668
+ }));
737
669
 
738
670
  //#endregion
739
671
  //#region src/extensions/bullet-list.tsx
@@ -1733,33 +1665,11 @@ const StyleAttribute = Extension.create({
1733
1665
 
1734
1666
  //#endregion
1735
1667
  //#region src/extensions/sup.tsx
1736
- /**
1737
- * This extension allows you to mark text as superscript.
1738
- * @see https://tiptap.dev/api/marks/superscript
1739
- */
1740
- const Sup = EmailMark.create({
1668
+ const SupBase = SuperscriptBase.extend({
1741
1669
  name: "sup",
1742
- addOptions() {
1743
- return { HTMLAttributes: {} };
1744
- },
1745
- parseHTML() {
1746
- return [{ tag: "sup" }];
1747
- },
1748
- renderHTML({ HTMLAttributes }) {
1749
- return [
1750
- "sup",
1751
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1752
- 0
1753
- ];
1754
- },
1755
- renderToReactEmail({ children, style }) {
1756
- return /* @__PURE__ */ jsx("sup", {
1757
- style,
1758
- children
1759
- });
1760
- },
1761
1670
  addCommands() {
1762
1671
  return {
1672
+ ...this.parent?.(),
1763
1673
  setSup: () => ({ commands }) => {
1764
1674
  return commands.setMark(this.name);
1765
1675
  },
@@ -1772,6 +1682,10 @@ const Sup = EmailMark.create({
1772
1682
  };
1773
1683
  }
1774
1684
  });
1685
+ const Sup = EmailMark.from(SupBase, ({ children, style }) => /* @__PURE__ */ jsx("sup", {
1686
+ style,
1687
+ children
1688
+ }));
1775
1689
 
1776
1690
  //#endregion
1777
1691
  //#region src/extensions/table.tsx
@@ -1959,6 +1873,13 @@ const TableHeader = Node$1.create({
1959
1873
  }
1960
1874
  });
1961
1875
 
1876
+ //#endregion
1877
+ //#region src/extensions/underline.tsx
1878
+ const Underline = EmailMark.from(UnderlineBase, ({ children, style }) => /* @__PURE__ */ jsx("u", {
1879
+ style,
1880
+ children
1881
+ }));
1882
+
1962
1883
  //#endregion
1963
1884
  //#region src/extensions/uppercase.tsx
1964
1885
  const Uppercase = EmailMark.create({
@@ -2196,6 +2117,7 @@ const starterKitExtensions = {
2196
2117
  Divider,
2197
2118
  Link,
2198
2119
  Sup,
2120
+ Underline,
2199
2121
  Uppercase,
2200
2122
  PreservedStyle,
2201
2123
  Table,
@@ -2240,6 +2162,7 @@ const StarterKit = Extension.create({
2240
2162
  Divider: {},
2241
2163
  Link: {},
2242
2164
  Sup: {},
2165
+ Underline: {},
2243
2166
  Uppercase: {},
2244
2167
  PreservedStyle: {},
2245
2168
  Table: {},
@@ -3077,7 +3000,7 @@ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, on
3077
3000
 
3078
3001
  //#endregion
3079
3002
  //#region src/ui/bubble-menu/root.tsx
3080
- function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
3003
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children }) {
3081
3004
  const { editor } = useCurrentEditor();
3082
3005
  if (!editor) return null;
3083
3006
  return /* @__PURE__ */ jsx(BubbleMenu$1, {
@@ -3090,7 +3013,7 @@ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, o
3090
3013
  },
3091
3014
  options: {
3092
3015
  placement,
3093
- offset,
3016
+ offset: offset$1,
3094
3017
  onHide
3095
3018
  },
3096
3019
  className,
@@ -3130,7 +3053,7 @@ const BubbleMenuUppercase = createMarkBubbleItem({
3130
3053
 
3131
3054
  //#endregion
3132
3055
  //#region src/ui/bubble-menu/default.tsx
3133
- function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset, onHide, className }) {
3056
+ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset: offset$1, onHide, className }) {
3134
3057
  const [isNodeSelectorOpen, setIsNodeSelectorOpen] = React.useState(false);
3135
3058
  const [isLinkSelectorOpen, setIsLinkSelectorOpen] = React.useState(false);
3136
3059
  const has = (item) => !excludeItems.includes(item);
@@ -3152,7 +3075,7 @@ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset,
3152
3075
  return /* @__PURE__ */ jsxs(BubbleMenuRoot, {
3153
3076
  excludeNodes,
3154
3077
  placement,
3155
- offset,
3078
+ offset: offset$1,
3156
3079
  onHide: handleHide,
3157
3080
  className,
3158
3081
  children: [
@@ -3249,7 +3172,7 @@ function ButtonBubbleMenuEditLink({ className, children, onClick, onMouseDown, .
3249
3172
 
3250
3173
  //#endregion
3251
3174
  //#region src/ui/button-bubble-menu/root.tsx
3252
- function ButtonBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3175
+ function ButtonBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3253
3176
  const { editor } = useCurrentEditor();
3254
3177
  const [isEditing, setIsEditing] = React.useState(false);
3255
3178
  if (!editor) return null;
@@ -3259,7 +3182,7 @@ function ButtonBubbleMenuRoot({ onHide, placement = "top", offset = 8, className
3259
3182
  shouldShow: ({ editor: e, view }) => e.isActive("button") && !view.dom.classList.contains("dragging"),
3260
3183
  options: {
3261
3184
  placement,
3262
- offset,
3185
+ offset: offset$1,
3263
3186
  onHide: () => {
3264
3187
  setIsEditing(false);
3265
3188
  onHide?.();
@@ -3291,10 +3214,10 @@ function ButtonBubbleMenuToolbar({ children, ...rest }) {
3291
3214
 
3292
3215
  //#endregion
3293
3216
  //#region src/ui/button-bubble-menu/default.tsx
3294
- function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className }) {
3217
+ function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3295
3218
  return /* @__PURE__ */ jsx(ButtonBubbleMenuRoot, {
3296
3219
  placement,
3297
- offset,
3220
+ offset: offset$1,
3298
3221
  onHide,
3299
3222
  className,
3300
3223
  children: !excludeItems.includes("edit-link") && /* @__PURE__ */ jsx(ButtonBubbleMenuToolbar, { children: /* @__PURE__ */ jsx(ButtonBubbleMenuEditLink, {}) })
@@ -3344,7 +3267,7 @@ function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ..
3344
3267
 
3345
3268
  //#endregion
3346
3269
  //#region src/ui/image-bubble-menu/root.tsx
3347
- function ImageBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3270
+ function ImageBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3348
3271
  const { editor } = useCurrentEditor();
3349
3272
  const [isEditing, setIsEditing] = React.useState(false);
3350
3273
  if (!editor) return null;
@@ -3354,7 +3277,7 @@ function ImageBubbleMenuRoot({ onHide, placement = "top", offset = 8, className,
3354
3277
  shouldShow: ({ editor: e, view }) => e.isActive("image") && !view.dom.classList.contains("dragging"),
3355
3278
  options: {
3356
3279
  placement,
3357
- offset,
3280
+ offset: offset$1,
3358
3281
  onHide: () => {
3359
3282
  setIsEditing(false);
3360
3283
  onHide?.();
@@ -3386,10 +3309,10 @@ function ImageBubbleMenuToolbar({ children, ...rest }) {
3386
3309
 
3387
3310
  //#endregion
3388
3311
  //#region src/ui/image-bubble-menu/default.tsx
3389
- function ImageBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className }) {
3312
+ function ImageBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3390
3313
  return /* @__PURE__ */ jsx(ImageBubbleMenuRoot, {
3391
3314
  placement,
3392
- offset,
3315
+ offset: offset$1,
3393
3316
  onHide,
3394
3317
  className,
3395
3318
  children: !excludeItems.includes("edit-link") && /* @__PURE__ */ jsx(ImageBubbleMenuToolbar, { children: /* @__PURE__ */ jsx(ImageBubbleMenuEditLink, {}) })
@@ -3561,7 +3484,7 @@ function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
3561
3484
 
3562
3485
  //#endregion
3563
3486
  //#region src/ui/link-bubble-menu/root.tsx
3564
- function LinkBubbleMenuRoot({ onHide, placement = "top", offset = 8, className, children }) {
3487
+ function LinkBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3565
3488
  const { editor } = useCurrentEditor();
3566
3489
  const [isEditing, setIsEditing] = React.useState(false);
3567
3490
  const linkHref = useEditorState({
@@ -3575,7 +3498,7 @@ function LinkBubbleMenuRoot({ onHide, placement = "top", offset = 8, className,
3575
3498
  shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
3576
3499
  options: {
3577
3500
  placement,
3578
- offset,
3501
+ offset: offset$1,
3579
3502
  onHide: () => {
3580
3503
  setIsEditing(false);
3581
3504
  onHide?.();
@@ -3631,11 +3554,11 @@ function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...re
3631
3554
 
3632
3555
  //#endregion
3633
3556
  //#region src/ui/link-bubble-menu/default.tsx
3634
- function LinkBubbleMenuDefault({ excludeItems = [], placement, offset, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
3557
+ function LinkBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
3635
3558
  const has = (item) => !excludeItems.includes(item);
3636
3559
  return /* @__PURE__ */ jsxs(LinkBubbleMenuRoot, {
3637
3560
  placement,
3638
- offset,
3561
+ offset: offset$1,
3639
3562
  onHide,
3640
3563
  className,
3641
3564
  children: [(has("edit-link") || has("open-link") || has("unlink")) && /* @__PURE__ */ jsxs(LinkBubbleMenuToolbar, { children: [
@@ -3721,42 +3644,14 @@ function CommandItem({ item, selected, onSelect }) {
3721
3644
  children: [item.icon, /* @__PURE__ */ jsx("span", { children: item.title })]
3722
3645
  });
3723
3646
  }
3724
- function CommandList({ items, command, query, ref }) {
3725
- const [selectedIndex, setSelectedIndex] = useState(0);
3647
+ function CommandList({ items, query, selectedIndex, onSelect }) {
3726
3648
  const containerRef = useRef(null);
3727
- useEffect(() => {
3728
- setSelectedIndex(0);
3729
- }, [items]);
3730
3649
  useLayoutEffect(() => {
3731
3650
  const container = containerRef.current;
3732
3651
  if (!container) return;
3733
3652
  const selected = container.querySelector("[data-selected]");
3734
3653
  if (selected) updateScrollView(container, selected);
3735
3654
  }, [selectedIndex]);
3736
- const selectItem = useCallback((index) => {
3737
- const item = items[index];
3738
- if (item) command(item);
3739
- }, [items, command]);
3740
- useImperativeHandle(ref, () => ({ onKeyDown: ({ event }) => {
3741
- if (items.length === 0) return false;
3742
- if (event.key === "ArrowUp") {
3743
- setSelectedIndex((i) => (i + items.length - 1) % items.length);
3744
- return true;
3745
- }
3746
- if (event.key === "ArrowDown") {
3747
- setSelectedIndex((i) => (i + 1) % items.length);
3748
- return true;
3749
- }
3750
- if (event.key === "Enter") {
3751
- selectItem(selectedIndex);
3752
- return true;
3753
- }
3754
- return false;
3755
- } }), [
3756
- items.length,
3757
- selectItem,
3758
- selectedIndex
3759
- ]);
3760
3655
  if (items.length === 0) return /* @__PURE__ */ jsx("div", {
3761
3656
  "data-re-slash-command": "",
3762
3657
  children: /* @__PURE__ */ jsx("div", {
@@ -3769,7 +3664,7 @@ function CommandList({ items, command, query, ref }) {
3769
3664
  ref: containerRef,
3770
3665
  children: items.map((item, index) => /* @__PURE__ */ jsx(CommandItem, {
3771
3666
  item,
3772
- onSelect: () => selectItem(index),
3667
+ onSelect: () => onSelect(index),
3773
3668
  selected: index === selectedIndex
3774
3669
  }, item.title))
3775
3670
  });
@@ -3785,7 +3680,7 @@ function CommandList({ items, command, query, ref }) {
3785
3680
  const currentIndex = flatIndex++;
3786
3681
  return /* @__PURE__ */ jsx(CommandItem, {
3787
3682
  item,
3788
- onSelect: () => selectItem(currentIndex),
3683
+ onSelect: () => onSelect(currentIndex),
3789
3684
  selected: currentIndex === selectedIndex
3790
3685
  }, item.title);
3791
3686
  })] }, group.category))
@@ -4003,76 +3898,6 @@ const defaultSlashCommands = [
4003
3898
  FOUR_COLUMNS
4004
3899
  ];
4005
3900
 
4006
- //#endregion
4007
- //#region src/ui/slash-command/extension.ts
4008
- const SlashCommandExtension = Extension.create({
4009
- name: "slash-command",
4010
- addOptions() {
4011
- return { suggestion: {
4012
- char: "/",
4013
- allow: ({ editor }) => !editor.isActive("codeBlock"),
4014
- command: ({ editor, range, props }) => {
4015
- props.command({
4016
- editor,
4017
- range
4018
- });
4019
- }
4020
- } };
4021
- },
4022
- addProseMirrorPlugins() {
4023
- return [Suggestion({
4024
- pluginKey: new PluginKey("slash-command"),
4025
- editor: this.editor,
4026
- ...this.options.suggestion
4027
- })];
4028
- }
4029
- });
4030
-
4031
- //#endregion
4032
- //#region src/ui/slash-command/render.tsx
4033
- function createRenderItems(component = CommandList) {
4034
- return () => {
4035
- let renderer = null;
4036
- let popup = null;
4037
- return {
4038
- onStart: (props) => {
4039
- renderer = new ReactRenderer(component, {
4040
- props,
4041
- editor: props.editor
4042
- });
4043
- if (!props.clientRect) return;
4044
- popup = tippy("body", {
4045
- getReferenceClientRect: props.clientRect,
4046
- appendTo: () => document.body,
4047
- content: renderer.element,
4048
- showOnCreate: true,
4049
- interactive: true,
4050
- trigger: "manual",
4051
- placement: "bottom-start"
4052
- });
4053
- },
4054
- onUpdate: (props) => {
4055
- if (!renderer) return;
4056
- renderer.updateProps(props);
4057
- if (popup?.[0] && props.clientRect) popup[0].setProps({ getReferenceClientRect: props.clientRect });
4058
- },
4059
- onKeyDown: (props) => {
4060
- if (props.event.key === "Escape") {
4061
- popup?.[0]?.hide();
4062
- return true;
4063
- }
4064
- return renderer?.ref?.onKeyDown(props) ?? false;
4065
- },
4066
- onExit: () => {
4067
- popup?.[0]?.destroy();
4068
- renderer?.destroy();
4069
- popup = null;
4070
- renderer = null;
4071
- }
4072
- };
4073
- };
4074
- }
4075
-
4076
3901
  //#endregion
4077
3902
  //#region src/ui/slash-command/search.ts
4078
3903
  function scoreItem(item, query) {
@@ -4103,23 +3928,145 @@ function filterAndRankItems(items, query) {
4103
3928
  }
4104
3929
 
4105
3930
  //#endregion
4106
- //#region src/ui/slash-command/create-slash-command.ts
3931
+ //#region src/ui/slash-command/root.tsx
3932
+ const pluginKey = new PluginKey("slash-command");
3933
+ const INITIAL_STATE = {
3934
+ active: false,
3935
+ query: "",
3936
+ items: [],
3937
+ clientRect: null
3938
+ };
4107
3939
  function defaultFilterItems(items, query, editor) {
4108
3940
  return filterAndRankItems(isAtMaxColumnsDepth(editor) ? items.filter((item) => item.category !== "Layout" || !item.title.includes("column")) : items, query);
4109
3941
  }
4110
- function createSlashCommand(options) {
4111
- const items = options?.items ?? defaultSlashCommands;
4112
- const filterFn = options?.filterItems ?? defaultFilterItems;
4113
- return SlashCommandExtension.configure({ suggestion: {
4114
- items: ({ query, editor }) => filterFn(items, query, editor),
4115
- render: createRenderItems(options?.component)
4116
- } });
3942
+ function SlashCommandRoot({ items: itemsProp, filterItems: filterItemsProp, char = "/", allow: allowProp, children }) {
3943
+ const { editor } = useCurrentEditor();
3944
+ const [state, setState] = useState(INITIAL_STATE);
3945
+ const [selectedIndex, setSelectedIndex] = useState(0);
3946
+ const itemsRef = useRef(itemsProp ?? defaultSlashCommands);
3947
+ const filterRef = useRef(filterItemsProp ?? defaultFilterItems);
3948
+ const allowRef = useRef(allowProp ?? (({ editor: e }) => !e.isActive("codeBlock")));
3949
+ itemsRef.current = itemsProp ?? defaultSlashCommands;
3950
+ filterRef.current = filterItemsProp ?? defaultFilterItems;
3951
+ allowRef.current = allowProp ?? (({ editor: e }) => !e.isActive("codeBlock"));
3952
+ const commandRef = useRef(null);
3953
+ const suggestionItemsRef = useRef([]);
3954
+ const selectedIndexRef = useRef(0);
3955
+ suggestionItemsRef.current = state.items;
3956
+ selectedIndexRef.current = selectedIndex;
3957
+ const { refs, floatingStyles } = useFloating({
3958
+ open: state.active,
3959
+ placement: "bottom-start",
3960
+ middleware: [
3961
+ offset(8),
3962
+ flip(),
3963
+ shift({ padding: 8 })
3964
+ ],
3965
+ whileElementsMounted: autoUpdate
3966
+ });
3967
+ useEffect(() => {
3968
+ if (!state.clientRect) return;
3969
+ refs.setReference({ getBoundingClientRect: state.clientRect });
3970
+ }, [state.clientRect, refs]);
3971
+ useEffect(() => {
3972
+ setSelectedIndex(0);
3973
+ }, [state.items]);
3974
+ const onSelect = useCallback((index) => {
3975
+ const item = suggestionItemsRef.current[index];
3976
+ if (item && commandRef.current) commandRef.current(item);
3977
+ }, []);
3978
+ useEffect(() => {
3979
+ if (!editor) return;
3980
+ const plugin = Suggestion({
3981
+ pluginKey,
3982
+ editor,
3983
+ char,
3984
+ allow: ({ editor: e }) => allowRef.current({ editor: e }),
3985
+ command: ({ editor: e, range, props }) => {
3986
+ props.command({
3987
+ editor: e,
3988
+ range
3989
+ });
3990
+ },
3991
+ items: ({ query, editor: e }) => filterRef.current(itemsRef.current, query, e),
3992
+ render: () => ({
3993
+ onStart: (props) => {
3994
+ commandRef.current = props.command;
3995
+ setState({
3996
+ active: true,
3997
+ query: props.query,
3998
+ items: props.items,
3999
+ clientRect: props.clientRect ?? null
4000
+ });
4001
+ },
4002
+ onUpdate: (props) => {
4003
+ commandRef.current = props.command;
4004
+ setState({
4005
+ active: true,
4006
+ query: props.query,
4007
+ items: props.items,
4008
+ clientRect: props.clientRect ?? null
4009
+ });
4010
+ },
4011
+ onKeyDown: ({ event }) => {
4012
+ if (event.key === "Escape") {
4013
+ setState(INITIAL_STATE);
4014
+ return true;
4015
+ }
4016
+ const items = suggestionItemsRef.current;
4017
+ if (items.length === 0) return false;
4018
+ if (event.key === "ArrowUp") {
4019
+ setSelectedIndex((i) => (i + items.length - 1) % items.length);
4020
+ return true;
4021
+ }
4022
+ if (event.key === "ArrowDown") {
4023
+ setSelectedIndex((i) => (i + 1) % items.length);
4024
+ return true;
4025
+ }
4026
+ if (event.key === "Enter") {
4027
+ const item = items[selectedIndexRef.current];
4028
+ if (item && commandRef.current) commandRef.current(item);
4029
+ return true;
4030
+ }
4031
+ return false;
4032
+ },
4033
+ onExit: () => {
4034
+ setState(INITIAL_STATE);
4035
+ requestAnimationFrame(() => {
4036
+ commandRef.current = null;
4037
+ });
4038
+ }
4039
+ })
4040
+ });
4041
+ editor.registerPlugin(plugin, (newPlugin, plugins) => [newPlugin, ...plugins]);
4042
+ return () => {
4043
+ editor.unregisterPlugin(pluginKey);
4044
+ };
4045
+ }, [editor, char]);
4046
+ if (!editor || !state.active) return null;
4047
+ const renderProps = {
4048
+ items: state.items,
4049
+ query: state.query,
4050
+ selectedIndex,
4051
+ onSelect
4052
+ };
4053
+ let content;
4054
+ if (children) content = children(renderProps);
4055
+ else content = /* @__PURE__ */ jsx(CommandList, { ...renderProps });
4056
+ return createPortal(/* @__PURE__ */ jsx("div", {
4057
+ ref: refs.setFloating,
4058
+ style: floatingStyles,
4059
+ children: content
4060
+ }), document.body);
4117
4061
  }
4118
4062
 
4119
4063
  //#endregion
4120
4064
  //#region src/ui/slash-command/index.ts
4121
- const SlashCommand = createSlashCommand();
4065
+ const SlashCommand = {
4066
+ Root: SlashCommandRoot,
4067
+ CommandList
4068
+ };
4122
4069
 
4123
4070
  //#endregion
4124
- export { AlignmentAttribute, BULLET_LIST, BUTTON, Blockquote, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, BulletList, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, Code, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, Divider, EmailNode, FOUR_COLUMNS, FourColumns, GlobalContent, H1, H2, H3, HardBreak, Heading, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, Italic, Link, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, ListItem, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, OrderedList, Paragraph, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StarterKit, Strike, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, composeReactEmail, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, getGlobalContent, isAtMaxColumnsDepth, isDocumentVisuallyEmpty, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useEditor, useImageBubbleMenuContext, useLinkBubbleMenuContext };
4071
+ export { AlignmentAttribute, BULLET_LIST, BUTTON, Blockquote, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, BulletList, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, Code, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, Divider, EmailNode, FOUR_COLUMNS, FourColumns, GlobalContent, H1, H2, H3, HardBreak, Heading, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, Italic, Link, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, ListItem, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, OrderedList, Paragraph, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StarterKit, Strike, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Underline, Uppercase, composeReactEmail, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, getGlobalContent, isAtMaxColumnsDepth, isDocumentVisuallyEmpty, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useEditor, useImageBubbleMenuContext, useLinkBubbleMenuContext };
4125
4072
  //# sourceMappingURL=index.mjs.map