@meowdown/react 0.66.0 → 0.67.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
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode, Ref } from "react";
2
- import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit } from "@meowdown/core";
2
+ import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
4
  import { useEditor, useExtension, useKeymap } from "@prosekit/react";
5
5
  //#region src/utils/date-format.d.ts
@@ -347,6 +347,13 @@ interface EditorProps {
347
347
  * for a toast. Pass a stable function (e.g. from `useCallback`).
348
348
  */
349
349
  onLinkCopy?: LinkCopyHandler;
350
+ /**
351
+ * Resolves optional display metadata for the link popup. Failures should be
352
+ * represented by `undefined`; the popup keeps its URL and actions available.
353
+ * May be called repeatedly for the same `href`, so cache expensive work in
354
+ * the host. Pass a stable function (e.g. from `useCallback`).
355
+ */
356
+ resolveLinkPreview?: LinkPreviewResolver$1;
350
357
  /**
351
358
  * Called with the tag name (without the leading `#`) on click of a rendered
352
359
  * `#tag`, or on `Mod-Enter` with the caret on one. Pass a stable function
@@ -508,7 +515,7 @@ interface EditorProps {
508
515
  *
509
516
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
510
517
  */
511
- export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): ReactElement;
518
+ export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): ReactElement;
512
519
  //#endregion
513
520
  //#region src/components/markdown-view.d.ts
514
521
  /**
@@ -654,4 +661,4 @@ interface WikilinkHoverCardProps {
654
661
  */
655
662
  export declare function WikilinkHoverCard({ children, className }: WikilinkHoverCardProps): ReactNode;
656
663
  //#endregion
657
- export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type MarkdownViewProps, type PendingReplacementResolveHandler, 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, useEditor, useExtension, useKeymap };
664
+ export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type LinkPreview, type LinkPreviewResolver, type MarkdownViewProps, type PendingReplacementResolveHandler, 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, useEditor, useExtension, useKeymap };
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { clsx } from "clsx/lite";
2
2
  import { Fragment, cloneElement, createElement, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
- import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, renderMathInto } from "@meowdown/core";
3
+ import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, renderMathInto } from "@meowdown/core";
4
4
  import { clamp } from "@ocavue/utils";
5
5
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
6
6
  import { Selection, TextSelection } from "@prosekit/pm/state";
7
7
  import { ProseKit, defineReactNodeView, useEditor, useEditor as useEditor$1, useEditorDerivedValue, useExtension, useExtension as useExtension$1, useKeymap } from "@prosekit/react";
8
8
  import GithubSlugger from "github-slugger";
9
9
  import { Combobox } from "@base-ui/react/combobox";
10
- import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, SparklesIcon, UnlinkIcon } from "lucide-react";
10
+ import { CheckIcon, ChevronsUpDownIcon, CopyIcon, Globe2Icon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, SparklesIcon, UnlinkIcon } from "lucide-react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from "@prosekit/react/block-handle";
13
13
  import { DropIndicator } from "@prosekit/react/drop-indicator";
@@ -499,70 +499,85 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
499
499
  return null;
500
500
  }
501
501
 
502
- //#endregion
503
- //#region src/hooks/use-delayed-flag.ts
504
- /**
505
- * Delay before the flag opens, in ms.
506
- */
507
- const OPEN_DELAY$1 = 400;
508
- /**
509
- * Grace before the flag closes, in ms. The window lets a pointer travel from
510
- * the hovered link onto the popover it anchors.
511
- */
512
- const CLOSE_DELAY$1 = 300;
513
- /**
514
- * Mirrors `value` into a boolean that flips true `openDelay`ms after `value`
515
- * becomes true and false `closeDelay`ms after it becomes false, cancelling any
516
- * pending flip on each change.
517
- */
518
- function useDelayedFlag(value, openDelay = OPEN_DELAY$1, closeDelay = CLOSE_DELAY$1) {
519
- const [flag, setFlag] = useState(false);
520
- const timerRef = useRef(void 0);
521
- useEffect(() => {
522
- clearTimeout(timerRef.current);
523
- timerRef.current = setTimeout(() => setFlag(value), value ? openDelay : closeDelay);
524
- return () => clearTimeout(timerRef.current);
525
- }, [
526
- value,
527
- openDelay,
528
- closeDelay
529
- ]);
530
- return flag;
531
- }
532
-
533
502
  //#endregion
534
503
  //#region src/components/link-menu.module.css
535
504
  var link_menu_module_default = {
505
+ "Actions": "meow_Actions_IjcqKG",
536
506
  "Button": "meow_Button_IjcqKG",
507
+ "Description": "meow_Description_IjcqKG",
508
+ "Field": "meow_Field_IjcqKG",
537
509
  "Form": "meow_Form_IjcqKG",
510
+ "FormActions": "meow_FormActions_IjcqKG",
511
+ "Host": "meow_Host_IjcqKG",
512
+ "Icon": "meow_Icon_IjcqKG",
538
513
  "Input": "meow_Input_IjcqKG",
539
514
  "Popup": "meow_Popup_IjcqKG",
540
515
  "Positioner": "meow_Positioner_IjcqKG",
516
+ "Preview": "meow_Preview_IjcqKG",
517
+ "PreviewBody": "meow_PreviewBody_IjcqKG",
518
+ "pulse": "meow_pulse_IjcqKG",
519
+ "RemoveButton": "meow_RemoveButton_IjcqKG",
520
+ "ReplaceButton": "meow_ReplaceButton_IjcqKG",
521
+ "ReplaceRow": "meow_ReplaceRow_IjcqKG",
541
522
  "Row": "meow_Row_IjcqKG",
542
- "SrOnly": "meow_SrOnly_IjcqKG",
543
- "Url": "meow_Url_IjcqKG"
523
+ "SaveButton": "meow_SaveButton_IjcqKG",
524
+ "Skeleton": "meow_Skeleton_IjcqKG",
525
+ "SkeletonLine": "meow_SkeletonLine_IjcqKG",
526
+ "SkeletonLineShort": "meow_SkeletonLineShort_IjcqKG",
527
+ "Title": "meow_Title_IjcqKG",
528
+ "Url": "meow_Url_IjcqKG",
529
+ "UseTitleButton": "meow_UseTitleButton_IjcqKG"
544
530
  };
