@meowdown/react 0.80.3 → 0.81.1

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.d.ts CHANGED
@@ -2,6 +2,14 @@ import { ComponentProps, ReactElement, ReactNode, Ref } from "react";
2
2
  import { AcceptPendingReplacementOptions, EditorConfig, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, ImageUrlResolver, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, ReferenceDefinitions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver, XPostMediaClickHandler, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoClickHandler, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
4
  import { ReactNodeViewComponent, ReactNodeViewComponent as ReactNodeViewComponent$1, useEditor, useExtension, useKeymap } from "@prosekit/react";
5
+ import { Popover } from "@base-ui/react/popover";
6
+ //#region src/components/collision-boundary.d.ts
7
+ /**
8
+ * The area the editor's floating menus and popovers stay inside. Same as the
9
+ * `collisionBoundary` prop of Base UI positioners.
10
+ */
11
+ type CollisionBoundary = NonNullable<Popover.Positioner.Props['collisionBoundary']>;
12
+ //#endregion
5
13
  //#region src/utils/date-format.d.ts
6
14
  type TimeFormat = '12' | '24';
7
15
  //#endregion
@@ -330,6 +338,12 @@ interface EditorProps {
330
338
  * is set. Ignored when `readOnly` is set.
331
339
  */
332
340
  selectionMenuAffordance?: boolean | undefined;
341
+ /**
342
+ * The element the editor's menus and popovers stay inside, for example one
343
+ * that leaves out the area under a phone's status bar. Defaults to the
344
+ * viewport.
345
+ */
346
+ collisionBoundary?: CollisionBoundary | undefined;
333
347
  /**
334
348
  * Extra controls rendered in the pending-replacement preview footer, next to
335
349
  * the built-in accept ("Replace selection" / "Insert below", per the staged
@@ -583,7 +597,7 @@ interface EditorProps {
583
597
  *
584
598
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
585
599
  */
586
- export declare function MarkdownEditor({ mode, initialMarkdown, singleParagraph, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste, linkPaste, bulletAfterHeading, backspaceDeletesEmptyFirstBlock, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }: EditorProps): ReactElement;
600
+ export declare function MarkdownEditor({ mode, initialMarkdown, singleParagraph, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, collisionBoundary, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste, linkPaste, bulletAfterHeading, backspaceDeletesEmptyFirstBlock, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }: EditorProps): ReactElement;
587
601
  //#endregion
588
602
  //#region src/components/markdown-view.d.ts
589
603
  /**
@@ -930,4 +944,4 @@ interface WikilinkHoverCardProps {
930
944
  */
931
945
  export declare function WikilinkHoverCard({ children, className }: WikilinkHoverCardProps): ReactNode;
932
946
  //#endregion
933
- export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type LightboxCloseOptions, type LightboxController, type LightboxFrameItem, type LightboxFrameProps, type LightboxImageItem, type LightboxImageProps, type LightboxItem, type LightboxRootProps, type LightboxVideoItem, type LightboxVideoProps, type LinkPreview, type LinkPreviewResolver, type MarkdownViewProps, type PendingReplacementResolveHandler, type ReactNodeViewComponent, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TaskClickHandler, type TaskClickPayload, type TimeFormat, type WikilinkHoverCardProps, type WikilinkItem, type WikilinkSearchHandler, type XPostResolver, type YouTubeVideoResolver, useEditor, useExtension, useKeymap };
947
+ export { type CollisionBoundary, type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type LightboxCloseOptions, type LightboxController, type LightboxFrameItem, type LightboxFrameProps, type LightboxImageItem, type LightboxImageProps, type LightboxItem, type LightboxRootProps, type LightboxVideoItem, type LightboxVideoProps, type LinkPreview, type LinkPreviewResolver, type MarkdownViewProps, type PendingReplacementResolveHandler, type ReactNodeViewComponent, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TaskClickHandler, type TaskClickPayload, type TimeFormat, type WikilinkHoverCardProps, type WikilinkItem, type WikilinkSearchHandler, type XPostResolver, type YouTubeVideoResolver, useEditor, useExtension, useKeymap };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { clsx } from "clsx/lite";
2
- import { Fragment, ViewTransition, cloneElement, createElement, memo, startTransition, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
+ import { Fragment, ViewTransition, cloneElement, createContext, createElement, memo, startTransition, use, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
3
  import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineCodeBlockPreviewPlugin, defineEditorExtension, defineLinkEditKeymap, defineLinkHoverHandler, definePendingReplacementHandler, defineSearchStatusHandler, defineVirtualCaret, defineWikilinkHoverHandler, dismissLinkHover, docToMarkdown, docToParagraphMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, paragraphMarkdownToDoc, parsePostEmbedSnapshot, renderMathInto, updateEditorConfig } from "@meowdown/core";
4
4
  import { clamp } from "@ocavue/utils";
5
5
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
@@ -98,6 +98,20 @@ var code_block_view_module_default = {
98
98
  "TriggerIcon": "meow_TriggerIcon_D3j_9q"
99
99
  };
100
100
 
101
+ //#endregion
102
+ //#region src/components/collision-boundary.ts
103
+ /**
104
+ * See `EditorProps.collisionBoundary`.
105
+ */
106
+ const CollisionBoundaryContext = createContext(void 0);
107
+ /**
108
+ * Converts a {@link CollisionBoundary} to the `boundary` prop of ProseKit
109
+ * positioners, which spell the keyword the floating-ui way.
110
+ */
111
+ function toFloatingBoundary(boundary) {
112
+ return boundary === "clipping-ancestors" ? "clippingAncestors" : boundary;
113
+ }
114
+
101
115
  //#endregion
102
116
  //#region src/components/copy-button.tsx
103
117
  const COPIED_RESET_MS = 1500;
@@ -272,6 +286,25 @@ function CodeBlockView(props) {
272
286
  });
273
287
  }
