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

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.
Files changed (44) hide show
  1. package/dist/columns-CUxUEHje.mjs +497 -0
  2. package/dist/columns-CUxUEHje.mjs.map +1 -0
  3. package/dist/columns-ZSaLdkg9.cjs +630 -0
  4. package/dist/core/index.cjs +8 -0
  5. package/dist/core/index.d.cts +2 -0
  6. package/dist/core/index.d.mts +2 -0
  7. package/dist/core/index.mjs +4 -0
  8. package/dist/core-BjmRceVw.mjs +1999 -0
  9. package/dist/core-BjmRceVw.mjs.map +1 -0
  10. package/dist/core-iuG1UrYN.cjs +2250 -0
  11. package/dist/extensions/index.cjs +46 -0
  12. package/dist/extensions/index.d.cts +389 -0
  13. package/dist/extensions/index.d.cts.map +1 -0
  14. package/dist/extensions/index.d.mts +389 -0
  15. package/dist/extensions/index.d.mts.map +1 -0
  16. package/dist/extensions/index.mjs +4 -0
  17. package/dist/index-CfslA7KT.d.cts +130 -0
  18. package/dist/index-CfslA7KT.d.cts.map +1 -0
  19. package/dist/index-hbHRR7oB.d.mts +130 -0
  20. package/dist/index-hbHRR7oB.d.mts.map +1 -0
  21. package/dist/set-text-alignment-Bx3bPteH.cjs +24 -0
  22. package/dist/set-text-alignment-DZvgnbvz.mjs +19 -0
  23. package/dist/set-text-alignment-DZvgnbvz.mjs.map +1 -0
  24. package/dist/ui/index.cjs +1646 -0
  25. package/dist/ui/index.d.cts +668 -0
  26. package/dist/ui/index.d.cts.map +1 -0
  27. package/dist/ui/index.d.mts +668 -0
  28. package/dist/ui/index.d.mts.map +1 -0
  29. package/dist/ui/index.mjs +1584 -0
  30. package/dist/ui/index.mjs.map +1 -0
  31. package/dist/utils/index.cjs +3 -0
  32. package/dist/utils/index.d.cts +7 -0
  33. package/dist/utils/index.d.cts.map +1 -0
  34. package/dist/utils/index.d.mts +7 -0
  35. package/dist/utils/index.d.mts.map +1 -0
  36. package/dist/utils/index.mjs +3 -0
  37. package/package.json +38 -11
  38. package/dist/index.cjs +0 -4228
  39. package/dist/index.d.cts +0 -1175
  40. package/dist/index.d.cts.map +0 -1
  41. package/dist/index.d.mts +0 -1175
  42. package/dist/index.d.mts.map +0 -1
  43. package/dist/index.mjs +0 -4072
  44. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,1584 @@