545
531
 
546
532
  //#endregion
547
533
  //#region src/components/link-menu.tsx
548
- /**
549
- * Select the link unit so the text-backed commands target it, and keep the
550
- * editor focused so its virtual selection stays visible behind the popover.
551
- */
534
+ function useLinkPreview(href, resolveLinkPreview) {
535
+ const [settled, setSettled] = useState();
536
+ useEffect(() => {
537
+ if (!href || !resolveLinkPreview || !isWebHref(href)) return;
538
+ let stale = false;
539
+ Promise.resolve().then(() => resolveLinkPreview(href)).then((preview) => {
540
+ if (!stale) setSettled({
541
+ href,
542
+ result: preview ? {
543
+ status: "resolved",
544
+ preview
545
+ } : { status: "failed" }
546
+ });
547
+ }).catch(() => {
548
+ if (!stale) setSettled({
549
+ href,
550
+ result: { status: "failed" }
551
+ });
552
+ });
553
+ return () => {
554
+ stale = true;
555
+ };
556
+ }, [href, resolveLinkPreview]);
557
+ if (!href || !resolveLinkPreview || !isWebHref(href)) return { status: "idle" };
558
+ return settled?.href === href ? settled.result : { status: "loading" };
559
+ }
560
+ function isWebHref(href) {
561
+ try {
562
+ const protocol = new URL(href).protocol;
563
+ return protocol === "https:" || protocol === "http:";
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
552
568
  function selectLinkUnit(editor, link) {
553
569
  editor.commands.selectText(link.unit.from, link.unit.to);
554
570
  editor.focus();
555
571
  }
556
- /**
557
- * A Base UI popover anchored at `anchor`. Base UI dismisses it on an outside
558
- * press or Escape, both routed through `onClose`.
559
- */
560
- function LinkPopover({ anchor, onClose, onPopupHover, children }) {
572
+ function LinkPopover({ anchor, open, onClose, onCloseComplete, onPopupHover, children }) {
561
573
  return /* @__PURE__ */ jsx(Popover.Root, {
562
- open: true,
574
+ open,
563
575
  onOpenChange: (open) => {
564
576
  if (!open) onClose();
565
577
  },
578
+ onOpenChangeComplete: (open) => {
579
+ if (!open) onCloseComplete?.();
580
+ },
566
581
  children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
567
582
  anchor,
568
583
  side: "bottom",
@@ -580,31 +595,29 @@ function LinkPopover({ anchor, onClose, onPopupHover, children }) {
580
595
  }) })
581
596
  });
582
597
  }