274
288
  function CodeBlockToolbar({ code, language, setLanguage }) {
289
+ const [comboboxOpen, setComboboxOpen] = useState(false);
290
+ return /* @__PURE__ */ jsxs("div", {
291
+ className: code_block_view_module_default.Toolbar,
292
+ contentEditable: false,
293
+ "data-open": comboboxOpen || void 0,
294
+ children: [/* @__PURE__ */ jsx(CodeBlockLanguagePickerMemo, {
295
+ language,
296
+ setLanguage,
297
+ onShownChange: setComboboxOpen
298
+ }), /* @__PURE__ */ jsx(CopyButton, {
299
+ getText: () => code,
300
+ label: "Copy code",
301
+ className: code_block_view_module_default.CopyButton,
302
+ "data-testid": "code-block-copy"
303
+ })]
304
+ });
305
+ }
306
+ function CodeBlockLanguagePicker({ language, setLanguage, onShownChange }) {
307
+ const collisionBoundary = use(CollisionBoundaryContext);
275
308
  const selected = useMemo(() => {
276
309
  return codeBlockLanguages.find((item) => item.value === language) ?? {
277
310
  value: language,
@@ -279,7 +312,6 @@ function CodeBlockToolbar({ code, language, setLanguage }) {
279
312
  };
280
313
  }, [language]);
281
314
  const [query, setQuery] = useState("");
282
- const [comboboxOpen, setComboboxOpen] = useState(false);
283
315
  const itemsForView = useMemo(() => {
284
316
  const value = query.trim();
285
317
  if (!value) return codeBlockLanguages;
@@ -289,73 +321,65 @@ function CodeBlockToolbar({ code, language, setLanguage }) {
289
321
  label: `Use "${value}"`
290
322
  }];
291
323
  }, [query]);
292
- return /* @__PURE__ */ jsxs("div", {
293
- className: code_block_view_module_default.Toolbar,
294
- contentEditable: false,
295
- "data-open": comboboxOpen || void 0,
296
- children: [/* @__PURE__ */ jsxs(Combobox.Root, {
297
- items: itemsForView,
298
- value: selected,
299
- onValueChange: (item) => setLanguage(item?.value ?? ""),
300
- inputValue: query,
301
- onInputValueChange: setQuery,
302
- onOpenChange: (open) => {
303
- if (open) setComboboxOpen(true);
304
- else setQuery("");
305
- },
306
- onOpenChangeComplete: (open) => {
307
- if (!open) setComboboxOpen(false);
308
- },
309
- children: [/* @__PURE__ */ jsxs(Combobox.Trigger, {
310
- className: code_block_view_module_default.Trigger,
311
- "data-testid": "code-block-language",
312
- children: [/* @__PURE__ */ jsx(Combobox.Value, { placeholder: "Plain Text" }), /* @__PURE__ */ jsx(Combobox.Icon, {
313
- className: code_block_view_module_default.TriggerIcon,
314
- children: /* @__PURE__ */ jsx(ChevronsUpDownIcon, {})
315
- })]
316
- }), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
317
- className: code_block_view_module_default.Positioner,
318
- sideOffset: 4,
319
- children: /* @__PURE__ */ jsxs(Combobox.Popup, {
320
- className: code_block_view_module_default.Popup,
321
- children: [
322
- /* @__PURE__ */ jsx("div", {
323
- className: code_block_view_module_default.SearchRow,
324
- children: /* @__PURE__ */ jsx(Combobox.Input, {
325
- className: code_block_view_module_default.Search,
326
- placeholder: "Search or type a language",
327
- "data-testid": "code-block-language-search"
328
- })
329
- }),
330
- /* @__PURE__ */ jsx(Combobox.Empty, {
331
- className: code_block_view_module_default.Empty,
332
- children: "No languages found."
333
- }),
334
- /* @__PURE__ */ jsx(Combobox.List, {
335
- className: code_block_view_module_default.List,
336
- children: (item) => /* @__PURE__ */ jsxs(Combobox.Item, {
337
- value: item,
338
- className: code_block_view_module_default.Item,
339
- children: [/* @__PURE__ */ jsx(Combobox.ItemIndicator, {
340
- className: code_block_view_module_default.ItemIndicator,
341
- children: /* @__PURE__ */ jsx(CheckIcon, {})
342
- }), /* @__PURE__ */ jsx("span", {
343
- className: code_block_view_module_default.ItemText,
344
- children: item.label
345
- })]
346
- }, item.label)
324
+ return /* @__PURE__ */ jsxs(Combobox.Root, {
325
+ items: itemsForView,
326
+ value: selected,
327
+ onValueChange: (item) => setLanguage(item?.value ?? ""),
328
+ inputValue: query,
329
+ onInputValueChange: setQuery,
330
+ onOpenChange: (open) => {
331
+ if (open) onShownChange(true);
332
+ else setQuery("");
333
+ },
334
+ onOpenChangeComplete: (open) => {
335
+ if (!open) onShownChange(false);
336
+ },
337
+ children: [/* @__PURE__ */ jsxs(Combobox.Trigger, {
338
+ className: code_block_view_module_default.Trigger,
339
+ "data-testid": "code-block-language",
340
+ children: [/* @__PURE__ */ jsx(Combobox.Value, { placeholder: "Plain Text" }), /* @__PURE__ */ jsx(Combobox.Icon, {
341
+ className: code_block_view_module_default.TriggerIcon,
342
+ children: /* @__PURE__ */ jsx(ChevronsUpDownIcon, {})
343
+ })]
344
+ }), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
345
+ className: code_block_view_module_default.Positioner,
346
+ sideOffset: 4,
347
+ collisionBoundary,
348
+ children: /* @__PURE__ */ jsxs(Combobox.Popup, {
349
+ className: code_block_view_module_default.Popup,
350
+ children: [
351
+ /* @__PURE__ */ jsx("div", {
352
+ className: code_block_view_module_default.SearchRow,
353
+ children: /* @__PURE__ */ jsx(Combobox.Input, {
354
+ className: code_block_view_module_default.Search,
355
+ placeholder: "Search or type a language",
356
+ "data-testid": "code-block-language-search"
347
357
  })
348
- ]
349
- })
350
- }) })]
351
- }), /* @__PURE__ */ jsx(CopyButton, {
352
- getText: () => code,
353
- label: "Copy code",
354
- className: code_block_view_module_default.CopyButton,
355
- "data-testid": "code-block-copy"
356
- })]
358
+ }),
359
+ /* @__PURE__ */ jsx(Combobox.Empty, {
360
+ className: code_block_view_module_default.Empty,
361
+ children: "No languages found."
362
+ }),
363
+ /* @__PURE__ */ jsx(Combobox.List, {
364
+ className: code_block_view_module_default.List,
365
+ children: (item) => /* @__PURE__ */ jsxs(Combobox.Item, {
366
+ value: item,
367
+ className: code_block_view_module_default.Item,
368
+ children: [/* @__PURE__ */ jsx(Combobox.ItemIndicator, {
369
+ className: code_block_view_module_default.ItemIndicator,
370
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
371
+ }), /* @__PURE__ */ jsx("span", {
372
+ className: code_block_view_module_default.ItemText,
373
+ children: item.label
374
+ })]
375
+ }, item.label)
376
+ })
377
+ ]
378
+ })
379
+ }) })]
357
380
  });
358
381
  }
382
+ const CodeBlockLanguagePickerMemo = /* @__PURE__ */ memo(CodeBlockLanguagePicker);
359
383
 
360
384
  //#endregion
361
385
  //#region src/extensions/code-block-view.ts
@@ -534,6 +558,7 @@ function selectLinkUnit(editor, link) {
534
558
  editor.focus();
535
559
  }