1
+ import { _ as editorEventBus, i as MAX_COLUMNS_DEPTH, s as getColumnsDepth } from "../columns-CUxUEHje.mjs";
2
+ import { t as setTextAlignment } from "../set-text-alignment-DZvgnbvz.mjs";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { useCurrentEditor, useEditorState } from "@tiptap/react";
5
+ import * as React from "react";
6
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
7
+ import { PluginKey } from "@tiptap/pm/state";
8
+ import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code, 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";
9
+ import * as Popover from "@radix-ui/react-popover";
10
+ import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
11
+ import { autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react-dom";
12
+ import Suggestion from "@tiptap/suggestion";
13
+ import { createPortal } from "react-dom";
14
+
15
+ //#region src/ui/bubble-menu/context.tsx
16
+ const BubbleMenuContext = React.createContext(null);
17
+ function useBubbleMenuContext() {
18
+ const context = React.useContext(BubbleMenuContext);
19
+ if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
20
+ return context;
21
+ }
22
+
23
+ //#endregion
24
+ //#region src/ui/bubble-menu/item.tsx
25
+ function BubbleMenuItem({ name, isActive, onCommand, className, children, ...rest }) {
26
+ return /* @__PURE__ */ jsx("button", {
27
+ type: "button",
28
+ "aria-label": name,
29
+ "aria-pressed": isActive,
30
+ className,
31
+ "data-re-bubble-menu-item": "",
32
+ "data-item": name,
33
+ ...isActive ? { "data-active": "" } : {},
34
+ onMouseDown: (e) => e.preventDefault(),
35
+ onClick: onCommand,
36
+ ...rest,
37
+ children
38
+ });
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/ui/bubble-menu/align-center.tsx
43
+ function BubbleMenuAlignCenter({ className, children }) {
44
+ const { editor } = useBubbleMenuContext();
45
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
46
+ name: "align-center",
47
+ isActive: useEditorState({
48
+ editor,
49
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
50
+ }),
51
+ onCommand: () => setTextAlignment(editor, "center"),
52
+ className,
53
+ children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
54
+ });
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/ui/bubble-menu/align-left.tsx
59
+ function BubbleMenuAlignLeft({ className, children }) {
60
+ const { editor } = useBubbleMenuContext();
61
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
62
+ name: "align-left",
63
+ isActive: useEditorState({
64
+ editor,
65
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
66
+ }),
67
+ onCommand: () => setTextAlignment(editor, "left"),
68
+ className,
69
+ children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
70
+ });
71
+ }
72
+
73
+ //#endregion
74
+ //#region src/ui/bubble-menu/align-right.tsx
75
+ function BubbleMenuAlignRight({ className, children }) {
76
+ const { editor } = useBubbleMenuContext();
77
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
78
+ name: "align-right",
79
+ isActive: useEditorState({
80
+ editor,
81
+ selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
82
+ }),
83
+ onCommand: () => setTextAlignment(editor, "right"),
84
+ className,
85
+ children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
86
+ });
87
+ }
88
+
89
+ //#endregion
90
+ //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
91
+ function createMarkBubbleItem(config) {
92
+ function MarkBubbleItem({ className, children }) {
93
+ const { editor } = useBubbleMenuContext();
94
+ const isActive = useEditorState({
95
+ editor,
96
+ selector: ({ editor: editor$1 }) => {
97
+ if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
98
+ return editor$1?.isActive(config.activeName) ?? false;
99
+ }
100
+ });
101
+ const handleCommand = () => {
102
+ const chain = editor.chain().focus();
103
+ const method = chain[config.command];
104
+ if (method) method.call(chain).run();
105
+ };
106
+ return /* @__PURE__ */ jsx(BubbleMenuItem, {
107
+ name: config.name,
108
+ isActive,
109
+ onCommand: handleCommand,
110
+ className,
111
+ children: children ?? config.icon
112
+ });
113
+ }
114
+ MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
115
+ return MarkBubbleItem;
116
+ }
117
+
118
+ //#endregion
119
+ //#region src/ui/bubble-menu/bold.tsx
120
+ const BubbleMenuBold = createMarkBubbleItem({
121
+ name: "bold",
122
+ activeName: "bold",
123
+ command: "toggleBold",
124
+ icon: /* @__PURE__ */ jsx(BoldIcon, {})
125
+ });
126
+
127
+ //#endregion
128
+ //#region src/ui/bubble-menu/code.tsx
129
+ const BubbleMenuCode = createMarkBubbleItem({
130
+ name: "code",
131
+ activeName: "code",
132
+ command: "toggleCode",
133
+ icon: /* @__PURE__ */ jsx(CodeIcon, {})
134
+ });
135
+
136
+ //#endregion
137
+ //#region src/ui/bubble-menu/group.tsx
138
+ function BubbleMenuItemGroup({ className, children }) {
139
+ return /* @__PURE__ */ jsx("fieldset", {
140
+ className,
141
+ "data-re-bubble-menu-group": "",
142
+ children
143
+ });
144
+ }
145
+
146
+ //#endregion
147
+ //#region src/ui/bubble-menu/italic.tsx
148
+ const BubbleMenuItalic = createMarkBubbleItem({
149
+ name: "italic",
150
+ activeName: "italic",
151
+ command: "toggleItalic",
152
+ icon: /* @__PURE__ */ jsx(ItalicIcon, {})
153
+ });
154
+
155
+ //#endregion
156
+ //#region src/ui/bubble-menu/utils.ts
157
+ const SAFE_PROTOCOLS = new Set([
158
+ "http:",
159
+ "https:",
160
+ "mailto:",
161
+ "tel:"
162
+ ]);
163
+ /**
164
+ * Basic URL validation and auto-prefixing.
165
+ * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
166
+ * Returns the valid URL string or null.
167
+ */
168
+ function getUrlFromString(str) {
169
+ if (str === "#") return str;
170
+ try {
171
+ const url = new URL(str);
172
+ if (SAFE_PROTOCOLS.has(url.protocol)) return str;
173
+ return null;
174
+ } catch {}
175
+ try {
176
+ if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
177
+ } catch {}
178
+ return null;
179
+ }
180
+ function setLinkHref(editor, href) {
181
+ if (href.length === 0) {
182
+ editor.chain().unsetLink().run();
183
+ return;
184
+ }
185
+ const { from, to } = editor.state.selection;
186
+ if (from === to) {
187
+ editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
188
+ from,
189
+ to
190
+ }).run();
191
+ return;
192
+ }
193
+ editor.chain().setLink({ href }).run();
194
+ }
195
+ function focusEditor(editor) {
196
+ setTimeout(() => {
197
+ editor.commands.focus();
198
+ }, 0);
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/ui/bubble-menu/link-selector.tsx
203
+ function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children, open: controlledOpen, onOpenChange }) {
204
+ const { editor } = useBubbleMenuContext();
205
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
206
+ const isControlled = controlledOpen !== void 0;
207
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
208
+ const setIsOpen = React.useCallback((value) => {
209
+ if (!isControlled) setUncontrolledOpen(value);
210
+ onOpenChange?.(value);
211
+ }, [isControlled, onOpenChange]);
212
+ const editorState = useEditorState({
213
+ editor,
214
+ selector: ({ editor: editor$1 }) => ({
215
+ isLinkActive: editor$1?.isActive("link") ?? false,
216
+ hasLink: Boolean(editor$1?.getAttributes("link").href),
217
+ currentHref: editor$1?.getAttributes("link").href || ""
218
+ })
219
+ });
220
+ const setIsOpenRef = React.useRef(setIsOpen);
221
+ setIsOpenRef.current = setIsOpen;
222
+ React.useEffect(() => {
223
+ const subscription = editorEventBus.on("bubble-menu:add-link", () => {
224
+ setIsOpenRef.current(true);
225
+ });
226
+ return () => {
227
+ setIsOpenRef.current(false);
228
+ subscription.unsubscribe();
229
+ };
230
+ }, []);
231
+ if (!editorState) return null;
232
+ const handleOpenLink = () => {
233
+ setIsOpen(!isOpen);
234
+ };
235
+ return /* @__PURE__ */ jsxs("div", {
236
+ "data-re-link-selector": "",
237
+ ...isOpen ? { "data-open": "" } : {},
238
+ ...editorState.hasLink ? { "data-has-link": "" } : {},
239
+ className,
240
+ children: [showToggle && /* @__PURE__ */ jsx("button", {
241
+ type: "button",
242
+ "aria-expanded": isOpen,
243
+ "aria-haspopup": "true",
244
+ "aria-label": "Add link",
245
+ "aria-pressed": editorState.isLinkActive && editorState.hasLink,
246
+ "data-re-link-selector-trigger": "",
247
+ onClick: handleOpenLink,
248
+ children: /* @__PURE__ */ jsx(LinkIcon, {})
249
+ }), isOpen && /* @__PURE__ */ jsx(LinkForm, {
250
+ editor,
251
+ currentHref: editorState.currentHref,
252
+ validateUrl,
253
+ onLinkApply,
254
+ onLinkRemove,
255
+ setIsOpen,
256
+ children
257
+ })]
258
+ });
259
+ }
260
+ function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
261
+ const inputRef = React.useRef(null);
262
+ const formRef = React.useRef(null);
263
+ const displayHref = currentHref === "#" ? "" : currentHref;
264
+ const [inputValue, setInputValue] = React.useState(displayHref);
265
+ React.useEffect(() => {
266
+ const timeoutId = setTimeout(() => {
267
+ inputRef.current?.focus();
268
+ }, 0);
269
+ return () => clearTimeout(timeoutId);
270
+ }, []);
271
+ React.useEffect(() => {
272
+ const handleKeyDown = (event) => {
273
+ if (event.key === "Escape") {
274
+ if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
275
+ setIsOpen(false);
276
+ }
277
+ };
278
+ const handleClickOutside = (event) => {
279
+ if (formRef.current && !formRef.current.contains(event.target)) {
280
+ const form = formRef.current;
281
+ const submitEvent = new Event("submit", {
282
+ bubbles: true,
283
+ cancelable: true
284
+ });
285
+ form.dispatchEvent(submitEvent);
286
+ setIsOpen(false);
287
+ }
288
+ };
289
+ document.addEventListener("mousedown", handleClickOutside);
290
+ window.addEventListener("keydown", handleKeyDown);
291
+ return () => {
292
+ window.removeEventListener("keydown", handleKeyDown);
293
+ document.removeEventListener("mousedown", handleClickOutside);
294
+ };
295
+ }, [editor, setIsOpen]);
296
+ function handleSubmit(e) {
297
+ e.preventDefault();
298
+ const value = inputValue.trim();
299
+ if (value === "") {
300
+ setLinkHref(editor, "");
301
+ setIsOpen(false);
302
+ focusEditor(editor);
303
+ onLinkRemove?.();
304
+ return;
305
+ }
306
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
307
+ if (!finalValue) {
308
+ setLinkHref(editor, "");
309
+ setIsOpen(false);
310
+ focusEditor(editor);
311
+ onLinkRemove?.();
312
+ return;
313
+ }
314
+ setLinkHref(editor, finalValue);
315
+ setIsOpen(false);
316
+ focusEditor(editor);
317
+ onLinkApply?.(finalValue);
318
+ }
319
+ function handleUnlink(e) {
320
+ e.stopPropagation();
321
+ setLinkHref(editor, "");
322
+ setIsOpen(false);
323
+ focusEditor(editor);
324
+ onLinkRemove?.();
325
+ }
326
+ return /* @__PURE__ */ jsxs("form", {
327
+ ref: formRef,
328
+ "data-re-link-selector-form": "",
329
+ onMouseDown: (e) => e.stopPropagation(),
330
+ onClick: (e) => e.stopPropagation(),
331
+ onKeyDown: (e) => e.stopPropagation(),
332
+ onSubmit: handleSubmit,
333
+ children: [
334
+ /* @__PURE__ */ jsx("input", {
335
+ ref: inputRef,
336
+ "data-re-link-selector-input": "",
337
+ value: inputValue,
338
+ onFocus: (e) => e.stopPropagation(),
339
+ onChange: (e) => setInputValue(e.target.value),
340
+ placeholder: "Paste a link",
341
+ type: "text"
342
+ }),
343
+ children,
344
+ displayHref ? /* @__PURE__ */ jsx("button", {
345
+ type: "button",
346
+ "aria-label": "Remove link",
347
+ "data-re-link-selector-unlink": "",
348
+ onClick: handleUnlink,
349
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
350
+ }) : /* @__PURE__ */ jsx("button", {
351
+ type: "submit",
352
+ "aria-label": "Apply link",
353
+ "data-re-link-selector-apply": "",
354
+ onMouseDown: (e) => e.stopPropagation(),
355
+ children: /* @__PURE__ */ jsx(Check, {})
356
+ })
357
+ ]
358
+ });
359
+ }
360
+
361
+ //#endregion
362
+ //#region src/ui/bubble-menu/node-selector.tsx
363
+ const NodeSelectorContext = React.createContext(null);
364
+ function useNodeSelectorContext() {
365
+ const context = React.useContext(NodeSelectorContext);
366
+ if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
367
+ return context;
368
+ }
369
+ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
370
+ const { editor } = useBubbleMenuContext();
371
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
372
+ const isControlled = controlledOpen !== void 0;
373
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
374
+ const setIsOpen = React.useCallback((value) => {
375
+ if (!isControlled) setUncontrolledOpen(value);
376
+ onOpenChange?.(value);
377
+ }, [isControlled, onOpenChange]);
378
+ const editorState = useEditorState({
379
+ editor,
380
+ selector: ({ editor: editor$1 }) => ({
381
+ isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
382
+ isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
383
+ isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
384
+ isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
385
+ isBulletListActive: editor$1?.isActive("bulletList") ?? false,
386
+ isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
387
+ isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
388
+ isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
389
+ })
390
+ });
391
+ const allItems = React.useMemo(() => [
392
+ {
393
+ name: "Text",
394
+ icon: TextIcon,
395
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
396
+ isActive: editorState?.isParagraphActive ?? false
397
+ },
398
+ {
399
+ name: "Title",
400
+ icon: Heading1,
401
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
402
+ isActive: editorState?.isHeading1Active ?? false
403
+ },
404
+ {
405
+ name: "Subtitle",
406
+ icon: Heading2,
407
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
408
+ isActive: editorState?.isHeading2Active ?? false
409
+ },
410
+ {
411
+ name: "Heading",
412
+ icon: Heading3,
413
+ command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
414
+ isActive: editorState?.isHeading3Active ?? false
415
+ },
416
+ {
417
+ name: "Bullet List",
418
+ icon: List,
419
+ command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
420
+ isActive: editorState?.isBulletListActive ?? false
421
+ },
422
+ {
423
+ name: "Numbered List",
424
+ icon: ListOrdered,
425
+ command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
426
+ isActive: editorState?.isOrderedListActive ?? false
427
+ },
428
+ {
429
+ name: "Quote",
430
+ icon: TextQuote,
431
+ command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
432
+ isActive: editorState?.isBlockquoteActive ?? false
433
+ },
434
+ {
435
+ name: "Code",
436
+ icon: Code,
437
+ command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
438
+ isActive: editorState?.isCodeBlockActive ?? false
439
+ }
440
+ ], [editor, editorState]);
441
+ const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
442
+ const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
443
+ const contextValue = React.useMemo(() => ({
444
+ items,
445
+ activeItem,
446
+ isOpen,
447
+ setIsOpen
448
+ }), [
449
+ items,
450
+ activeItem,
451
+ isOpen,
452
+ setIsOpen
453
+ ]);
454
+ if (!editorState || items.length === 0) return null;
455
+ return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
456
+ value: contextValue,
457
+ children: /* @__PURE__ */ jsx(Popover.Root, {
458
+ open: isOpen,
459
+ onOpenChange: setIsOpen,
460
+ children: /* @__PURE__ */ jsx("div", {
461
+ "data-re-node-selector": "",
462
+ ...isOpen ? { "data-open": "" } : {},
463
+ className,
464
+ children
465
+ })
466
+ })
467
+ });
468
+ }
469
+ function NodeSelectorTrigger({ className, children }) {
470
+ const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
471
+ return /* @__PURE__ */ jsx(Popover.Trigger, {
472
+ "data-re-node-selector-trigger": "",
473
+ className,
474
+ onClick: () => setIsOpen(!isOpen),
475
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
476
+ });
477
+ }
478
+ function NodeSelectorContent({ className, align = "start", children }) {
479
+ const { items, setIsOpen } = useNodeSelectorContext();
480
+ return /* @__PURE__ */ jsx(Popover.Content, {
481
+ align,
482
+ "data-re-node-selector-content": "",
483
+ className,
484
+ children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
485
+ const Icon = item.icon;
486
+ return /* @__PURE__ */ jsxs("button", {
487
+ type: "button",
488
+ "data-re-node-selector-item": "",
489
+ ...item.isActive ? { "data-active": "" } : {},
490
+ onClick: () => {
491
+ item.command();
492
+ setIsOpen(false);
493
+ },
494
+ children: [
495
+ /* @__PURE__ */ jsx(Icon, {}),
496
+ /* @__PURE__ */ jsx("span", { children: item.name }),
497
+ item.isActive && /* @__PURE__ */ jsx(Check, {})
498
+ ]
499
+ }, item.name);
500
+ })
501
+ });
502
+ }
503
+ function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
504
+ return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
505
+ omit,
506
+ open,
507
+ onOpenChange,
508
+ className,
509
+ children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
510
+ });
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/ui/bubble-menu/root.tsx
515
+ function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children }) {
516
+ const { editor } = useCurrentEditor();
517
+ if (!editor) return null;
518
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
519
+ editor,
520
+ "data-re-bubble-menu": "",
521
+ shouldShow: ({ editor: editor$1, view }) => {
522
+ for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
523
+ if (view.dom.classList.contains("dragging")) return false;
524
+ return editor$1.view.state.selection.content().size > 0;
525
+ },
526
+ options: {
527
+ placement,
528
+ offset: offset$1,
529
+ onHide
530
+ },
531
+ className,
532
+ children: /* @__PURE__ */ jsx(BubbleMenuContext.Provider, {
533
+ value: { editor },
534
+ children
535
+ })
536
+ });
537
+ }
538
+
539
+ //#endregion
540
+ //#region src/ui/bubble-menu/strike.tsx
541
+ const BubbleMenuStrike = createMarkBubbleItem({
542
+ name: "strike",
543
+ activeName: "strike",
544
+ command: "toggleStrike",
545
+ icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
546
+ });
547
+
548
+ //#endregion
549
+ //#region src/ui/bubble-menu/underline.tsx
550
+ const BubbleMenuUnderline = createMarkBubbleItem({
551
+ name: "underline",
552
+ activeName: "underline",
553
+ command: "toggleUnderline",
554
+ icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
555
+ });
556
+
557
+ //#endregion
558
+ //#region src/ui/bubble-menu/uppercase.tsx
559
+ const BubbleMenuUppercase = createMarkBubbleItem({
560
+ name: "uppercase",
561
+ activeName: "uppercase",
562
+ command: "toggleUppercase",
563
+ icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
564
+ });
565
+
566
+ //#endregion
567
+ //#region src/ui/bubble-menu/default.tsx
568
+ function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset: offset$1, onHide, className }) {
569
+ const [isNodeSelectorOpen, setIsNodeSelectorOpen] = React.useState(false);
570
+ const [isLinkSelectorOpen, setIsLinkSelectorOpen] = React.useState(false);
571
+ const has = (item) => !excludeItems.includes(item);
572
+ const handleNodeSelectorOpenChange = React.useCallback((open) => {
573
+ setIsNodeSelectorOpen(open);
574
+ if (open) setIsLinkSelectorOpen(false);
575
+ }, []);
576
+ const handleLinkSelectorOpenChange = React.useCallback((open) => {
577
+ setIsLinkSelectorOpen(open);
578
+ if (open) setIsNodeSelectorOpen(false);
579
+ }, []);
580
+ const handleHide = React.useCallback(() => {
581
+ setIsNodeSelectorOpen(false);
582
+ setIsLinkSelectorOpen(false);
583
+ onHide?.();
584
+ }, [onHide]);
585
+ const hasFormattingItems = has("bold") || has("italic") || has("underline") || has("strike") || has("code") || has("uppercase");
586
+ const hasAlignmentItems = has("align-left") || has("align-center") || has("align-right");
587
+ return /* @__PURE__ */ jsxs(BubbleMenuRoot, {
588
+ excludeNodes,
589
+ placement,
590
+ offset: offset$1,
591
+ onHide: handleHide,
592
+ className,
593
+ children: [
594
+ has("node-selector") && /* @__PURE__ */ jsx(BubbleMenuNodeSelector, {
595
+ open: isNodeSelectorOpen,
596
+ onOpenChange: handleNodeSelectorOpenChange
597
+ }),
598
+ has("link-selector") && /* @__PURE__ */ jsx(BubbleMenuLinkSelector, {
599
+ open: isLinkSelectorOpen,
600
+ onOpenChange: handleLinkSelectorOpenChange
601
+ }),
602
+ hasFormattingItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
603
+ has("bold") && /* @__PURE__ */ jsx(BubbleMenuBold, {}),
604
+ has("italic") && /* @__PURE__ */ jsx(BubbleMenuItalic, {}),
605
+ has("underline") && /* @__PURE__ */ jsx(BubbleMenuUnderline, {}),
606
+ has("strike") && /* @__PURE__ */ jsx(BubbleMenuStrike, {}),
607
+ has("code") && /* @__PURE__ */ jsx(BubbleMenuCode, {}),
608
+ has("uppercase") && /* @__PURE__ */ jsx(BubbleMenuUppercase, {})
609
+ ] }),
610
+ hasAlignmentItems && /* @__PURE__ */ jsxs(BubbleMenuItemGroup, { children: [
611
+ has("align-left") && /* @__PURE__ */ jsx(BubbleMenuAlignLeft, {}),
612
+ has("align-center") && /* @__PURE__ */ jsx(BubbleMenuAlignCenter, {}),
613
+ has("align-right") && /* @__PURE__ */ jsx(BubbleMenuAlignRight, {})
614
+ ] })
615
+ ]
616
+ });
617
+ }
618
+
619
+ //#endregion
620
+ //#region src/ui/bubble-menu/separator.tsx
621
+ function BubbleMenuSeparator({ className }) {
622
+ return /* @__PURE__ */ jsx("hr", {
623
+ className,
624
+ "data-re-bubble-menu-separator": ""
625
+ });
626
+ }
627
+
628
+ //#endregion
629
+ //#region src/ui/bubble-menu/index.ts
630
+ const BubbleMenu = {
631
+ Root: BubbleMenuRoot,
632
+ ItemGroup: BubbleMenuItemGroup,
633
+ Separator: BubbleMenuSeparator,
634
+ Item: BubbleMenuItem,
635
+ Bold: BubbleMenuBold,
636
+ Italic: BubbleMenuItalic,
637
+ Underline: BubbleMenuUnderline,
638
+ Strike: BubbleMenuStrike,
639
+ Code: BubbleMenuCode,
640
+ Uppercase: BubbleMenuUppercase,
641
+ AlignLeft: BubbleMenuAlignLeft,
642
+ AlignCenter: BubbleMenuAlignCenter,
643
+ AlignRight: BubbleMenuAlignRight,
644
+ NodeSelector: Object.assign(BubbleMenuNodeSelector, {
645
+ Root: NodeSelectorRoot,
646
+ Trigger: NodeSelectorTrigger,
647
+ Content: NodeSelectorContent
648
+ }),
649
+ LinkSelector: BubbleMenuLinkSelector,
650
+ Default: BubbleMenuDefault
651
+ };
652
+
653
+ //#endregion
654
+ //#region src/ui/button-bubble-menu/context.tsx
655
+ const ButtonBubbleMenuContext = React.createContext(null);
656
+ function useButtonBubbleMenuContext() {
657
+ const context = React.useContext(ButtonBubbleMenuContext);
658
+ if (!context) throw new Error("ButtonBubbleMenu compound components must be used within <ButtonBubbleMenu.Root>");
659
+ return context;
660
+ }
661
+
662
+ //#endregion
663
+ //#region src/ui/button-bubble-menu/edit-link.tsx
664
+ function ButtonBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
665
+ const { setIsEditing } = useButtonBubbleMenuContext();
666
+ return /* @__PURE__ */ jsx("button", {
667
+ ...rest,
668
+ type: "button",
669
+ "aria-label": "Edit link",
670
+ "data-re-btn-bm-item": "",
671
+ "data-item": "edit-link",
672
+ className,
673
+ onMouseDown: (e) => {
674
+ e.preventDefault();
675
+ onMouseDown?.(e);
676
+ },
677
+ onClick: (e) => {
678
+ onClick?.(e);
679
+ setIsEditing(true);
680
+ },
681
+ children: children ?? /* @__PURE__ */ jsx(LinkIcon, {})
682
+ });
683
+ }
684
+
685
+ //#endregion
686
+ //#region src/ui/button-bubble-menu/root.tsx
687
+ function ButtonBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
688
+ const { editor } = useCurrentEditor();
689
+ const [isEditing, setIsEditing] = React.useState(false);
690
+ if (!editor) return null;
691
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
692
+ editor,
693
+ "data-re-btn-bm": "",
694
+ shouldShow: ({ editor: e, view }) => e.isActive("button") && !view.dom.classList.contains("dragging"),
695
+ options: {
696
+ placement,
697
+ offset: offset$1,
698
+ onHide: () => {
699
+ setIsEditing(false);
700
+ onHide?.();
701
+ }
702
+ },
703
+ className,
704
+ children: /* @__PURE__ */ jsx(ButtonBubbleMenuContext.Provider, {
705
+ value: {
706
+ editor,
707
+ isEditing,
708
+ setIsEditing
709
+ },
710
+ children
711
+ })
712
+ });
713
+ }
714
+
715
+ //#endregion
716
+ //#region src/ui/button-bubble-menu/toolbar.tsx
717
+ function ButtonBubbleMenuToolbar({ children, ...rest }) {
718
+ const { isEditing } = useButtonBubbleMenuContext();
719
+ if (isEditing) return null;
720
+ return /* @__PURE__ */ jsx("div", {
721
+ "data-re-btn-bm-toolbar": "",
722
+ ...rest,
723
+ children
724
+ });
725
+ }
726
+
727
+ //#endregion
728
+ //#region src/ui/button-bubble-menu/default.tsx
729
+ function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
730
+ return /* @__PURE__ */ jsx(ButtonBubbleMenuRoot, {
731
+ placement,
732
+ offset: offset$1,
733
+ onHide,
734
+ className,
735
+ children: !excludeItems.includes("edit-link") && /* @__PURE__ */ jsx(ButtonBubbleMenuToolbar, { children: /* @__PURE__ */ jsx(ButtonBubbleMenuEditLink, {}) })
736
+ });
737
+ }
738
+
739
+ //#endregion
740
+ //#region src/ui/button-bubble-menu/index.ts
741
+ const ButtonBubbleMenu = {
742
+ Root: ButtonBubbleMenuRoot,
743
+ Toolbar: ButtonBubbleMenuToolbar,
744
+ EditLink: ButtonBubbleMenuEditLink,
745
+ Default: ButtonBubbleMenuDefault
746
+ };
747
+
748
+ //#endregion
749
+ //#region src/ui/image-bubble-menu/context.tsx
750
+ const ImageBubbleMenuContext = React.createContext(null);
751
+ function useImageBubbleMenuContext() {
752
+ const context = React.useContext(ImageBubbleMenuContext);
753
+ if (!context) throw new Error("ImageBubbleMenu compound components must be used within <ImageBubbleMenu.Root>");
754
+ return context;
755
+ }
756
+
757
+ //#endregion
758
+ //#region src/ui/image-bubble-menu/edit-link.tsx
759
+ function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
760
+ const { setIsEditing } = useImageBubbleMenuContext();
761
+ return /* @__PURE__ */ jsx("button", {
762
+ ...rest,
763
+ type: "button",
764
+ "aria-label": "Edit link",
765
+ "data-re-img-bm-item": "",
766
+ "data-item": "edit-link",
767
+ className,
768
+ onMouseDown: (e) => {
769
+ e.preventDefault();
770
+ onMouseDown?.(e);
771
+ },
772
+ onClick: (e) => {
773
+ onClick?.(e);
774
+ setIsEditing(true);
775
+ },
776
+ children: children ?? /* @__PURE__ */ jsx(LinkIcon, {})
777
+ });
778
+ }
779
+
780
+ //#endregion
781
+ //#region src/ui/image-bubble-menu/root.tsx
782
+ function ImageBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
783
+ const { editor } = useCurrentEditor();
784
+ const [isEditing, setIsEditing] = React.useState(false);
785
+ if (!editor) return null;
786
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
787
+ editor,
788
+ "data-re-img-bm": "",
789
+ shouldShow: ({ editor: e, view }) => e.isActive("image") && !view.dom.classList.contains("dragging"),
790
+ options: {
791
+ placement,
792
+ offset: offset$1,
793
+ onHide: () => {
794
+ setIsEditing(false);
795
+ onHide?.();
796
+ }
797
+ },
798
+ className,
799
+ children: /* @__PURE__ */ jsx(ImageBubbleMenuContext.Provider, {
800
+ value: {
801
+ editor,
802
+ isEditing,
803
+ setIsEditing
804
+ },
805
+ children
806
+ })
807
+ });
808
+ }
809
+
810
+ //#endregion
811
+ //#region src/ui/image-bubble-menu/toolbar.tsx
812
+ function ImageBubbleMenuToolbar({ children, ...rest }) {
813
+ const { isEditing } = useImageBubbleMenuContext();
814
+ if (isEditing) return null;
815
+ return /* @__PURE__ */ jsx("div", {
816
+ "data-re-img-bm-toolbar": "",
817
+ ...rest,
818
+ children
819
+ });
820
+ }
821
+
822
+ //#endregion
823
+ //#region src/ui/image-bubble-menu/default.tsx
824
+ function ImageBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
825
+ return /* @__PURE__ */ jsx(ImageBubbleMenuRoot, {
826
+ placement,
827
+ offset: offset$1,
828
+ onHide,
829
+ className,
830
+ children: !excludeItems.includes("edit-link") && /* @__PURE__ */ jsx(ImageBubbleMenuToolbar, { children: /* @__PURE__ */ jsx(ImageBubbleMenuEditLink, {}) })
831
+ });
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/ui/image-bubble-menu/index.ts
836
+ const ImageBubbleMenu = {
837
+ Root: ImageBubbleMenuRoot,
838
+ Toolbar: ImageBubbleMenuToolbar,
839
+ EditLink: ImageBubbleMenuEditLink,
840
+ Default: ImageBubbleMenuDefault
841
+ };
842
+
843
+ //#endregion
844
+ //#region src/ui/link-bubble-menu/context.tsx
845
+ const LinkBubbleMenuContext = React.createContext(null);
846
+ function useLinkBubbleMenuContext() {
847
+ const context = React.useContext(LinkBubbleMenuContext);
848
+ if (!context) throw new Error("LinkBubbleMenu compound components must be used within <LinkBubbleMenu.Root>");
849
+ return context;
850
+ }
851
+
852
+ //#endregion
853
+ //#region src/ui/link-bubble-menu/edit-link.tsx
854
+ function LinkBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
855
+ const { setIsEditing } = useLinkBubbleMenuContext();
856
+ return /* @__PURE__ */ jsx("button", {
857
+ type: "button",
858
+ "aria-label": "Edit link",
859
+ "data-re-link-bm-item": "",
860
+ "data-item": "edit-link",
861
+ className,
862
+ onMouseDown: (e) => {
863
+ e.preventDefault();
864
+ onMouseDown?.(e);
865
+ },
866
+ onClick: (e) => {
867
+ onClick?.(e);
868
+ setIsEditing(true);
869
+ },
870
+ ...rest,
871
+ children: children ?? /* @__PURE__ */ jsx(PencilIcon, {})
872
+ });
873
+ }
874
+
875
+ //#endregion
876
+ //#region src/ui/link-bubble-menu/form.tsx
877
+ function LinkBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove, children }) {
878
+ const { editor, linkHref, isEditing, setIsEditing } = useLinkBubbleMenuContext();
879
+ const inputRef = React.useRef(null);
880
+ const formRef = React.useRef(null);
881
+ const displayHref = linkHref === "#" ? "" : linkHref;
882
+ const [inputValue, setInputValue] = React.useState(displayHref);
883
+ React.useEffect(() => {
884
+ if (!isEditing) return;
885
+ const timeoutId = setTimeout(() => {
886
+ inputRef.current?.focus();
887
+ }, 0);
888
+ return () => clearTimeout(timeoutId);
889
+ }, [isEditing]);
890
+ React.useEffect(() => {
891
+ if (!isEditing) return;
892
+ const handleKeyDown = (event) => {
893
+ if (event.key === "Escape") setIsEditing(false);
894
+ };
895
+ const handleClickOutside = (event) => {
896
+ if (formRef.current && !formRef.current.contains(event.target)) {
897
+ const form = formRef.current;
898
+ const submitEvent = new Event("submit", {
899
+ bubbles: true,
900
+ cancelable: true
901
+ });
902
+ form.dispatchEvent(submitEvent);
903
+ setIsEditing(false);
904
+ }
905
+ };
906
+ document.addEventListener("mousedown", handleClickOutside);
907
+ window.addEventListener("keydown", handleKeyDown);
908
+ return () => {
909
+ window.removeEventListener("keydown", handleKeyDown);
910
+ document.removeEventListener("mousedown", handleClickOutside);
911
+ };
912
+ }, [isEditing, setIsEditing]);
913
+ if (!isEditing) return null;
914
+ function handleSubmit(e) {
915
+ e.preventDefault();
916
+ const value = inputValue.trim();
917
+ if (value === "") {
918
+ setLinkHref(editor, "");
919
+ setIsEditing(false);
920
+ focusEditor(editor);
921
+ onLinkRemove?.();
922
+ return;
923
+ }
924
+ const finalValue = (validateUrl ?? getUrlFromString)(value);
925
+ if (!finalValue) {
926
+ setLinkHref(editor, "");
927
+ setIsEditing(false);
928
+ focusEditor(editor);
929
+ onLinkRemove?.();
930
+ return;
931
+ }
932
+ setLinkHref(editor, finalValue);
933
+ setIsEditing(false);
934
+ focusEditor(editor);
935
+ onLinkApply?.(finalValue);
936
+ }
937
+ function handleUnlink(e) {
938
+ e.stopPropagation();
939
+ setLinkHref(editor, "");
940
+ setIsEditing(false);
941
+ focusEditor(editor);
942
+ onLinkRemove?.();
943
+ }
944
+ return /* @__PURE__ */ jsxs("form", {
945
+ ref: formRef,
946
+ "data-re-link-bm-form": "",
947
+ className,
948
+ onMouseDown: (e) => e.stopPropagation(),
949
+ onClick: (e) => e.stopPropagation(),
950
+ onKeyDown: (e) => e.stopPropagation(),
951
+ onSubmit: handleSubmit,
952
+ children: [
953
+ /* @__PURE__ */ jsx("input", {
954
+ ref: inputRef,
955
+ "data-re-link-bm-input": "",
956
+ value: inputValue,
957
+ onFocus: (e) => e.stopPropagation(),
958
+ onChange: (e) => setInputValue(e.target.value),
959
+ placeholder: "Paste a link",
960
+ type: "text"
961
+ }),
962
+ children,
963
+ displayHref ? /* @__PURE__ */ jsx("button", {
964
+ type: "button",
965
+ "aria-label": "Remove link",
966
+ "data-re-link-bm-unlink": "",
967
+ onClick: handleUnlink,
968
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
969
+ }) : /* @__PURE__ */ jsx("button", {
970
+ type: "submit",
971
+ "aria-label": "Apply link",
972
+ "data-re-link-bm-apply": "",
973
+ onMouseDown: (e) => e.stopPropagation(),
974
+ children: /* @__PURE__ */ jsx(Check, {})
975
+ })
976
+ ]
977
+ });
978
+ }
979
+
980
+ //#endregion
981
+ //#region src/ui/link-bubble-menu/open-link.tsx
982
+ function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
983
+ const { linkHref } = useLinkBubbleMenuContext();
984
+ return /* @__PURE__ */ jsx("a", {
985
+ ...rest,
986
+ href: linkHref,
987
+ target: "_blank",
988
+ rel: "noopener noreferrer",
989
+ "aria-label": "Open link",
990
+ "data-re-link-bm-item": "",
991
+ "data-item": "open-link",
992
+ className,
993
+ children: children ?? /* @__PURE__ */ jsx(ExternalLinkIcon, {})
994
+ });
995
+ }
996
+
997
+ //#endregion
998
+ //#region src/ui/link-bubble-menu/root.tsx
999
+ function LinkBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
1000
+ const { editor } = useCurrentEditor();
1001
+ const [isEditing, setIsEditing] = React.useState(false);
1002
+ const linkHref = useEditorState({
1003
+ editor,
1004
+ selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
1005
+ });
1006
+ if (!editor) return null;
1007
+ return /* @__PURE__ */ jsx(BubbleMenu$1, {
1008
+ editor,
1009
+ "data-re-link-bm": "",
1010
+ shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
1011
+ options: {
1012
+ placement,
1013
+ offset: offset$1,
1014
+ onHide: () => {
1015
+ setIsEditing(false);
1016
+ onHide?.();
1017
+ }
1018
+ },
1019
+ className,
1020
+ children: /* @__PURE__ */ jsx(LinkBubbleMenuContext.Provider, {
1021
+ value: {
1022
+ editor,
1023
+ linkHref: linkHref ?? "",
1024
+ isEditing,
1025
+ setIsEditing
1026
+ },
1027
+ children
1028
+ })
1029
+ });
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region src/ui/link-bubble-menu/toolbar.tsx
1034
+ function LinkBubbleMenuToolbar({ children, ...rest }) {
1035
+ const { isEditing } = useLinkBubbleMenuContext();
1036
+ if (isEditing) return null;
1037
+ return /* @__PURE__ */ jsx("div", {
1038
+ "data-re-link-bm-toolbar": "",
1039
+ ...rest,
1040
+ children
1041
+ });
1042
+ }
1043
+
1044
+ //#endregion
1045
+ //#region src/ui/link-bubble-menu/unlink.tsx
1046
+ function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...rest }) {
1047
+ const { editor } = useLinkBubbleMenuContext();
1048
+ return /* @__PURE__ */ jsx("button", {
1049
+ type: "button",
1050
+ "aria-label": "Remove link",
1051
+ "data-re-link-bm-item": "",
1052
+ "data-item": "unlink",
1053
+ className,
1054
+ onMouseDown: (e) => {
1055
+ e.preventDefault();
1056
+ onMouseDown?.(e);
1057
+ },
1058
+ onClick: (e) => {
1059
+ onClick?.(e);
1060
+ editor.chain().focus().unsetLink().run();
1061
+ },
1062
+ ...rest,
1063
+ children: children ?? /* @__PURE__ */ jsx(UnlinkIcon, {})
1064
+ });
1065
+ }
1066
+
1067
+ //#endregion
1068
+ //#region src/ui/link-bubble-menu/default.tsx
1069
+ function LinkBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
1070
+ const has = (item) => !excludeItems.includes(item);
1071
+ return /* @__PURE__ */ jsxs(LinkBubbleMenuRoot, {
1072
+ placement,
1073
+ offset: offset$1,
1074
+ onHide,
1075
+ className,
1076
+ children: [(has("edit-link") || has("open-link") || has("unlink")) && /* @__PURE__ */ jsxs(LinkBubbleMenuToolbar, { children: [
1077
+ has("edit-link") && /* @__PURE__ */ jsx(LinkBubbleMenuEditLink, {}),
1078
+ has("open-link") && /* @__PURE__ */ jsx(LinkBubbleMenuOpenLink, {}),
1079
+ has("unlink") && /* @__PURE__ */ jsx(LinkBubbleMenuUnlink, {})
1080
+ ] }), /* @__PURE__ */ jsx(LinkBubbleMenuForm, {
1081
+ validateUrl,
1082
+ onLinkApply,
1083
+ onLinkRemove
1084
+ })]
1085
+ });
1086
+ }
1087
+
1088
+ //#endregion
1089
+ //#region src/ui/link-bubble-menu/index.ts
1090
+ const LinkBubbleMenu = {
1091
+ Root: LinkBubbleMenuRoot,
1092
+ Toolbar: LinkBubbleMenuToolbar,
1093
+ Form: LinkBubbleMenuForm,
1094
+ EditLink: LinkBubbleMenuEditLink,
1095
+ Unlink: LinkBubbleMenuUnlink,
1096
+ OpenLink: LinkBubbleMenuOpenLink,
1097
+ Default: LinkBubbleMenuDefault
1098
+ };
1099
+
1100
+ //#endregion
1101
+ //#region src/ui/slash-command/utils.ts
1102
+ function isInsideNode(editor, type) {
1103
+ const { $from } = editor.state.selection;
1104
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === type) return true;
1105
+ return false;
1106
+ }
1107
+ function isAtMaxColumnsDepth(editor) {
1108
+ const { from } = editor.state.selection;
1109
+ return getColumnsDepth(editor.state.doc, from) >= MAX_COLUMNS_DEPTH;
1110
+ }
1111
+ function updateScrollView(container, item) {
1112
+ const containerRect = container.getBoundingClientRect();
1113
+ const itemRect = item.getBoundingClientRect();
1114
+ if (itemRect.top < containerRect.top) container.scrollTop -= containerRect.top - itemRect.top;
1115
+ else if (itemRect.bottom > containerRect.bottom) container.scrollTop += itemRect.bottom - containerRect.bottom;
1116
+ }
1117
+
1118
+ //#endregion
1119
+ //#region src/ui/slash-command/command-list.tsx
1120
+ const CATEGORY_ORDER = [
1121
+ "Text",
1122
+ "Media",
1123
+ "Layout",
1124
+ "Utility"
1125
+ ];
1126
+ function groupByCategory(items) {
1127
+ const seen = /* @__PURE__ */ new Map();
1128
+ for (const item of items) {
1129
+ const existing = seen.get(item.category);
1130
+ if (existing) existing.push(item);
1131
+ else seen.set(item.category, [item]);
1132
+ }
1133
+ const ordered = [];
1134
+ for (const cat of CATEGORY_ORDER) {
1135
+ const group = seen.get(cat);
1136
+ if (group) {
1137
+ ordered.push({
1138
+ category: cat,
1139
+ items: group
1140
+ });
1141
+ seen.delete(cat);
1142
+ }
1143
+ }
1144
+ for (const [category, group] of seen) ordered.push({
1145
+ category,
1146
+ items: group
1147
+ });
1148
+ return ordered;
1149
+ }
1150
+ function CommandItem({ item, selected, onSelect }) {
1151
+ return /* @__PURE__ */ jsxs("button", {
1152
+ "data-re-slash-command-item": "",
1153
+ "data-selected": selected || void 0,
1154
+ onClick: onSelect,
1155
+ type: "button",
1156
+ children: [item.icon, /* @__PURE__ */ jsx("span", { children: item.title })]
1157
+ });
1158
+ }
1159
+ function CommandList({ items, query, selectedIndex, onSelect }) {
1160
+ const containerRef = useRef(null);
1161
+ useLayoutEffect(() => {
1162
+ const container = containerRef.current;
1163
+ if (!container) return;
1164
+ const selected = container.querySelector("[data-selected]");
1165
+ if (selected) updateScrollView(container, selected);
1166
+ }, [selectedIndex]);
1167
+ if (items.length === 0) return /* @__PURE__ */ jsx("div", {
1168
+ "data-re-slash-command": "",
1169
+ children: /* @__PURE__ */ jsx("div", {
1170
+ "data-re-slash-command-empty": "",
1171
+ children: "No results"
1172
+ })
1173
+ });
1174
+ if (query.trim().length > 0) return /* @__PURE__ */ jsx("div", {
1175
+ "data-re-slash-command": "",
1176
+ ref: containerRef,
1177
+ children: items.map((item, index) => /* @__PURE__ */ jsx(CommandItem, {
1178
+ item,
1179
+ onSelect: () => onSelect(index),
1180
+ selected: index === selectedIndex
1181
+ }, item.title))
1182
+ });
1183
+ const groups = groupByCategory(items);
1184
+ let flatIndex = 0;
1185
+ return /* @__PURE__ */ jsx("div", {
1186
+ "data-re-slash-command": "",
1187
+ ref: containerRef,
1188
+ children: groups.map((group) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
1189
+ "data-re-slash-command-category": "",
1190
+ children: group.category
1191
+ }), group.items.map((item) => {
1192
+ const currentIndex = flatIndex++;
1193
+ return /* @__PURE__ */ jsx(CommandItem, {
1194
+ item,
1195
+ onSelect: () => onSelect(currentIndex),
1196
+ selected: currentIndex === selectedIndex
1197
+ }, item.title);
1198
+ })] }, group.category))
1199
+ });
1200
+ }
1201
+
1202
+ //#endregion
1203
+ //#region src/ui/slash-command/commands.tsx
1204
+ const TEXT = {
1205
+ title: "Text",
1206
+ description: "Plain text block",
1207
+ icon: /* @__PURE__ */ jsx(Text, { size: 20 }),
1208
+ category: "Text",
1209
+ searchTerms: ["p", "paragraph"],
1210
+ command: ({ editor, range }) => {
1211
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
1212
+ }
1213
+ };
1214
+ const H1 = {
1215
+ title: "Title",
1216
+ description: "Large heading",
1217
+ icon: /* @__PURE__ */ jsx(Heading1, { size: 20 }),
1218
+ category: "Text",
1219
+ searchTerms: [
1220
+ "title",
1221
+ "big",
1222
+ "large",
1223
+ "h1"
1224
+ ],
1225
+ command: ({ editor, range }) => {
1226
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
1227
+ }
1228
+ };
1229
+ const H2 = {
1230
+ title: "Subtitle",
1231
+ description: "Medium heading",
1232
+ icon: /* @__PURE__ */ jsx(Heading2, { size: 20 }),
1233
+ category: "Text",
1234
+ searchTerms: [
1235
+ "subtitle",
1236
+ "medium",
1237
+ "h2"
1238
+ ],
1239
+ command: ({ editor, range }) => {
1240
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
1241
+ }
1242
+ };
1243
+ const H3 = {
1244
+ title: "Heading",
1245
+ description: "Small heading",
1246
+ icon: /* @__PURE__ */ jsx(Heading3, { size: 20 }),
1247
+ category: "Text",
1248
+ searchTerms: [
1249
+ "subtitle",
1250
+ "small",
1251
+ "h3"
1252
+ ],
1253
+ command: ({ editor, range }) => {
1254
+ editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
1255
+ }
1256
+ };
1257
+ const BULLET_LIST = {
1258
+ title: "Bullet list",
1259
+ description: "Unordered list",
1260
+ icon: /* @__PURE__ */ jsx(List, { size: 20 }),
1261
+ category: "Text",
1262
+ searchTerms: ["unordered", "point"],
1263
+ command: ({ editor, range }) => {
1264
+ editor.chain().focus().deleteRange(range).toggleBulletList().run();
1265
+ }
1266
+ };
1267
+ const NUMBERED_LIST = {
1268
+ title: "Numbered list",
1269
+ description: "Ordered list",
1270
+ icon: /* @__PURE__ */ jsx(ListOrdered, { size: 20 }),
1271
+ category: "Text",
1272
+ searchTerms: ["ordered"],
1273
+ command: ({ editor, range }) => {
1274
+ editor.chain().focus().deleteRange(range).toggleOrderedList().run();
1275
+ }
1276
+ };
1277
+ const QUOTE = {
1278
+ title: "Quote",
1279
+ description: "Block quote",
1280
+ icon: /* @__PURE__ */ jsx(TextQuote, { size: 20 }),
1281
+ category: "Text",
1282
+ searchTerms: ["blockquote"],
1283
+ command: ({ editor, range }) => {
1284
+ editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run();
1285
+ }
1286
+ };
1287
+ const CODE = {
1288
+ title: "Code block",
1289
+ description: "Code snippet",
1290
+ icon: /* @__PURE__ */ jsx(SquareCode, { size: 20 }),
1291
+ category: "Text",
1292
+ searchTerms: ["codeblock"],
1293
+ command: ({ editor, range }) => {
1294
+ editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
1295
+ }
1296
+ };
1297
+ const BUTTON = {
1298
+ title: "Button",
1299
+ description: "Clickable button",
1300
+ icon: /* @__PURE__ */ jsx(MousePointer, { size: 20 }),
1301
+ category: "Layout",
1302
+ searchTerms: ["button"],
1303
+ command: ({ editor, range }) => {
1304
+ editor.chain().focus().deleteRange(range).setButton().run();
1305
+ }
1306
+ };
1307
+ const DIVIDER = {
1308
+ title: "Divider",
1309
+ description: "Horizontal separator",
1310
+ icon: /* @__PURE__ */ jsx(SplitSquareVertical, { size: 20 }),
1311
+ category: "Layout",
1312
+ searchTerms: [
1313
+ "hr",
1314
+ "divider",
1315
+ "separator"
1316
+ ],
1317
+ command: ({ editor, range }) => {
1318
+ editor.chain().focus().deleteRange(range).setHorizontalRule().run();
1319
+ }
1320
+ };
1321
+ const SECTION = {
1322
+ title: "Section",
1323
+ description: "Content section",
1324
+ icon: /* @__PURE__ */ jsx(Rows2, { size: 20 }),
1325
+ category: "Layout",
1326
+ searchTerms: [
1327
+ "section",
1328
+ "row",
1329
+ "container"
1330
+ ],
1331
+ command: ({ editor, range }) => {
1332
+ editor.chain().focus().deleteRange(range).insertSection().run();
1333
+ }
1334
+ };
1335
+ const TWO_COLUMNS = {
1336
+ title: "2 columns",
1337
+ description: "Two column layout",
1338
+ icon: /* @__PURE__ */ jsx(Columns2, { size: 20 }),
1339
+ category: "Layout",
1340
+ searchTerms: [
1341
+ "columns",
1342
+ "column",
1343
+ "layout",
1344
+ "grid",
1345
+ "split",
1346
+ "side-by-side",
1347
+ "multi-column",
1348
+ "row",
1349
+ "two",
1350
+ "2"
1351
+ ],
1352
+ command: ({ editor, range }) => {
1353
+ editor.chain().focus().deleteRange(range).insertColumns(2).run();
1354
+ }
1355
+ };
1356
+ const THREE_COLUMNS = {
1357
+ title: "3 columns",
1358
+ description: "Three column layout",
1359
+ icon: /* @__PURE__ */ jsx(Columns3, { size: 20 }),
1360
+ category: "Layout",
1361
+ searchTerms: [
1362
+ "columns",
1363
+ "column",
1364
+ "layout",
1365
+ "grid",
1366
+ "split",
1367
+ "multi-column",
1368
+ "row",
1369
+ "three",
1370
+ "3"
1371
+ ],
1372
+ command: ({ editor, range }) => {
1373
+ editor.chain().focus().deleteRange(range).insertColumns(3).run();
1374
+ }
1375
+ };
1376
+ const FOUR_COLUMNS = {
1377
+ title: "4 columns",
1378
+ description: "Four column layout",
1379
+ icon: /* @__PURE__ */ jsx(Columns4, { size: 20 }),
1380
+ category: "Layout",
1381
+ searchTerms: [
1382
+ "columns",
1383
+ "column",
1384
+ "layout",
1385
+ "grid",
1386
+ "split",
1387
+ "multi-column",
1388
+ "row",
1389
+ "four",
1390
+ "4"
1391
+ ],
1392
+ command: ({ editor, range }) => {
1393
+ editor.chain().focus().deleteRange(range).insertColumns(4).run();
1394
+ }
1395
+ };
1396
+ const defaultSlashCommands = [
1397
+ TEXT,
1398
+ H1,
1399
+ H2,
1400
+ H3,
1401
+ BULLET_LIST,
1402
+ NUMBERED_LIST,
1403
+ QUOTE,
1404
+ CODE,
1405
+ BUTTON,
1406
+ DIVIDER,
1407
+ SECTION,
1408
+ TWO_COLUMNS,
1409
+ THREE_COLUMNS,
1410
+ FOUR_COLUMNS
1411
+ ];
1412
+
1413
+ //#endregion
1414
+ //#region src/ui/slash-command/search.ts
1415
+ function scoreItem(item, query) {
1416
+ if (!query) return 100;
1417
+ const q = query.toLowerCase();
1418
+ const title = item.title.toLowerCase();
1419
+ const description = item.description.toLowerCase();
1420
+ const terms = item.searchTerms?.map((t) => t.toLowerCase()) ?? [];
1421
+ if (title === q) return 100;
1422
+ if (title.startsWith(q)) return 90;
1423
+ if (title.split(/\s+/).some((w) => w.startsWith(q))) return 80;
1424
+ if (terms.some((t) => t === q)) return 70;
1425
+ if (terms.some((t) => t.startsWith(q))) return 60;
1426
+ if (title.includes(q)) return 40;
1427
+ if (terms.some((t) => t.includes(q))) return 30;
1428
+ if (description.includes(q)) return 20;
1429
+ return 0;
1430
+ }
1431
+ function filterAndRankItems(items, query) {
1432
+ const trimmed = query.trim();
1433
+ if (!trimmed) return items;
1434
+ const scored = items.map((item) => ({
1435
+ item,
1436
+ score: scoreItem(item, trimmed)
1437
+ })).filter(({ score }) => score > 0);
1438
+ scored.sort((a, b) => b.score - a.score);
1439
+ return scored.map(({ item }) => item);
1440
+ }
1441
+
1442
+ //#endregion
1443
+ //#region src/ui/slash-command/root.tsx
1444
+ const pluginKey = new PluginKey("slash-command");
1445
+ const INITIAL_STATE = {
1446
+ active: false,
1447
+ query: "",
1448
+ items: [],
1449
+ clientRect: null
1450
+ };
1451
+ function defaultFilterItems(items, query, editor) {
1452
+ return filterAndRankItems(isAtMaxColumnsDepth(editor) ? items.filter((item) => item.category !== "Layout" || !item.title.includes("column")) : items, query);
1453
+ }
1454
+ function SlashCommandRoot({ items: itemsProp, filterItems: filterItemsProp, char = "/", allow: allowProp, children }) {
1455
+ const { editor } = useCurrentEditor();
1456
+ const [state, setState] = useState(INITIAL_STATE);
1457
+ const [selectedIndex, setSelectedIndex] = useState(0);
1458
+ const itemsRef = useRef(itemsProp ?? defaultSlashCommands);
1459
+ const filterRef = useRef(filterItemsProp ?? defaultFilterItems);
1460
+ const allowRef = useRef(allowProp ?? (({ editor: e }) => !e.isActive("codeBlock")));
1461
+ itemsRef.current = itemsProp ?? defaultSlashCommands;
1462
+ filterRef.current = filterItemsProp ?? defaultFilterItems;
1463
+ allowRef.current = allowProp ?? (({ editor: e }) => !e.isActive("codeBlock"));
1464
+ const commandRef = useRef(null);
1465
+ const suggestionItemsRef = useRef([]);
1466
+ const selectedIndexRef = useRef(0);
1467
+ suggestionItemsRef.current = state.items;
1468
+ selectedIndexRef.current = selectedIndex;
1469
+ const { refs, floatingStyles } = useFloating({
1470
+ open: state.active,
1471
+ placement: "bottom-start",
1472
+ middleware: [
1473
+ offset(8),
1474
+ flip(),
1475
+ shift({ padding: 8 })
1476
+ ],
1477
+ whileElementsMounted: autoUpdate
1478
+ });
1479
+ useEffect(() => {
1480
+ if (!state.clientRect) return;
1481
+ refs.setReference({ getBoundingClientRect: state.clientRect });
1482
+ }, [state.clientRect, refs]);
1483
+ useEffect(() => {
1484
+ setSelectedIndex(0);
1485
+ }, [state.items]);
1486
+ const onSelect = useCallback((index) => {
1487
+ const item = suggestionItemsRef.current[index];
1488
+ if (item && commandRef.current) commandRef.current(item);
1489
+ }, []);
1490
+ useEffect(() => {
1491
+ if (!editor) return;
1492
+ const plugin = Suggestion({
1493
+ pluginKey,
1494
+ editor,
1495
+ char,
1496
+ allow: ({ editor: e }) => allowRef.current({ editor: e }),
1497
+ command: ({ editor: e, range, props }) => {
1498
+ props.command({
1499
+ editor: e,
1500
+ range
1501
+ });
1502
+ },
1503
+ items: ({ query, editor: e }) => filterRef.current(itemsRef.current, query, e),
1504
+ render: () => ({
1505
+ onStart: (props) => {
1506
+ commandRef.current = props.command;
1507
+ setState({
1508
+ active: true,
1509
+ query: props.query,
1510
+ items: props.items,
1511
+ clientRect: props.clientRect ?? null
1512
+ });
1513
+ },
1514
+ onUpdate: (props) => {
1515
+ commandRef.current = props.command;
1516
+ setState({
1517
+ active: true,
1518
+ query: props.query,
1519
+ items: props.items,
1520
+ clientRect: props.clientRect ?? null
1521
+ });
1522
+ },
1523
+ onKeyDown: ({ event }) => {
1524
+ if (event.key === "Escape") {
1525
+ setState(INITIAL_STATE);
1526
+ return true;
1527
+ }
1528
+ const items = suggestionItemsRef.current;
1529
+ if (items.length === 0) return false;
1530
+ if (event.key === "ArrowUp") {
1531
+ setSelectedIndex((i) => (i + items.length - 1) % items.length);
1532
+ return true;
1533
+ }
1534
+ if (event.key === "ArrowDown") {
1535
+ setSelectedIndex((i) => (i + 1) % items.length);
1536
+ return true;
1537
+ }
1538
+ if (event.key === "Enter") {
1539
+ const item = items[selectedIndexRef.current];
1540
+ if (item && commandRef.current) commandRef.current(item);
1541
+ return true;
1542
+ }
1543
+ return false;
1544
+ },
1545
+ onExit: () => {
1546
+ setState(INITIAL_STATE);
1547
+ requestAnimationFrame(() => {
1548
+ commandRef.current = null;
1549
+ });
1550
+ }
1551
+ })
1552
+ });
1553
+ editor.registerPlugin(plugin, (newPlugin, plugins) => [newPlugin, ...plugins]);
1554
+ return () => {
1555
+ editor.unregisterPlugin(pluginKey);
1556
+ };
1557
+ }, [editor, char]);
1558
+ if (!editor || !state.active) return null;
1559
+ const renderProps = {
1560
+ items: state.items,
1561
+ query: state.query,
1562
+ selectedIndex,
1563
+ onSelect
1564
+ };
1565
+ let content;
1566
+ if (children) content = children(renderProps);
1567
+ else content = /* @__PURE__ */ jsx(CommandList, { ...renderProps });
1568
+ return createPortal(/* @__PURE__ */ jsx("div", {
1569
+ ref: refs.setFloating,
1570
+ style: floatingStyles,
1571
+ children: content
1572
+ }), document.body);
1573
+ }
1574
+
1575
+ //#endregion
1576
+ //#region src/ui/slash-command/index.ts
1577
+ const SlashCommand = {
1578
+ Root: SlashCommandRoot,
1579
+ CommandList
1580
+ };
1581
+
1582
+ //#endregion
1583
+ export { BULLET_LIST, BUTTON, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, CommandList, DIVIDER, FOUR_COLUMNS, H1, H2, H3, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, QUOTE, SECTION, SlashCommand, TEXT, THREE_COLUMNS, TWO_COLUMNS, defaultSlashCommands, filterAndRankItems, isAtMaxColumnsDepth, isInsideNode, scoreItem, useButtonBubbleMenuContext, useImageBubbleMenuContext, useLinkBubbleMenuContext };
1584
+ //# sourceMappingURL=index.mjs.map