583
- /**
584
- * The hover preview: the url plus copy, edit, and remove actions.
585
- */
586
- function LinkInfoContent({ href, onLinkClick, onLinkCopy, onEdit, onRemove }) {
598
+ function LinkAnchor({ href, className, onLinkClick, children }) {
599
+ return /* @__PURE__ */ jsx("a", {
600
+ className,
601
+ href,
602
+ title: href,
603
+ target: "_blank",
604
+ rel: "noopener noreferrer",
605
+ onClick: (event) => {
606
+ if (!onLinkClick) return;
607
+ event.preventDefault();
608
+ onLinkClick({
609
+ href,
610
+ event: event.nativeEvent,
611
+ mod: isModEvent(event)
612
+ });
613
+ },
614
+ children
615
+ });
616
+ }
617
+ function LinkActions({ href, onLinkCopy, onEdit, onRemove }) {
587
618
  return /* @__PURE__ */ jsxs("div", {
588
- className: link_menu_module_default.Row,
589
- "data-testid": "link-popover-read",
619
+ className: link_menu_module_default.Actions,
590
620
  children: [
591
- /* @__PURE__ */ jsx("a", {
592
- className: link_menu_module_default.Url,
593
- href,
594
- title: href,
595
- target: "_blank",
596
- rel: "noopener noreferrer",
597
- onClick: (event) => {
598
- if (!onLinkClick) return;
599
- event.preventDefault();
600
- onLinkClick({
601
- href,
602
- event: event.nativeEvent,
603
- mod: isModEvent(event)
604
- });
605
- },
606
- children: href
607
- }),
608
621
  /* @__PURE__ */ jsx(CopyButton, {
609
622
  getText: () => href,
610
623
  label: "Copy link",
@@ -630,155 +643,297 @@ function LinkInfoContent({ href, onLinkClick, onLinkCopy, onEdit, onRemove }) {
630
643
  ]
631
644
  });
632
645
  }
633
- /**
634
- * The url and title form, opened by `Mod-k` or the preview's edit button.
635
- */
636
- function LinkEditContent({ link, onSubmit }) {
637
- const hrefInputRef = useRef(null);
638
- const titleInputRef = useRef(null);
639
- const href = link ? link.href : "";
640
- const title = link ? link.title : "";
646
+ function PreviewIcon({ preview }) {
647
+ const [failed, setFailed] = useState(false);
648
+ if (!preview.iconSrc || failed) return /* @__PURE__ */ jsx(Globe2Icon, { "aria-hidden": "true" });
649
+ return /* @__PURE__ */ jsx("img", {
650
+ src: preview.iconSrc,
651
+ alt: "",
652
+ onError: () => setFailed(true)
653
+ });
654
+ }
655
+ function getHostname(href) {
656
+ try {
657
+ return new URL(href).hostname;
658
+ } catch {
659
+ return href;
660
+ }
661
+ }
662
+ function LinkInfoContent({ href, previewState, onLinkClick, onLinkCopy, onEdit, onRemove, onUseTitle }) {
663
+ if (previewState.status === "resolved") {
664
+ const { preview } = previewState;
665
+ return /* @__PURE__ */ jsxs("div", {
666
+ "data-testid": "link-popover-info",
667
+ children: [
668
+ /* @__PURE__ */ jsxs("div", {
669
+ className: link_menu_module_default.Preview,
670
+ children: [
671
+ /* @__PURE__ */ jsx("div", {
672
+ className: link_menu_module_default.Icon,
673
+ children: /* @__PURE__ */ jsx(PreviewIcon, { preview }, preview.iconSrc)
674
+ }),
675
+ /* @__PURE__ */ jsxs("div", {
676
+ className: link_menu_module_default.PreviewBody,
677
+ children: [/* @__PURE__ */ jsx(LinkAnchor, {
678
+ href,
679
+ className: link_menu_module_default.Title,
680
+ onLinkClick,
681
+ children: preview.title
682
+ }), /* @__PURE__ */ jsx("div", {
683
+ className: link_menu_module_default.Host,
684
+ children: getHostname(href)
685
+ })]
686
+ }),
687
+ /* @__PURE__ */ jsx(LinkActions, {
688
+ href,
689
+ onLinkCopy,
690
+ onEdit,
691
+ onRemove
692
+ })
693
+ ]
694
+ }),
695
+ preview.description && /* @__PURE__ */ jsx("p", {
696
+ className: link_menu_module_default.Description,
697
+ children: preview.description
698
+ }),
699
+ onUseTitle && /* @__PURE__ */ jsxs("div", {
700
+ className: link_menu_module_default.ReplaceRow,
701
+ children: [
702
+ /* @__PURE__ */ jsx(SparklesIcon, { "aria-hidden": "true" }),
703
+ /* @__PURE__ */ jsx("span", { children: "Replace URL with its title?" }),
704
+ /* @__PURE__ */ jsx("button", {
705
+ type: "button",
706
+ className: link_menu_module_default.ReplaceButton,
707
+ "aria-label": "Replace URL with its title",
708
+ onClick: () => onUseTitle(preview.title),
709
+ children: "Yes"
710
+ })
711
+ ]
712
+ })
713
+ ]
714
+ });
715
+ }
716
+ return /* @__PURE__ */ jsxs("div", {
717
+ "data-testid": "link-popover-info",
718
+ children: [/* @__PURE__ */ jsxs("div", {
719
+ className: link_menu_module_default.Row,
720
+ children: [/* @__PURE__ */ jsx(LinkAnchor, {
721
+ href,
722
+ className: link_menu_module_default.Url,
723
+ onLinkClick,
724
+ children: href
725
+ }), /* @__PURE__ */ jsx(LinkActions, {
726
+ href,
727
+ onLinkCopy,
728
+ onEdit,
729
+ onRemove
730
+ })]
731
+ }), previewState.status === "loading" && /* @__PURE__ */ jsxs("div", {
732
+ className: link_menu_module_default.Skeleton,
733
+ "data-testid": "link-popover-loading",
734
+ role: "status",
735
+ "aria-label": "Loading link preview",
736
+ children: [/* @__PURE__ */ jsx("div", { className: link_menu_module_default.SkeletonLine }), /* @__PURE__ */ jsx("div", { className: link_menu_module_default.SkeletonLineShort })]
737
+ })]
738
+ });
739
+ }
740
+ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
741
+ const [text, setText] = useState(edit.text);
742
+ const [href, setHref] = useState(edit.link?.href ?? "");
743
+ const [debouncedHref, setDebouncedHref] = useState(href.trim());
744
+ const textInputRef = useRef(null);
745
+ const previewHref = normalizeHref(debouncedHref);
746
+ const previewState = useLinkPreview(previewHref || void 0, resolveLinkPreview);
747
+ const canUseTitle = !!edit.link && debouncedHref === href.trim() && isLinkTextForHref(text, previewHref);
748
+ useEffect(() => {
749
+ const timer = window.setTimeout(() => setDebouncedHref(href.trim()), 400);
750
+ return () => window.clearTimeout(timer);
751
+ }, [href]);
641
752
  useEffect(() => {
642
- hrefInputRef.current?.focus();
753
+ textInputRef.current?.focus();
643
754
  }, []);
644
755
  return /* @__PURE__ */ jsxs("form", {
645
756
  className: link_menu_module_default.Form,
646
757
  "data-testid": "link-popover-edit",
647
758
  onSubmit: (event) => {
648
759
  event.preventDefault();
649
- onSubmit(hrefInputRef.current?.value || "", titleInputRef.current?.value || "");
760
+ if (text.trim() && href.trim()) onSubmit(text, href);
650
761
  },
651
762
  children: [
652
- /* @__PURE__ */ jsx("input", {
653
- ref: hrefInputRef,
654
- className: link_menu_module_default.Input,
655
- defaultValue: href,
656
- placeholder: "Paste link...",
657
- "data-testid": "link-popover-input"
763
+ /* @__PURE__ */ jsxs("label", {
764
+ className: link_menu_module_default.Field,
765
+ children: [/* @__PURE__ */ jsx("span", { children: "Text" }), /* @__PURE__ */ jsx("input", {
766
+ ref: textInputRef,
767
+ className: link_menu_module_default.Input,
768
+ value: text,
769
+ required: true,
770
+ "data-testid": "link-popover-text-input",
771
+ onChange: (event) => setText(event.target.value)
772
+ })]
658
773
  }),
659
- /* @__PURE__ */ jsx("input", {
660
- ref: titleInputRef,
661
- className: link_menu_module_default.Input,
662
- defaultValue: title,
663
- placeholder: "Title (optional)"
774
+ /* @__PURE__ */ jsxs("label", {
775
+ className: link_menu_module_default.Field,
776
+ children: [/* @__PURE__ */ jsx("span", { children: "Link" }), /* @__PURE__ */ jsx("input", {
777
+ className: link_menu_module_default.Input,
778
+ value: href,
779
+ required: true,
780
+ placeholder: "Paste link...",
781
+ "data-testid": "link-popover-input",
782
+ onChange: (event) => setHref(event.target.value)
783
+ })]
664
784
  }),
665
- /* @__PURE__ */ jsx("button", {
666
- type: "submit",
667
- className: link_menu_module_default.SrOnly,
668
- "data-testid": "link-popover-submit",
669
- children: "Save"
785
+ /* @__PURE__ */ jsxs("div", {
786
+ className: link_menu_module_default.FormActions,
787
+ children: [
788
+ onRemove && /* @__PURE__ */ jsx("button", {
789
+ type: "button",
790
+ className: link_menu_module_default.RemoveButton,
791
+ onClick: onRemove,
792
+ children: "Remove link"
793
+ }),
794
+ canUseTitle && previewState.status === "resolved" && /* @__PURE__ */ jsx("button", {
795
+ type: "button",
796
+ className: link_menu_module_default.UseTitleButton,
797
+ onClick: () => setText(previewState.preview.title),
798
+ children: "Use page title"
799
+ }),
800
+ /* @__PURE__ */ jsx("button", {
801
+ type: "submit",
802
+ className: link_menu_module_default.SaveButton,
803
+ "data-testid": "link-popover-submit",
804
+ children: "Save"
805
+ })
806
+ ]
670
807
  })
671
808
  ]
672
809
  });
673
810
  }
674
- /**
675
- * Owns both link triggers and shows one popover at a time:
676
- *
677
- * - hovering a link opens a read-only preview that follows the pointer;
678
- * - `Mod-k` (or the preview's edit button) opens an edit form that stays until
679
- * it is submitted, dismissed with Escape, or pressed outside.
680
- */
681
- function LinkMenu({ onLinkClick, onLinkCopy }) {
811
+ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = false }) {
682
812
  const editor = useEditor$1();
683
- const [hover, setHover] = useState();
684
- const [onLink, setOnLink] = useState(false);
685
- const [overPopup, setOverPopup] = useState(false);
813
+ const [infoOpen, setInfoOpen] = useState(false);
814
+ const [editOpen, setEditOpen] = useState(false);
815
+ const [link, setLink] = useState();
686
816
  const [edit, setEdit] = useState();
687
- const hoverOpen = useDelayedFlag(onLink || overPopup);
688
- const linkHoverExtension = useMemo(() => {
689
- return defineLinkHoverHandler((hit) => {
690
- setOnLink(!!hit);
691
- if (hit) setHover(hit.payload);
692
- });
693
- }, []);
817
+ const isPointerOverPopupRef = useRef(false);
818
+ const linkHoverExtension = useMemo(() => defineLinkHoverHandler((nextHit) => {
819
+ setInfoOpen(nextHit != null);
820
+ if (nextHit) setLink(nextHit.payload);
821
+ }, { canLeave: () => !isPointerOverPopupRef.current }), []);
694
822
  useExtension$1(linkHoverExtension);
695
- const linkEditExtension = useMemo(() => {
696
- return defineLinkEditKeymap((options) => {
697
- setEdit(options);
698
- });
699
- }, []);
823
+ const linkEditExtension = useMemo(() => readOnly ? null : defineLinkEditKeymap((options) => {
824
+ setEdit(options);
825
+ setEditOpen(true);
826
+ setInfoOpen(false);
827
+ }), [readOnly]);
700
828
  useExtension$1(linkEditExtension);
701
- const closeHover = useCallback(() => {
702
- setOnLink(false);
703
- setOverPopup(false);
704
- setHover(void 0);
829
+ const handlePointerHover = useCallback((over) => {
830
+ isPointerOverPopupRef.current = over;
831
+ }, []);
832
+ const closeInfo = useCallback(() => {
833
+ isPointerOverPopupRef.current = false;
834
+ setInfoOpen(false);
705
835
  }, []);
706
836
  const closeEdit = useCallback(() => {
707
- setEdit(void 0);
708
- closeHover();
709
- editor.focus();
710
- }, [editor, closeHover]);
711
- let rangeFrom;
712
- let rangeTo;
713
- if (edit) {
714
- const anchorRange = edit.link?.text ?? edit;
715
- rangeFrom = anchorRange.from;
716
- rangeTo = anchorRange.to;
717
- } else if (hover) {
718
- const anchorRange = hover.text;
719
- rangeFrom = anchorRange.from;
720
- rangeTo = anchorRange.to;
721
- }
722
- const anchor = useMemo(() => {
723
- if (rangeFrom == null || rangeTo == null) return;
724
- return getVirtualElementFromRange(editor.view, {
725
- from: rangeFrom,
726
- to: rangeTo
837
+ setEditOpen(false);
838
+ closeInfo();
839
+ }, [closeInfo]);
840
+ const handleEditRemove = useCallback(() => {
841
+ editor.commands.removeLink();
842
+ closeEdit();
843
+ }, [editor, closeEdit]);
844
+ const handleEditSubmit = useCallback((text, href) => {
845
+ if (!edit) return;
846
+ if (edit.link) editor.commands.updateLink({
847
+ text: text === edit.text ? void 0 : text,
848
+ href,
849
+ title: edit.link.title
850
+ });
851
+ else editor.commands.insertLink({
852
+ text,
853
+ href
727
854
  });
855
+ closeEdit();
728
856
  }, [
729
- rangeFrom,
730
- rangeTo,
731
- editor
857
+ edit,
858
+ editor,
859
+ closeEdit
860
+ ]);
861
+ const mutable = link != null && !readOnly && link.form !== "reference";
862
+ const linkText = useMemo(() => link ? getLinkText(link) : "", [link]);
863
+ const editLink = useCallback(() => {
864
+ if (!link) return;
865
+ selectLinkUnit(editor, link);
866
+ setEdit({
867
+ from: link.unit.from,
868
+ to: link.unit.to,
869
+ link,
870
+ text: linkText
871
+ });
872
+ setEditOpen(true);
873
+ closeInfo();
874
+ }, [
875
+ link,
876
+ editor,
877
+ linkText,
878
+ closeInfo
879
+ ]);
880
+ const removeLink = useCallback(() => {
881
+ if (!link) return;
882
+ selectLinkUnit(editor, link);
883
+ editor.commands.removeLink();
884
+ closeInfo();
885
+ }, [
886
+ link,
887
+ editor,
888
+ closeInfo
889
+ ]);
890
+ const handleUseTitle = useMemo(() => {
891
+ if (!link || !mutable || !isLinkTextForHref(linkText, link.href)) return;
892
+ return (title) => {
893
+ selectLinkUnit(editor, link);
894
+ editor.commands.updateLink({ text: title });
895
+ closeInfo();
896
+ };
897
+ }, [
898
+ link,
899
+ editor,
900
+ closeInfo,
901
+ linkText,
902
+ mutable
732
903
  ]);
733
- if (edit) return /* @__PURE__ */ jsx(LinkPopover, {
904
+ const previewState = useLinkPreview(link?.href, resolveLinkPreview);
905
+ const range = edit ? edit.link?.text ?? edit : link?.text;
906
+ const anchor = useMemo(() => {
907
+ if (!range) return;
908
+ return getVirtualElementFromRange(editor.view, range);
909
+ }, [range, editor]);
910
+ const editing = edit != null && !readOnly;
911
+ return /* @__PURE__ */ jsx(LinkPopover, {
734
912
  anchor,
735
- onClose: closeEdit,
736
- children: /* @__PURE__ */ jsx(LinkEditContent, {
737
- link: edit.link,
738
- onSubmit: (href, title) => {
739
- if (edit.link) {
740
- if (href.trim()) editor.commands.updateLink({
741
- href,
742
- title
743
- });
744
- else editor.commands.removeLink();
745
- } else if (href.trim()) editor.commands.insertLink({
746
- href,
747
- title
748
- });
749
- closeEdit();
750
- }
751
- })
913
+ open: editing ? editOpen : infoOpen,
914
+ onClose: editing ? closeEdit : closeInfo,
915
+ onCloseComplete: () => {
916
+ if (editing) {
917
+ setEdit(void 0);
918
+ editor.focus();
919
+ } else if (!infoOpen) setLink(void 0);
920
+ },
921
+ onPopupHover: handlePointerHover,
922
+ children: editing ? /* @__PURE__ */ jsx(LinkEditContent, {
923
+ edit,
924
+ resolveLinkPreview,
925
+ onRemove: handleEditRemove,
926
+ onSubmit: handleEditSubmit
927
+ }) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
928
+ href: link.href,
929
+ previewState,
930
+ onLinkClick,
931
+ onLinkCopy,
932
+ onEdit: mutable ? editLink : void 0,
933
+ onRemove: mutable ? removeLink : void 0,
934
+ onUseTitle: handleUseTitle
935
+ }) : void 0
752
936
  });
753
- if (hoverOpen && hover) {
754
- const link = hover;
755
- const editable = link.form === "inline";
756
- return /* @__PURE__ */ jsx(LinkPopover, {
757
- anchor,
758
- onClose: closeHover,
759
- onPopupHover: setOverPopup,
760
- children: /* @__PURE__ */ jsx(LinkInfoContent, {
761
- href: link.href,
762
- onLinkClick,
763
- onLinkCopy,
764
- onEdit: editable ? () => {
765
- selectLinkUnit(editor, link);
766
- setEdit({
767
- from: link.unit.from,
768
- to: link.unit.to,
769
- link
770
- });
771
- closeHover();
772
- } : void 0,
773
- onRemove: editable ? () => {
774
- selectLinkUnit(editor, link);
775
- editor.commands.removeLink();
776
- closeHover();
777
- } : void 0
778
- })
779
- });
780
- }
781
- return null;
782
937
  }
783
938
 
784
939
  //#endregion
@@ -922,6 +1077,37 @@ function PendingReplacementPreview({ actions, onResolve }) {
922
1077
  });
923
1078
  }
924
1079
 
1080
+ //#endregion
1081
+ //#region src/hooks/use-delayed-flag.ts
1082
+ /**
1083
+ * Delay before the flag opens, in ms.
1084
+ */
1085
+ const OPEN_DELAY$1 = 300;
1086
+ /**
1087
+ * Grace before the flag closes, in ms. The window lets a pointer travel from
1088
+ * the hovered link onto the popover it anchors.
1089
+ */
1090
+ const CLOSE_DELAY$1 = 100;
1091
+ /**
1092
+ * Mirrors `value` into a boolean that flips true `openDelay`ms after `value`
1093
+ * becomes true and false `closeDelay`ms after it becomes false, cancelling any
1094
+ * pending flip on each change.
1095
+ */
1096
+ function useDelayedFlag(value, openDelay = OPEN_DELAY$1, closeDelay = CLOSE_DELAY$1) {
1097
+ const [flag, setFlag] = useState(false);
1098
+ const timerRef = useRef(void 0);
1099
+ useEffect(() => {
1100
+ clearTimeout(timerRef.current);
1101
+ timerRef.current = setTimeout(() => setFlag(value), value ? openDelay : closeDelay);
1102
+ return () => clearTimeout(timerRef.current);
1103
+ }, [
1104
+ value,
1105
+ openDelay,
1106
+ closeDelay
1107
+ ]);
1108
+ return flag;
1109
+ }
1110
+
925
1111
  //#endregion
926
1112
  //#region src/components/selection-menu.module.css
927
1113
  var selection_menu_module_default = {
@@ -1727,7 +1913,7 @@ function findHeadingPosition(doc, fragment) {
1727
1913
  });
1728
1914
  return match;
1729
1915
  }
1730
- function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, ref, children }) {
1916
+ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, ref, children }) {
1731
1917
  const [editor] = useState(() => {
1732
1918
  const baseExtension = defineEditorExtension({
1733
1919
  resolveFileLink,
@@ -1918,9 +2104,11 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1918
2104
  onFilePaste,
1919
2105
  onFileSaveError
1920
2106
  }),
1921
- !readOnly && /* @__PURE__ */ jsx(LinkMenu, {
2107
+ /* @__PURE__ */ jsx(LinkMenu, {
1922
2108
  onLinkClick,
1923
- onLinkCopy
2109
+ onLinkCopy,
2110
+ resolveLinkPreview,
2111
+ readOnly
1924
2112
  }),
1925
2113
  onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
1926
2114
  onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
@@ -1949,7 +2137,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
1949
2137
  *
1950
2138
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
1951
2139
  */
1952
- function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
2140
+ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
1953
2141
  const childRef = useRef(null);
1954
2142
  useImperativeHandle(handleRef, () => {
1955
2143
  function getMarkdown() {
@@ -2060,6 +2248,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2060
2248
  onWikilinkClick,
2061
2249
  onLinkClick,
2062
2250
  onLinkCopy,
2251
+ resolveLinkPreview,
2063
2252
  onTagClick,
2064
2253
  onExitBoundary,
2065
2254
  resolveImageUrl,
package/dist/style.css CHANGED
@@ -294,18 +294,43 @@
294
294
 
295
295
  .meow_Popup_IjcqKG {
296
296
  box-sizing: border-box;
297
- border: 1px solid var(--meowdown-border);
297
+ width: 100%;
298
+ color: var(--meowdown-text);
299
+ border: 1px solid var(--meowdown-hover-card-border);
300
+ border-radius: var(--meowdown-hover-card-radius);
298
301
  background: var(--meowdown-popover-bg);
299
- border-radius: .75rem;
302
+ box-shadow: var(--meowdown-hover-card-shadow);
303
+ transform-origin: var(--transform-origin);
304
+ outline: none;
300
305
  flex-direction: column;
301
- width: 100%;
302
- padding: .25rem;
303
306
  font-size: .875rem;
307
+ transition: opacity .15s, transform .15s;
304
308
  display: flex;
305
- box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
309
+ overflow: hidden;
310
+
311
+ &[data-closed] {
312
+ transition: opacity .1s, transform .1s;
313
+ }
314
+
315
+ &[data-starting-style], &[data-ending-style] {
316
+ opacity: 0;
317
+ transform: scale(.96);
318
+ }
319
+
320
+ @media (prefers-reduced-motion: reduce) {
321
+ transition: none;
322
+ }
306
323
  }
307
324
 
308
- .meow_Row_IjcqKG {
325
+ .meow_Row_IjcqKG, .meow_Preview_IjcqKG {
326
+ align-items: center;
327
+ gap: .5rem;
328
+ padding: .5rem;
329
+ display: flex;
330
+ }
331
+
332
+ .meow_Actions_IjcqKG {
333
+ flex: none;
309
334
  align-items: center;
310
335
  gap: .125rem;
311
336
  display: flex;
@@ -318,18 +343,23 @@
318
343
  color: var(--meowdown-accent);
319
344
  cursor: pointer;
320
345
  flex: 1;
321
- padding: .25rem .5rem;
346
+ padding: .25rem .375rem;
347
+ text-decoration: none;
322
348
  overflow: hidden;
323
349
  }
324
350
 
325
351
  .meow_Button_IjcqKG {
352
+ width: 2rem;
353
+ height: 2rem;
326
354
  color: var(--meowdown-text);
327
355
  cursor: pointer;
356
+ background: none;
357
+ border: 0;
328
358
  border-radius: .5rem;
329
359
  flex: none;
330
360
  justify-content: center;
331
361
  align-items: center;
332
- padding: .375rem;
362
+ padding: 0;
333
363
  display: inline-flex;
334
364
 
335
365
  &:hover {
@@ -340,26 +370,173 @@
340
370
  width: 1rem;
341
371
  height: 1rem;
342
372
  }
373
+
374
+ @media (pointer: coarse) {
375
+ width: 2.75rem;
376
+ height: 2.75rem;
377
+ }
343
378
  }
344
379
 
345
- .meow_Form_IjcqKG {
380
+ .meow_Icon_IjcqKG {
381
+ width: 2rem;
382
+ height: 2rem;
383
+ color: var(--meowdown-muted);
384
+ border-radius: .375rem;
385
+ flex: none;
386
+ justify-content: center;
387
+ align-items: center;
388
+ display: flex;
389
+ overflow: hidden;
390
+
391
+ & img, & svg {
392
+ object-fit: contain;
393
+ width: 1.25rem;
394
+ height: 1.25rem;
395
+ }
396
+ }
397
+
398
+ .meow_PreviewBody_IjcqKG {
346
399
  flex-direction: column;
347
- gap: .25rem;
348
- padding: .25rem;
400
+ flex: 1;
401
+ min-width: 0;
349
402
  display: flex;
350
403
  }
351
404
 
352
- .meow_SrOnly_IjcqKG {
353
- clip: rect(0, 0, 0, 0);
354
- border: 0;
355
- width: 1px;
356
- height: 1px;
357
- margin: -1px;
358
- padding: 0;
359
- position: absolute;
405
+ .meow_Title_IjcqKG {
406
+ white-space: nowrap;
407
+ text-overflow: ellipsis;
408
+ color: var(--meowdown-accent);
409
+ font-weight: 500;
410
+ line-height: 1.3;
411
+ text-decoration: none;
412
+ overflow: hidden;
413
+ }
414
+
415
+ .meow_Host_IjcqKG {
416
+ white-space: nowrap;
417
+ text-overflow: ellipsis;
418
+ color: var(--meowdown-muted);
419
+ font-size: .8125rem;
420
+ line-height: 1.3;
421
+ overflow: hidden;
422
+ }
423
+
424
+ .meow_Description_IjcqKG {
425
+ color: var(--meowdown-muted);
426
+ -webkit-line-clamp: 2;
427
+ -webkit-box-orient: vertical;
428
+ margin: 0;
429
+ padding: 0 .75rem .75rem 3rem;
430
+ line-height: 1.4;
431
+ display: -webkit-box;
360
432
  overflow: hidden;
361
433
  }
362
434
 
435
+ .meow_ReplaceRow_IjcqKG {
436
+ min-height: 2.5rem;
437
+ color: var(--meowdown-text);
438
+ border-top: 1px solid var(--meowdown-border);
439
+ align-items: center;
440
+ gap: .5rem;
441
+ padding: .375rem .5rem .375rem .75rem;
442
+ display: flex;
443
+
444
+ & > svg {
445
+ width: .875rem;
446
+ height: .875rem;
447
+ color: var(--meowdown-accent);
448
+ }
449
+
450
+ & > span {
451
+ flex: 1;
452
+ min-width: 0;
453
+ font-size: .8125rem;
454
+ font-weight: 500;
455
+ }
456
+ }
457
+
458
+ .meow_ReplaceButton_IjcqKG, .meow_SaveButton_IjcqKG {
459
+ min-height: 2rem;
460
+ font: inherit;
461
+ color: var(--meowdown-accent-contrast);
462
+ background: var(--meowdown-accent);
463
+ cursor: pointer;
464
+ border: 0;
465
+ border-radius: .5rem;
466
+ padding: .375rem .625rem;
467
+ font-weight: 500;
468
+
469
+ @media (pointer: coarse) {
470
+ min-height: 2.75rem;
471
+ }
472
+ }
473
+
474
+ .meow_ReplaceButton_IjcqKG {
475
+ flex: none;
476
+ min-height: 1.75rem;
477
+ padding: .25rem .625rem;
478
+ transition: filter .1s, transform .1s ease-out;
479
+
480
+ &:hover {
481
+ filter: brightness(1.05);
482
+ }
483
+
484
+ &:active {
485
+ transform: scale(.97);
486
+ }
487
+ }
488
+
489
+ .meow_Skeleton_IjcqKG {
490
+ flex-direction: column;
491
+ gap: .625rem;
492
+ padding: 0 .5rem .625rem;
493
+ display: flex;
494
+ }
495
+
496
+ .meow_SkeletonLine_IjcqKG, .meow_SkeletonLineShort_IjcqKG {
497
+ background: var(--meowdown-popover-hover-bg);
498
+ border-radius: 999px;
499
+ height: .75rem;
500
+ animation: 1.4s ease-in-out infinite meow_pulse_IjcqKG;
501
+
502
+ @media (prefers-reduced-motion: reduce) {
503
+ animation: none;
504
+ }
505
+ }
506
+
507
+ .meow_SkeletonLine_IjcqKG {
508
+ width: 90%;
509
+ }
510
+
511
+ .meow_SkeletonLineShort_IjcqKG {
512
+ width: 70%;
513
+ }
514
+
515
+ @keyframes meow_pulse_IjcqKG {
516
+ 50% {
517
+ opacity: .45;
518
+ }
519
+ }
520
+
521
+ .meow_Form_IjcqKG {
522
+ flex-direction: column;
523
+ gap: .625rem;
524
+ padding: .75rem;
525
+ display: flex;
526
+ }
527
+
528
+ .meow_Field_IjcqKG {
529
+ flex-direction: column;
530
+ gap: .25rem;
531
+ display: flex;
532
+
533
+ & > span {
534
+ color: var(--meowdown-muted);
535
+ font-size: .75rem;
536
+ font-weight: 600;
537
+ }
538
+ }
539
+
363
540
  .meow_Input_IjcqKG {
364
541
  box-sizing: border-box;
365
542
  width: 100%;
@@ -368,13 +545,43 @@
368
545
  border: 1px solid var(--meowdown-border);
369
546
  background: none;
370
547
  border-radius: .5rem;
371
- padding: .375rem .5rem;
548
+ padding: .5rem .625rem;
372
549
 
373
550
  &:focus {
374
551
  outline: 2px solid var(--meowdown-accent);
375
552
  outline-offset: -1px;
376
553
  }
377
554
  }
555
+
556
+ .meow_FormActions_IjcqKG {
557
+ align-items: center;
558
+ gap: .375rem;
559
+ padding-top: .125rem;
560
+ display: flex;
561
+ }
562
+
563
+ .meow_RemoveButton_IjcqKG, .meow_UseTitleButton_IjcqKG {
564
+ min-height: 2rem;
565
+ font: inherit;
566
+ cursor: pointer;
567
+ background: none;
568
+ border: 0;
569
+ border-radius: .5rem;
570
+ padding: .375rem .625rem;
571
+
572
+ @media (pointer: coarse) {
573
+ min-height: 2.75rem;
574
+ }
575
+ }
576
+
577
+ .meow_RemoveButton_IjcqKG {
578
+ color: var(--meowdown-danger, #c33);
579
+ }
580
+
581
+ .meow_UseTitleButton_IjcqKG {
582
+ color: var(--meowdown-accent);
583
+ margin-left: auto;
584
+ }
378
585
  .meow_Positioner_DPJ5ia {
379
586
  z-index: 50;
380
587
  width: min(30rem, 100vw - 1rem);
@@ -420,9 +627,12 @@
420
627
  }
421
628
 
422
629
  .meow_Button_DPJ5ia {
630
+ font: inherit;
423
631
  color: var(--meowdown-text);
424
632
  white-space: nowrap;
425
633
  cursor: pointer;
634
+ background: none;
635
+ border: 0;
426
636
  border-radius: .5rem;
427
637
  align-items: center;
428
638
  padding: .25rem .625rem;
@@ -434,10 +644,12 @@
434
644
  }
435
645
 
436
646
  .meow_AcceptButton_DPJ5ia {
647
+ font: inherit;
437
648
  color: var(--meowdown-popover-bg);
438
649
  background: var(--meowdown-accent);
439
650
  white-space: nowrap;
440
651
  cursor: pointer;
652
+ border: 0;
441
653
  border-radius: .5rem;
442
654
  align-items: center;
443
655
  padding: .25rem .625rem;
@@ -497,8 +709,11 @@
497
709
 
498
710
  .meow_Item_2_4Zjq {
499
711
  text-align: left;
712
+ font: inherit;
500
713
  color: var(--meowdown-text);
501
714
  cursor: pointer;
715
+ background: none;
716
+ border: 0;
502
717
  border-radius: .5rem;
503
718
  align-items: baseline;
504
719
  gap: .5rem;
@@ -794,16 +1009,16 @@
794
1009
  .meow_Popup_HMEwgW {
795
1010
  box-sizing: border-box;
796
1011
  pointer-events: none;
797
- border: 1px solid var(--meowdown-border);
1012
+ border: 1px solid var(--meowdown-hover-card-border);
1013
+ border-radius: var(--meowdown-hover-card-radius);
798
1014
  background: var(--meowdown-popover-bg);
799
1015
  width: min(20rem, 100vw - 1rem);
800
1016
  max-height: min(12rem, 100vh - 1rem);
1017
+ box-shadow: var(--meowdown-hover-card-shadow);
801
1018
  transform-origin: var(--transform-origin);
802
- border-radius: .75rem;
803
1019
  outline: none;
804
1020
  transition: opacity .15s, transform .15s;
805
1021
  display: flex;
806
- box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
807
1022
  }
808
1023
 
809
1024
  .meow_Popup_HMEwgW[data-closed] {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.66.0",
4
+ "version": "0.67.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -23,15 +23,15 @@
23
23
  "dependencies": {
24
24
  "@base-ui/react": "^1.7.0",
25
25
  "@ocavue/utils": "^1.7.0",
26
- "@prosekit/core": "^0.13.0",
26
+ "@prosekit/core": "^0.13.1",
27
27
  "@prosekit/pm": "^0.1.19",
28
- "@prosekit/react": "^0.8.0",
28
+ "@prosekit/react": "^0.8.1",
29
29
  "beautiful-mermaid": "^1.1.3",
30
30
  "clsx": "^2.1.1",
31
31
  "github-slugger": "^2.0.0",
32
32
  "lucide-react": "^1.28.0",
33
33
  "react-property": "^2.0.2",
34
- "@meowdown/core": "0.66.0"
34
+ "@meowdown/core": "0.67.1"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^19.0.0",
@@ -51,12 +51,12 @@
51
51
  "@tsdown/css": "^0.23.0-beta.2",
52
52
  "@types/react": "^19.2.18",
53
53
  "@types/react-dom": "^19.2.4",
54
- "@vitest/browser-playwright": "^4.1.10",
54
+ "@vitest/browser-playwright": "^4.1.11",
55
55
  "dedent": "^1.7.2",
56
56
  "react": "^19.2.8",
57
57
  "react-dom": "^19.2.8",
58
58
  "tsdown": "^0.23.0-beta.2",
59
- "vitest": "^4.1.10",
59
+ "vitest": "^4.1.11",
60
60
  "vitest-browser-commands": "^0.2.1",
61
61
  "vitest-browser-react": "^2.2.0",
62
62
  "@meowdown/vitest": "0.0.0"