536
560
  function LinkPopover({ anchor, open, onClose, onCloseComplete, onPopupHover, children }) {
561
+ const collisionBoundary = use(CollisionBoundaryContext);
537
562
  return /* @__PURE__ */ jsx(Popover.Root, {
538
563
  open,
539
564
  onOpenChange: (open) => {
@@ -546,6 +571,7 @@ function LinkPopover({ anchor, open, onClose, onCloseComplete, onPopupHover, chi
546
571
  anchor,
547
572
  side: "bottom",
548
573
  sideOffset: 8,
574
+ collisionBoundary,
549
575
  className: link_menu_module_default.Positioner,
550
576
  children: /* @__PURE__ */ jsx(Popover.Popup, {
551
577
  className: link_menu_module_default.Popup,
@@ -1099,17 +1125,18 @@ var selection_menu_module_default = {
1099
1125
  */
1100
1126
  function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, affordance = true }) {
1101
1127
  const editor = useEditor$1();
1128
+ const collisionBoundary = use(CollisionBoundaryContext);
1102
1129
  const [selection, setSelection] = useState();
1103
1130
  useExtension$1(useMemo(() => {
1104
1131
  return defineUpdateHandler((view) => {
1105
1132
  const { from, to, empty } = view.state.selection;
1106
1133
  const anchorable = !empty && isTextSelection(view.state.selection) && !getPendingReplacement(view.state) && getSearchStatus(view.state).active === 0;
1107
1134
  setSelection((previous) => {
1108
- if (previous?.from === from && previous?.to === to && previous?.anchorable === anchorable) return previous;
1135
+ if (!anchorable) return;
1136
+ if (previous?.from === from && previous?.to === to) return previous;
1109
1137
  return {
1110
1138
  from,
1111
- to,
1112
- anchorable
1139
+ to
1113
1140
  };
1114
1141
  });
1115
1142
  });
@@ -1120,12 +1147,9 @@ function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, afford
1120
1147
  }, [onClose, editor]);
1121
1148
  const menuAnchor = useMemo(() => {
1122
1149
  if (!context) return;
1123
- return getVirtualElementFromRange(editor.view, {
1124
- from: context.from,
1125
- to: context.to
1126
- });
1150
+ return getMenuAnchor(editor.view, context.from, context.to);
1127
1151
  }, [context, editor]);
1128
- const showAffordance = affordance && !!!context && !!selection?.anchorable;
1152
+ const showAffordance = affordance && !!!context && !!selection;
1129
1153
  const affordanceVisible = useDelayedFlag(showAffordance, 250, 0);
1130
1154
  const affordanceAnchor = useMemo(() => {
1131
1155
  if (!showAffordance || !selection) return;
@@ -1147,6 +1171,7 @@ function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, afford
1147
1171
  anchor: menuAnchor,
1148
1172
  side: "bottom",
1149
1173
  sideOffset: 8,
1174
+ collisionBoundary,
1150
1175
  className: selection_menu_module_default.Positioner,
1151
1176
  children: /* @__PURE__ */ jsx(Popover.Popup, {
1152
1177
  className: selection_menu_module_default.Popup,
@@ -1167,6 +1192,7 @@ function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, afford
1167
1192
  anchor: affordanceAnchor,
1168
1193
  side: "bottom",
1169
1194
  sideOffset: 4,
1195
+ collisionBoundary,
1170
1196
  className: selection_menu_module_default.AffordancePositioner,
1171
1197
  children: /* @__PURE__ */ jsx(Popover.Popup, {
1172
1198
  className: selection_menu_module_default.AffordancePopup,
@@ -1188,6 +1214,30 @@ function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, afford
1188
1214
  return null;
1189
1215
  }
1190
1216
  /**
1217
+ * Anchors the menu to the selected range, or to the range's end when the range
1218
+ * is taller than a third of the visible height: such a range leaves no room
1219
+ * for the menu above or below it.
1220
+ */
1221
+ function getMenuAnchor(view, from, to) {
1222
+ const range = getVirtualElementFromRange(view, {
1223
+ from,
1224
+ to
1225
+ });
1226
+ if (from === to) return range;
1227
+ const end = getVirtualElementFromRange(view, {
1228
+ from: to,
1229
+ to
1230
+ });
1231
+ const pickAnchor = () => {
1232
+ const visibleHeight = view.dom.ownerDocument.defaultView?.visualViewport?.height ?? Infinity;
1233
+ return range.getBoundingClientRect().height > visibleHeight / 3 ? end : range;
1234
+ };
1235
+ return {
1236
+ getBoundingClientRect: () => pickAnchor().getBoundingClientRect(),
1237
+ contextElement: view.dom
1238
+ };
1239
+ }
1240
+ /**
1191
1241
  * The menu content. Mounted only while the menu is open, so its filter state
1192
1242
  * resets naturally on close.
1193
1243
  */
@@ -1322,10 +1372,104 @@ function SlashMenuItem({ label, keywords, detail, kbd, onSelect }) {
1322
1372
  function selectionInTableCell(editor) {
1323
1373
  return isSelectionInTableCell(editor.state);
1324
1374
  }
1375
+ function BuiltinSlashMenuItems({ editor, timeFormat, onAttachFile }) {
1376
+ const inTableCell = useEditorDerivedValue(selectionInTableCell);
1377
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1378
+ !inTableCell && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1379
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1380
+ label: "Text",
1381
+ keywords: ["paragraph", "plain"],
1382
+ onSelect: () => editor.commands.turnIntoText()
1383
+ }),
1384
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1385
+ label: "Heading 1",
1386
+ keywords: ["h1"],
1387
+ kbd: "#",
1388
+ onSelect: () => editor.commands.setHeading({ level: 1 })
1389
+ }),
1390
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1391
+ label: "Heading 2",
1392
+ keywords: ["h2"],
1393
+ kbd: "##",
1394
+ onSelect: () => editor.commands.setHeading({ level: 2 })
1395
+ }),
1396
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1397
+ label: "Heading 3",
1398
+ keywords: ["h3"],
1399
+ kbd: "###",
1400
+ onSelect: () => editor.commands.setHeading({ level: 3 })
1401
+ }),
1402
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1403
+ label: "Heading 4",
1404
+ keywords: ["h4"],
1405
+ kbd: "####",
1406
+ onSelect: () => editor.commands.setHeading({ level: 4 })
1407
+ }),
1408
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1409
+ label: "Blockquote",
1410
+ kbd: ">",
1411
+ onSelect: () => editor.commands.setBlockquote()
1412
+ }),
1413
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1414
+ label: "Bullet list",
1415
+ kbd: "-",
1416
+ onSelect: () => editor.commands.wrapInList({ kind: "bullet" })
1417
+ }),
1418
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1419
+ label: "Ordered list",
1420
+ kbd: "1.",
1421
+ onSelect: () => editor.commands.wrapInList({ kind: "ordered" })
1422
+ }),
1423
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1424
+ label: "Task list",
1425
+ kbd: "+ [ ] ",
1426
+ onSelect: () => editor.commands.wrapInCircleTask()
1427
+ }),
1428
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1429
+ label: "Checkbox list",
1430
+ kbd: "- [ ] ",
1431
+ onSelect: () => editor.commands.wrapInSquareTask()
1432
+ }),
1433
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1434
+ label: "Code block",
1435
+ kbd: "```",
1436
+ onSelect: () => editor.commands.setCodeBlock()
1437
+ }),
1438
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1439
+ label: "Math",
1440
+ keywords: ["latex"],
1441
+ kbd: "```math",
1442
+ onSelect: () => editor.commands.insertMarkdown("```math\n```")
1443
+ }),
1444
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1445
+ label: "Table",
1446
+ onSelect: () => editor.commands.insertTable({
1447
+ row: 3,
1448
+ col: 3,
1449
+ header: true
1450
+ })
1451
+ })
1452
+ ] }),
1453
+ /* @__PURE__ */ jsx(SlashMenuItem, {
1454
+ label: "Now",
1455
+ onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
1456
+ }),
1457
+ onAttachFile ? /* @__PURE__ */ jsx(SlashMenuItem, {
1458
+ label: "Attach file",
1459
+ keywords: [
1460
+ "attachment",
1461
+ "file",
1462
+ "upload"
1463
+ ],
1464
+ onSelect: onAttachFile
1465
+ }) : null
1466
+ ] });
1467
+ }
1468
+ const BuiltinSlashMenuItemsMemo = /* @__PURE__ */ memo(BuiltinSlashMenuItems);
1325
1469
  function SlashMenu({ timeFormat = "12", onSlashMenuSearch, onFilePaste, onFileSaveError }) {
1326
1470
  const editor = useEditor$1();
1471
+ const collisionBoundary = use(CollisionBoundaryContext);
1327
1472
  const fileInputRef = useRef(null);
1328
- const inTableCell = useEditorDerivedValue(selectionInTableCell);
1329
1473
  const [open, setOpen] = useState(false);
1330
1474
  const [query, setQuery] = useState("");
1331
1475
  const [hostItems, setHostItems] = useState([]);
@@ -1386,98 +1530,16 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch, onFilePaste, onFileSa
1386
1530
  onChange: handleFileInputChange
1387
1531
  }) : null, /* @__PURE__ */ jsx(AutocompletePositioner, {
1388
1532
  className: autocomplete_menu_module_default.Positioner,
1533
+ boundary: toFloatingBoundary(collisionBoundary),
1389
1534
  children: /* @__PURE__ */ jsxs(AutocompletePopup, {
1390
1535
  className: autocomplete_menu_module_default.Popup,
1391
1536
  "data-testid": "slash-menu",
1392
1537
  children: [
1393
- !inTableCell && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1394
- /* @__PURE__ */ jsx(SlashMenuItem, {
1395
- label: "Text",
1396
- keywords: ["paragraph", "plain"],
1397
- onSelect: () => editor.commands.turnIntoText()
1398
- }),
1399
- /* @__PURE__ */ jsx(SlashMenuItem, {
1400
- label: "Heading 1",
1401
- keywords: ["h1"],
1402
- kbd: "#",
1403
- onSelect: () => editor.commands.setHeading({ level: 1 })
1404
- }),
1405
- /* @__PURE__ */ jsx(SlashMenuItem, {
1406
- label: "Heading 2",
1407
- keywords: ["h2"],
1408
- kbd: "##",
1409
- onSelect: () => editor.commands.setHeading({ level: 2 })
1410
- }),
1411
- /* @__PURE__ */ jsx(SlashMenuItem, {
1412
- label: "Heading 3",
1413
- keywords: ["h3"],
1414
- kbd: "###",
1415
- onSelect: () => editor.commands.setHeading({ level: 3 })
1416
- }),
1417
- /* @__PURE__ */ jsx(SlashMenuItem, {
1418
- label: "Heading 4",
1419
- keywords: ["h4"],
1420
- kbd: "####",
1421
- onSelect: () => editor.commands.setHeading({ level: 4 })
1422
- }),
1423
- /* @__PURE__ */ jsx(SlashMenuItem, {
1424
- label: "Blockquote",
1425
- kbd: ">",
1426
- onSelect: () => editor.commands.setBlockquote()
1427
- }),
1428
- /* @__PURE__ */ jsx(SlashMenuItem, {
1429
- label: "Bullet list",
1430
- kbd: "-",
1431
- onSelect: () => editor.commands.wrapInList({ kind: "bullet" })
1432
- }),
1433
- /* @__PURE__ */ jsx(SlashMenuItem, {
1434
- label: "Ordered list",
1435
- kbd: "1.",
1436
- onSelect: () => editor.commands.wrapInList({ kind: "ordered" })
1437
- }),
1438
- /* @__PURE__ */ jsx(SlashMenuItem, {
1439
- label: "Task list",
1440
- kbd: "+ [ ] ",
1441
- onSelect: () => editor.commands.wrapInCircleTask()
1442
- }),
1443
- /* @__PURE__ */ jsx(SlashMenuItem, {
1444
- label: "Checkbox list",
1445
- kbd: "- [ ] ",
1446
- onSelect: () => editor.commands.wrapInSquareTask()
1447
- }),
1448
- /* @__PURE__ */ jsx(SlashMenuItem, {
1449
- label: "Code block",
1450
- kbd: "```",
1451
- onSelect: () => editor.commands.setCodeBlock()
1452
- }),
1453
- /* @__PURE__ */ jsx(SlashMenuItem, {
1454
- label: "Math",
1455
- keywords: ["latex"],
1456
- kbd: "```math",
1457
- onSelect: () => editor.commands.insertMarkdown("```math\n```")
1458
- }),
1459
- /* @__PURE__ */ jsx(SlashMenuItem, {
1460
- label: "Table",
1461
- onSelect: () => editor.commands.insertTable({
1462
- row: 3,
1463
- col: 3,
1464
- header: true
1465
- })
1466
- })
1467
- ] }),
1468
- /* @__PURE__ */ jsx(SlashMenuItem, {
1469
- label: "Now",
1470
- onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
1538
+ /* @__PURE__ */ jsx(BuiltinSlashMenuItemsMemo, {
1539
+ editor,
1540
+ timeFormat,
1541
+ onAttachFile: onFilePaste ? openFilePicker : void 0
1471
1542
  }),
1472
- onFilePaste ? /* @__PURE__ */ jsx(SlashMenuItem, {
1473
- label: "Attach file",
1474
- keywords: [
1475
- "attachment",
1476
- "file",
1477
- "upload"
1478
- ],
1479
- onSelect: openFilePicker
1480
- }) : null,
1481
1543
  hostItems.map((item) => /* @__PURE__ */ jsx(SlashMenuItem, {
1482
1544
  label: item.label,
1483
1545
  keywords: item.keywords,
@@ -1508,68 +1570,188 @@ var table_handle_module_default = {
1508
1570
 
1509
1571
  //#endregion
1510
1572
  //#region src/components/table-handle.tsx
1511
- function getTableHandleState(editor) {
1512
- const commands = editor.commands;
1513
- const columnAlign = getTableColumnAlign(editor.state);
1514
- return {
1515
- columnAlign,
1516
- setTableColumnAlign: {
1517
- canExec: commands.setTableColumnAlign.canExec("left"),
1518
- command: (align) => {
1519
- return commands.setTableColumnAlign(columnAlign === align ? null : align);
1520
- }
1521
- },
1522
- addTableColumnBefore: {
1523
- canExec: commands.addTableColumnBefore.canExec(),
1524
- command: () => commands.addTableColumnBefore()
1525
- },
1526
- addTableColumnAfter: {
1527
- canExec: commands.addTableColumnAfter.canExec(),
1528
- command: () => commands.addTableColumnAfter()
1529
- },
1530
- addTableRowAbove: {
1531
- canExec: commands.addTableRowAbove.canExec(),
1532
- command: () => commands.addTableRowAbove()
1533
- },
1534
- addTableRowBelow: {
1535
- canExec: commands.addTableRowBelow.canExec(),
1536
- command: () => commands.addTableRowBelow()
1537
- },
1538
- deleteCellSelection: {
1539
- canExec: commands.deleteCellSelection.canExec(),
1540
- command: () => commands.deleteCellSelection()
1541
- },
1542
- deleteTableColumn: {
1543
- canExec: commands.deleteTableColumn.canExec(),
1544
- command: () => commands.deleteTableColumn()
1545
- },
1546
- deleteTableRow: {
1547
- canExec: commands.deleteTableRow.canExec(),
1548
- command: () => commands.deleteTableRow()
1549
- },
1550
- deleteTable: {
1551
- canExec: commands.deleteTable.canExec(),
1552
- command: () => commands.deleteTable()
1553
- }
1554
- };
1555
- }
1556
- const COLUMN_ALIGN_LABELS = {
1557
- left: "Align Left",
1558
- center: "Align Center",
1559
- right: "Align Right"
1560
- };
1561
- function ColumnAlignMenuItem({ align, columnAlign, onSelect }) {
1562
- const active = columnAlign === align;
1563
- return /* @__PURE__ */ jsxs(MenuItem, {
1573
+ function TableHandleItem({ children, onSelect, testId, active = false, danger = false }) {
1574
+ return /* @__PURE__ */ jsx(MenuItem, {
1564
1575
  className: table_handle_module_default.MenuItem,
1565
- "data-testid": `table-align-${align}`,
1566
- "data-active": active ? "" : void 0,
1567
1576
  onSelect,
1568
- children: [/* @__PURE__ */ jsx("span", { children: COLUMN_ALIGN_LABELS[align] }), active && /* @__PURE__ */ jsx(CheckIcon, {})]
1577
+ "data-testid": testId,
1578
+ "data-active": active ? "" : void 0,
1579
+ "data-danger": danger ? "" : void 0,
1580
+ children
1569
1581
  });
1570
1582
  }
1583
+ function canInsertLeft(editor) {
1584
+ return editor.commands.addTableColumnBefore.canExec();
1585
+ }
1586
+ function runInsertLeft(editor) {
1587
+ editor.commands.addTableColumnBefore();
1588
+ }
1589
+ function TableMenuInsertLeft({ editor }) {
1590
+ const canExec = useEditorDerivedValue(canInsertLeft);
1591
+ const handler = useCallback(() => runInsertLeft(editor), [editor]);
1592
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1593
+ testId: "table-insert-left",
1594
+ onSelect: handler,
1595
+ children: /* @__PURE__ */ jsx("span", { children: "Insert Left" })
1596
+ }) : null;
1597
+ }
1598
+ function canInsertRight(editor) {
1599
+ return editor.commands.addTableColumnAfter.canExec();
1600
+ }
1601
+ function runInsertRight(editor) {
1602
+ editor.commands.addTableColumnAfter();
1603
+ }
1604
+ function TableMenuInsertRight({ editor }) {
1605
+ const canExec = useEditorDerivedValue(canInsertRight);
1606
+ const handler = useCallback(() => runInsertRight(editor), [editor]);
1607
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1608
+ testId: "table-insert-right",
1609
+ onSelect: handler,
1610
+ children: /* @__PURE__ */ jsx("span", { children: "Insert Right" })
1611
+ }) : null;
1612
+ }
1613
+ function canAlign(editor) {
1614
+ return editor.commands.setTableColumnAlign.canExec("left");
1615
+ }
1616
+ function getActiveAlign(editor) {
1617
+ return getTableColumnAlign(editor.state);
1618
+ }
1619
+ function runAlign(editor, align, active) {
1620
+ editor.commands.setTableColumnAlign(active ? null : align);
1621
+ }
1622
+ function TableMenuAlignItem({ label, editor, align, activeAlign }) {
1623
+ const active = activeAlign === align;
1624
+ const handler = useCallback(() => runAlign(editor, align, active), [
1625
+ editor,
1626
+ align,
1627
+ active
1628
+ ]);
1629
+ return /* @__PURE__ */ jsxs(TableHandleItem, {
1630
+ testId: `table-align-${align}`,
1631
+ active,
1632
+ onSelect: handler,
1633
+ children: [/* @__PURE__ */ jsx("span", { children: label }), active && /* @__PURE__ */ jsx(CheckIcon, {})]
1634
+ });
1635
+ }
1636
+ function TableMenuAlignItems({ editor }) {
1637
+ const activeAlign = useEditorDerivedValue(getActiveAlign);
1638
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1639
+ /* @__PURE__ */ jsx(TableMenuAlignItem, {
1640
+ align: "left",
1641
+ label: "Align Left",
1642
+ editor,
1643
+ activeAlign
1644
+ }),
1645
+ /* @__PURE__ */ jsx(TableMenuAlignItem, {
1646
+ align: "center",
1647
+ label: "Align Center",
1648
+ editor,
1649
+ activeAlign
1650
+ }),
1651
+ /* @__PURE__ */ jsx(TableMenuAlignItem, {
1652
+ align: "right",
1653
+ label: "Align Right",
1654
+ editor,
1655
+ activeAlign
1656
+ })
1657
+ ] });
1658
+ }
1659
+ function TableMenuAlign({ editor }) {
1660
+ return useEditorDerivedValue(canAlign) ? /* @__PURE__ */ jsx(TableMenuAlignItems, { editor }) : null;
1661
+ }
1662
+ function canClearContents(editor) {
1663
+ return editor.commands.deleteCellSelection.canExec();
1664
+ }
1665
+ function runClearContents(editor) {
1666
+ editor.commands.deleteCellSelection();
1667
+ }
1668
+ function TableMenuClearContents({ editor, testId }) {
1669
+ const canExec = useEditorDerivedValue(canClearContents);
1670
+ const handler = useCallback(() => runClearContents(editor), [editor]);
1671
+ return canExec ? /* @__PURE__ */ jsxs(TableHandleItem, {
1672
+ testId,
1673
+ onSelect: handler,
1674
+ children: [/* @__PURE__ */ jsx("span", { children: "Clear Contents" }), /* @__PURE__ */ jsx("kbd", { children: "Del" })]
1675
+ }) : null;
1676
+ }
1677
+ function canDeleteColumn(editor) {
1678
+ return editor.commands.deleteTableColumn.canExec();
1679
+ }
1680
+ function runDeleteColumn(editor) {
1681
+ editor.commands.deleteTableColumn();
1682
+ }
1683
+ function TableMenuDeleteColumn({ editor }) {
1684
+ const canExec = useEditorDerivedValue(canDeleteColumn);
1685
+ const handler = useCallback(() => runDeleteColumn(editor), [editor]);
1686
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1687
+ testId: "table-delete-column",
1688
+ onSelect: handler,
1689
+ children: /* @__PURE__ */ jsx("span", { children: "Delete Column" })
1690
+ }) : null;
1691
+ }
1692
+ function canInsertAbove(editor) {
1693
+ return editor.commands.addTableRowAbove.canExec();
1694
+ }
1695
+ function runInsertAbove(editor) {
1696
+ editor.commands.addTableRowAbove();
1697
+ }
1698
+ function TableMenuInsertAbove({ editor }) {
1699
+ const canExec = useEditorDerivedValue(canInsertAbove);
1700
+ const handler = useCallback(() => runInsertAbove(editor), [editor]);
1701
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1702
+ testId: "table-insert-above",
1703
+ onSelect: handler,
1704
+ children: /* @__PURE__ */ jsx("span", { children: "Insert Above" })
1705
+ }) : null;
1706
+ }
1707
+ function canInsertBelow(editor) {
1708
+ return editor.commands.addTableRowBelow.canExec();
1709
+ }
1710
+ function runInsertBelow(editor) {
1711
+ editor.commands.addTableRowBelow();
1712
+ }
1713
+ function TableMenuInsertBelow({ editor }) {
1714
+ const canExec = useEditorDerivedValue(canInsertBelow);
1715
+ const handler = useCallback(() => runInsertBelow(editor), [editor]);
1716
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1717
+ testId: "table-insert-below",
1718
+ onSelect: handler,
1719
+ children: /* @__PURE__ */ jsx("span", { children: "Insert Below" })
1720
+ }) : null;
1721
+ }
1722
+ function canDeleteRow(editor) {
1723
+ return editor.commands.deleteTableRow.canExec();
1724
+ }
1725
+ function runDeleteRow(editor) {
1726
+ editor.commands.deleteTableRow();
1727
+ }
1728
+ function TableMenuDeleteRow({ editor }) {
1729
+ const canExec = useEditorDerivedValue(canDeleteRow);
1730
+ const handler = useCallback(() => runDeleteRow(editor), [editor]);
1731
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1732
+ testId: "table-delete-row",
1733
+ onSelect: handler,
1734
+ children: /* @__PURE__ */ jsx("span", { children: "Delete Row" })
1735
+ }) : null;
1736
+ }
1737
+ function canDeleteTable(editor) {
1738
+ return editor.commands.deleteTable.canExec();
1739
+ }
1740
+ function runDeleteTable(editor) {
1741
+ editor.commands.deleteTable();
1742
+ }
1743
+ function TableMenuDeleteTable({ editor, testId }) {
1744
+ const canExec = useEditorDerivedValue(canDeleteTable);
1745
+ const handler = useCallback(() => runDeleteTable(editor), [editor]);
1746
+ return canExec ? /* @__PURE__ */ jsx(TableHandleItem, {
1747
+ testId,
1748
+ danger: true,
1749
+ onSelect: handler,
1750
+ children: /* @__PURE__ */ jsx("span", { children: "Delete Table" })
1751
+ }) : null;
1752
+ }
1571
1753
  function TableHandle() {
1572
- const state = useEditorDerivedValue(getTableHandleState);
1754
+ const editor = useEditor$1();
1573
1755
  return /* @__PURE__ */ jsxs(TableHandleRoot, { children: [
1574
1756
  /* @__PURE__ */ jsx(TableHandleDragPreview, {}),
1575
1757
  /* @__PURE__ */ jsx(TableHandleDropIndicator, {}),
@@ -1587,53 +1769,17 @@ function TableHandle() {
1587
1769
  className: table_handle_module_default.MenuPopup,
1588
1770
  "data-testid": "table-handle-column-menu",
1589
1771
  children: [
1590
- state.addTableColumnBefore.canExec && /* @__PURE__ */ jsx(MenuItem, {
1591
- className: table_handle_module_default.MenuItem,
1592
- "data-testid": "table-insert-left",
1593
- onSelect: state.addTableColumnBefore.command,
1594
- children: /* @__PURE__ */ jsx("span", { children: "Insert Left" })
1595
- }),
1596
- state.addTableColumnAfter.canExec && /* @__PURE__ */ jsx(MenuItem, {
1597
- className: table_handle_module_default.MenuItem,
1598
- "data-testid": "table-insert-right",
1599
- onSelect: state.addTableColumnAfter.command,
1600
- children: /* @__PURE__ */ jsx("span", { children: "Insert Right" })
1772
+ /* @__PURE__ */ jsx(TableMenuInsertLeft, { editor }),
1773
+ /* @__PURE__ */ jsx(TableMenuInsertRight, { editor }),
1774
+ /* @__PURE__ */ jsx(TableMenuAlign, { editor }),
1775
+ /* @__PURE__ */ jsx(TableMenuClearContents, {
1776
+ editor,
1777
+ testId: "table-clear-column"
1601
1778
  }),
1602
- state.setTableColumnAlign.canExec && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1603
- /* @__PURE__ */ jsx(ColumnAlignMenuItem, {
1604
- align: "left",
1605
- columnAlign: state.columnAlign,
1606
- onSelect: () => state.setTableColumnAlign.command("left")
1607
- }),
1608
- /* @__PURE__ */ jsx(ColumnAlignMenuItem, {
1609
- align: "center",
1610
- columnAlign: state.columnAlign,
1611
- onSelect: () => state.setTableColumnAlign.command("center")
1612
- }),
1613
- /* @__PURE__ */ jsx(ColumnAlignMenuItem, {
1614
- align: "right",
1615
- columnAlign: state.columnAlign,
1616
- onSelect: () => state.setTableColumnAlign.command("right")
1617
- })
1618
- ] }),
1619
- state.deleteCellSelection.canExec && /* @__PURE__ */ jsxs(MenuItem, {
1620
- className: table_handle_module_default.MenuItem,
1621
- "data-testid": "table-clear-column",
1622
- onSelect: state.deleteCellSelection.command,
1623
- children: [/* @__PURE__ */ jsx("span", { children: "Clear Contents" }), /* @__PURE__ */ jsx("kbd", { children: "Del" })]
1624
- }),
1625
- state.deleteTableColumn.canExec && /* @__PURE__ */ jsx(MenuItem, {
1626
- className: table_handle_module_default.MenuItem,
1627
- "data-testid": "table-delete-column",
1628
- onSelect: state.deleteTableColumn.command,
1629
- children: /* @__PURE__ */ jsx("span", { children: "Delete Column" })
1630
- }),
1631
- state.deleteTable.canExec && /* @__PURE__ */ jsx(MenuItem, {
1632
- className: table_handle_module_default.MenuItem,
1633
- "data-danger": "",
1634
- "data-testid": "table-delete-table-column",
1635
- onSelect: state.deleteTable.command,
1636
- children: /* @__PURE__ */ jsx("span", { children: "Delete Table" })
1779
+ /* @__PURE__ */ jsx(TableMenuDeleteColumn, { editor }),
1780
+ /* @__PURE__ */ jsx(TableMenuDeleteTable, {
1781
+ editor,
1782
+ testId: "table-delete-table-column"
1637
1783
  })
1638
1784
  ]
1639
1785
  })
@@ -1641,7 +1787,6 @@ function TableHandle() {
1641
1787
  })
1642
1788
  }),
1643
1789
  /* @__PURE__ */ jsx(TableHandleRowPositioner, {
1644
- placement: "left",
1645
1790
  className: table_handle_module_default.Positioner,
1646
1791
  children: /* @__PURE__ */ jsx(TableHandleRowPopup, {
1647
1792
  className: table_handle_module_default.RowPopup,
@@ -1655,36 +1800,16 @@ function TableHandle() {
1655
1800
  className: table_handle_module_default.MenuPopup,
1656
1801
  "data-testid": "table-handle-row-menu",
1657
1802
  children: [
1658
- state.addTableRowAbove.canExec && /* @__PURE__ */ jsx(MenuItem, {
1659
- className: table_handle_module_default.MenuItem,
1660
- "data-testid": "table-insert-above",
1661
- onSelect: state.addTableRowAbove.command,
1662
- children: /* @__PURE__ */ jsx("span", { children: "Insert Above" })
1663
- }),
1664
- state.addTableRowBelow.canExec && /* @__PURE__ */ jsx(MenuItem, {
1665
- className: table_handle_module_default.MenuItem,
1666
- "data-testid": "table-insert-below",
1667
- onSelect: state.addTableRowBelow.command,
1668
- children: /* @__PURE__ */ jsx("span", { children: "Insert Below" })
1669
- }),
1670
- state.deleteCellSelection.canExec && /* @__PURE__ */ jsxs(MenuItem, {
1671
- className: table_handle_module_default.MenuItem,
1672
- "data-testid": "table-clear-row",
1673
- onSelect: state.deleteCellSelection.command,
1674
- children: [/* @__PURE__ */ jsx("span", { children: "Clear Contents" }), /* @__PURE__ */ jsx("kbd", { children: "Del" })]
1675
- }),
1676
- state.deleteTableRow.canExec && /* @__PURE__ */ jsx(MenuItem, {
1677
- className: table_handle_module_default.MenuItem,
1678
- "data-testid": "table-delete-row",
1679
- onSelect: state.deleteTableRow.command,
1680
- children: /* @__PURE__ */ jsx("span", { children: "Delete Row" })
1803
+ /* @__PURE__ */ jsx(TableMenuInsertAbove, { editor }),
1804
+ /* @__PURE__ */ jsx(TableMenuInsertBelow, { editor }),
1805
+ /* @__PURE__ */ jsx(TableMenuClearContents, {
1806
+ editor,
1807
+ testId: "table-clear-row"
1681
1808
  }),
1682
- state.deleteTable.canExec && /* @__PURE__ */ jsx(MenuItem, {
1683
- className: table_handle_module_default.MenuItem,
1684
- "data-danger": "",
1685
- "data-testid": "table-delete-table-row",
1686
- onSelect: state.deleteTable.command,
1687
- children: /* @__PURE__ */ jsx("span", { children: "Delete Table" })
1809
+ /* @__PURE__ */ jsx(TableMenuDeleteRow, { editor }),
1810
+ /* @__PURE__ */ jsx(TableMenuDeleteTable, {
1811
+ editor,
1812
+ testId: "table-delete-table-row"
1688
1813
  })
1689
1814
  ]
1690
1815
  })
@@ -1705,6 +1830,7 @@ function returnsTrue() {
1705
1830
  const regex$1 = new RegExp((canUseRegexLookbehind() ? String.raw`(?<!\S)` : "") + String.raw`#[\da-z]+$`, "iu");
1706
1831
  function TagMenu({ onTagSearch }) {
1707
1832
  const editor = useEditor$1();
1833
+ const collisionBoundary = use(CollisionBoundaryContext);
1708
1834
  const [open, setOpen] = useState(false);
1709
1835
  const [query, setQuery] = useState("");
1710
1836
  const [items, setItems] = useState([]);
@@ -1738,6 +1864,7 @@ function TagMenu({ onTagSearch }) {
1738
1864
  onQueryChange: (event) => setQuery(event.detail),
1739
1865
  children: /* @__PURE__ */ jsx(AutocompletePositioner, {
1740
1866
  className: autocomplete_menu_module_default.Positioner,
1867
+ boundary: toFloatingBoundary(collisionBoundary),
1741
1868
  children: /* @__PURE__ */ jsxs(AutocompletePopup, {
1742
1869
  className: autocomplete_menu_module_default.Popup,
1743
1870
  "data-testid": "tag-menu",
@@ -1780,6 +1907,7 @@ function queryFromRegexMatch(match) {
1780
1907
  }
1781
1908
  function WikilinkMenu({ onWikilinkSearch }) {
1782
1909
  const editor = useEditor$1();
1910
+ const collisionBoundary = use(CollisionBoundaryContext);
1783
1911
  const [open, setOpen] = useState(false);
1784
1912
  const [query, setQuery] = useState("");
1785
1913
  const [items, setItems] = useState([]);
@@ -1815,6 +1943,7 @@ function WikilinkMenu({ onWikilinkSearch }) {
1815
1943
  onQueryChange: (event) => setQuery(event.detail),
1816
1944
  children: /* @__PURE__ */ jsx(AutocompletePositioner, {
1817
1945
  className: autocomplete_menu_module_default.Positioner,
1946
+ boundary: toFloatingBoundary(collisionBoundary),
1818
1947
  children: /* @__PURE__ */ jsxs(AutocompletePopup, {
1819
1948
  className: autocomplete_menu_module_default.Popup,
1820
1949
  "data-testid": "wikilink-menu",
@@ -1887,7 +2016,7 @@ function findHeadingPosition(doc, fragment) {
1887
2016
  });
1888
2017
  return match;
1889
2018
  }
1890
- function ProseKitEditor({ markMode = "focus", initialMarkdown, singleParagraph = false, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste, linkPaste, bulletAfterHeading, backspaceDeletesEmptyFirstBlock, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, CodeBlockView, ref, children }) {
2019
+ function ProseKitEditor({ markMode = "focus", initialMarkdown, singleParagraph = false, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, collisionBoundary, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste, linkPaste, bulletAfterHeading, backspaceDeletesEmptyFirstBlock, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, CodeBlockView, ref, children }) {
1891
2020
  const suppressDocChangeRef = useRef(false);
1892
2021
  const handleDocChange = useMemo(() => {
1893
2022
  if (!onDocChange) return;
@@ -2111,47 +2240,50 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, singleParagraph =
2111
2240
  const mount = useCallback((element) => {
2112
2241
  return editor.mount(element);
2113
2242
  }, [editor]);
2114
- return /* @__PURE__ */ jsxs(ProseKit, {
2115
- editor,
2116
- children: [
2117
- /* @__PURE__ */ jsx(VirtualCaret, {}),
2118
- /* @__PURE__ */ jsx("div", { ref: mount }),
2119
- /* @__PURE__ */ jsx(EditorExtensions, {
2120
- config,
2121
- searchQuery,
2122
- onDocChange: handleDocChange,
2123
- onSearchChange
2124
- }),
2125
- blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
2126
- !readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
2127
- blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
2128
- /* @__PURE__ */ jsx(SlashMenu, {
2129
- timeFormat,
2130
- onSlashMenuSearch,
2131
- onFilePaste,
2132
- onFileSaveError
2133
- }),
2134
- /* @__PURE__ */ jsx(LinkMenu, {
2135
- onLinkClick,
2136
- onLinkCopy,
2137
- resolveLinkPreview,
2138
- readOnly
2139
- }, String(readOnly)),
2140
- onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
2141
- onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
2142
- onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
2143
- onSelectionMenuSearch,
2144
- context: selectionMenuContext,
2145
- onOpen: openSelectionMenu,
2146
- onClose: closeSelectionMenu,
2147
- affordance: selectionMenuAffordance
2148
- }),
2149
- !readOnly && /* @__PURE__ */ jsx(PendingReplacementPreview, {
2150
- actions: pendingReplacementActions,
2151
- onResolve: onPendingReplacementResolve
2152
- }),
2153
- children
2154
- ]
2243
+ return /* @__PURE__ */ jsx(CollisionBoundaryContext, {
2244
+ value: collisionBoundary,
2245
+ children: /* @__PURE__ */ jsxs(ProseKit, {
2246
+ editor,
2247
+ children: [
2248
+ /* @__PURE__ */ jsx(VirtualCaret, {}),
2249
+ /* @__PURE__ */ jsx("div", { ref: mount }),
2250
+ /* @__PURE__ */ jsx(EditorExtensions, {
2251
+ config,
2252
+ searchQuery,
2253
+ onDocChange: handleDocChange,
2254
+ onSearchChange
2255
+ }),
2256
+ blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
2257
+ !readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
2258
+ blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
2259
+ /* @__PURE__ */ jsx(SlashMenu, {
2260
+ timeFormat,
2261
+ onSlashMenuSearch,
2262
+ onFilePaste,
2263
+ onFileSaveError
2264
+ }),
2265
+ /* @__PURE__ */ jsx(LinkMenu, {
2266
+ onLinkClick,
2267
+ onLinkCopy,
2268
+ resolveLinkPreview,
2269
+ readOnly
2270
+ }, String(readOnly)),
2271
+ onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
2272
+ onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
2273
+ onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
2274
+ onSelectionMenuSearch,
2275
+ context: selectionMenuContext,
2276
+ onOpen: openSelectionMenu,
2277
+ onClose: closeSelectionMenu,
2278
+ affordance: selectionMenuAffordance
2279
+ }),
2280
+ !readOnly && /* @__PURE__ */ jsx(PendingReplacementPreview, {
2281
+ actions: pendingReplacementActions,
2282
+ onResolve: onPendingReplacementResolve
2283
+ }),
2284
+ children
2285
+ ]
2286
+ })
2155
2287
  });
2156
2288
  }
2157
2289
 
@@ -2164,7 +2296,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
2164
2296
  *
2165
2297
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
2166
2298
  */
2167
- function MarkdownEditor({ mode = "focus", initialMarkdown, singleParagraph, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, backspaceDeletesEmptyFirstBlock = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }) {
2299
+ function MarkdownEditor({ mode = "focus", initialMarkdown, singleParagraph, referenceDefinitions, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, collisionBoundary, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, onXPostMediaClick, onYouTubeVideoClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, backspaceDeletesEmptyFirstBlock = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }) {
2168
2300
  const childRef = useRef(null);
2169
2301
  useImperativeHandle(handleRef, () => {
2170
2302
  function getMarkdown() {
@@ -2277,6 +2409,7 @@ function MarkdownEditor({ mode = "focus", initialMarkdown, singleParagraph, refe
2277
2409
  onWikilinkSearch,
2278
2410
  onSelectionMenuSearch,
2279
2411
  selectionMenuAffordance,
2412
+ collisionBoundary,
2280
2413
  pendingReplacementActions,
2281
2414
  onPendingReplacementResolve,
2282
2415
  onWikilinkClick,
package/dist/style.css CHANGED
@@ -746,11 +746,12 @@
746
746
 
747
747
  .meow_Popup_2_4Zjq {
748
748
  box-sizing: border-box;
749
+ width: 100%;
750
+ max-height: var(--available-height);
749
751
  border: 1px solid var(--meowdown-border);
750
752
  background: var(--meowdown-popover-bg);
751
753
  border-radius: .75rem;
752
754
  flex-direction: column;
753
- width: 100%;
754
755
  padding: .25rem;
755
756
  font-size: .875rem;
756
757
  display: flex;
@@ -765,6 +766,7 @@
765
766
  border: 1px solid var(--meowdown-border);
766
767
  background: none;
767
768
  border-radius: .5rem;
769
+ flex: none;
768
770
  margin-bottom: .25rem;
769
771
  padding: .375rem .5rem;
770
772
 
@@ -776,6 +778,8 @@
776
778
 
777
779
  .meow_List_2_4Zjq {
778
780
  flex-direction: column;
781
+ flex: auto;
782
+ min-height: 6rem;
779
783
  max-height: 16rem;
780
784
  display: flex;
781
785
  overflow-y: auto;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.80.3",
4
+ "version": "0.81.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@base-ui/react": "^1.9.0",
25
- "@meowdown/core": "0.81.1",
25
+ "@meowdown/core": "0.81.2",
26
26
  "@meowdown/embed": "0.3.1",
27
27
  "@meowdown/markdown": "0.77.0",
28
28
  "@ocavue/utils": "^1.9.0",
@@ -56,12 +56,12 @@
56
56
  "@tsdown/css": "^0.23.1",
57
57
  "@types/react": "^19.3.0",
58
58
  "@types/react-dom": "^19.3.0",
59
- "@vitest/browser-playwright": "^5.0.3",
59
+ "@vitest/browser-playwright": "^5.0.5",
60
60
  "dedent": "^1.7.2",
61
61
  "react": "^19.3.0",
62
62
  "react-dom": "^19.3.0",
63
63
  "tsdown": "^0.23.1",
64
- "vitest": "^5.0.3",
64
+ "vitest": "^5.0.5",
65
65
  "vitest-browser-commands": "^0.5.0",
66
66
  "vitest-browser-react": "^2.3.0"
67
67
  },