@meowdown/react 0.68.2 → 0.69.0

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/README.md CHANGED
@@ -66,6 +66,7 @@ Common `MeowdownEditor` props:
66
66
  | `onFilePaste` | Persist pasted or dropped files |
67
67
  | `onSlashMenuSearch` / `onTagSearch` / `onWikilinkSearch` / `onSelectionMenuSearch` | Search menus for `/`, `#`, `[[`, and selection commands |
68
68
  | `readOnly` / `placeholder` / `blockHandle` / `caretGlide` / `embedPaste` / `linkPaste` / `bulletAfterHeading` | Behavior toggles |
69
+ | `CodeBlockView` | Custom React node view component for code blocks |
69
70
 
70
71
  Every prop, callback, and `EditorHandle` method is documented in the [API reference](https://npmx.dev/package-docs/@meowdown%2Freact/).
71
72
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ReactElement, ReactNode, Ref } from "react";
2
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, WikilinkResolver } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
- import { useEditor, useExtension, useKeymap } from "@prosekit/react";
4
+ import { ReactNodeViewComponent, ReactNodeViewComponent as ReactNodeViewComponent$1, useEditor, useExtension, useKeymap } from "@prosekit/react";
5
5
  //#region src/utils/date-format.d.ts
6
6
  type TimeFormat = '12' | '24';
7
7
  //#endregion
@@ -509,6 +509,11 @@ interface EditorProps {
509
509
  * Class on the outer `.meowdown` wrapper div.
510
510
  */
511
511
  wrapperClassName?: string;
512
+ /**
513
+ * React component that renders code blocks in place of the built-in one.
514
+ * `false` disables the React code block view.
515
+ */
516
+ CodeBlockView?: ReactNodeViewComponent$1 | false | undefined;
512
517
  /**
513
518
  * Imperative handle for the editor.
514
519
  */
@@ -524,7 +529,7 @@ interface EditorProps {
524
529
  *
525
530
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
526
531
  */
527
- export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): ReactElement;
532
+ export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }: EditorProps): ReactElement;
528
533
  //#endregion
529
534
  //#region src/components/markdown-view.d.ts
530
535
  /**
@@ -675,4 +680,4 @@ interface WikilinkHoverCardProps {
675
680
  */
676
681
  export declare function WikilinkHoverCard({ children, className }: WikilinkHoverCardProps): ReactNode;
677
682
  //#endregion
678
- 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 };
683
+ export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, 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, useEditor, useExtension, useKeymap };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { clsx } from "clsx/lite";
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, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, renderMathInto } from "@meowdown/core";
2
+ import { Fragment, cloneElement, createElement, memo, 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, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, 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";
@@ -353,11 +353,11 @@ function CodeBlockToolbar({ code, language, setLanguage }) {
353
353
 
354
354
  //#endregion
355
355
  //#region src/extensions/code-block-view.ts
356
- function defineCodeBlockView() {
356
+ function defineCodeBlockView(component = CodeBlockView) {
357
357
  return union(defineReactNodeView({
358
358
  name: "codeBlock",
359
359
  contentAs: "code",
360
- component: CodeBlockView
360
+ component
361
361
  }), defineCodeBlockPreviewPlugin());
362
362
  }
363
363
 
@@ -741,32 +741,33 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
741
741
  const [text, setText] = useState(edit.text);
742
742
  const [href, setHref] = useState(edit.link?.href ?? "");
743
743
  const [debouncedHref, setDebouncedHref] = useState(href.trim());
744
- const textInputRef = useRef(null);
744
+ const hrefInputRef = useRef(null);
745
745
  const previewHref = normalizeHref(debouncedHref);
746
746
  const previewState = useLinkPreview(previewHref || void 0, resolveLinkPreview);
747
747
  const canUseTitle = !!edit.link && debouncedHref === href.trim() && isLinkTextForHref(text, previewHref);
748
+ const canSave = text.trim() !== "" && href.trim() !== "";
748
749
  useEffect(() => {
749
750
  const timer = window.setTimeout(() => setDebouncedHref(href.trim()), 400);
750
751
  return () => window.clearTimeout(timer);
751
752
  }, [href]);
752
753
  useEffect(() => {
753
- textInputRef.current?.focus();
754
+ hrefInputRef.current?.focus();
755
+ hrefInputRef.current?.select();
754
756
  }, []);
755
757
  return /* @__PURE__ */ jsxs("form", {
756
758
  className: link_menu_module_default.Form,
757
759
  "data-testid": "link-popover-edit",
760
+ noValidate: true,
758
761
  onSubmit: (event) => {
759
762
  event.preventDefault();
760
- if (text.trim() && href.trim()) onSubmit(text, href);
763
+ if (canSave) onSubmit(text, href);
761
764
  },
762
765
  children: [
763
766
  /* @__PURE__ */ jsxs("label", {
764
767
  className: link_menu_module_default.Field,
765
768
  children: [/* @__PURE__ */ jsx("span", { children: "Text" }), /* @__PURE__ */ jsx("input", {
766
- ref: textInputRef,
767
769
  className: link_menu_module_default.Input,
768
770
  value: text,
769
- required: true,
770
771
  "data-testid": "link-popover-text-input",
771
772
  onChange: (event) => setText(event.target.value)
772
773
  })]
@@ -774,10 +775,10 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
774
775
  /* @__PURE__ */ jsxs("label", {
775
776
  className: link_menu_module_default.Field,
776
777
  children: [/* @__PURE__ */ jsx("span", { children: "Link" }), /* @__PURE__ */ jsx("input", {
778
+ ref: hrefInputRef,
777
779
  className: link_menu_module_default.Input,
778
780
  value: href,
779
- required: true,
780
- placeholder: "Paste link...",
781
+ placeholder: "Paste link…",
781
782
  "data-testid": "link-popover-input",
782
783
  onChange: (event) => setHref(event.target.value)
783
784
  })]
@@ -800,6 +801,7 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
800
801
  /* @__PURE__ */ jsx("button", {
801
802
  type: "submit",
802
803
  className: link_menu_module_default.SaveButton,
804
+ disabled: !canSave,
803
805
  "data-testid": "link-popover-submit",
804
806
  children: "Save"
805
807
  })
@@ -926,7 +928,7 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
926
928
  children: editing ? /* @__PURE__ */ jsx(LinkEditContent, {
927
929
  edit,
928
930
  resolveLinkPreview,
929
- onRemove: handleEditRemove,
931
+ onRemove: edit.link ? handleEditRemove : void 0,
930
932
  onSubmit: handleEditSubmit
931
933
  }) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
932
934
  href: link.href,
@@ -1923,7 +1925,7 @@ function findHeadingPosition(doc, fragment) {
1923
1925
  });
1924
1926
  return match;
1925
1927
  }
1926
- function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, ref, children }) {
1928
+ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, CodeBlockView, ref, children }) {
1927
1929
  const [editor] = useState(() => {
1928
1930
  const baseExtension = defineEditorExtension({
1929
1931
  resolveFileLink,
@@ -1931,7 +1933,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1931
1933
  resolveWikilink,
1932
1934
  markMode
1933
1935
  });
1934
- const extension = union(baseExtension, defineCodeBlockView());
1936
+ const extension = CodeBlockView === false ? baseExtension : union(baseExtension, defineCodeBlockView(CodeBlockView));
1935
1937
  const editor = createEditor({ extension });
1936
1938
  if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
1937
1939
  nodes: editor.nodes,
@@ -2148,7 +2150,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
2148
2150
  *
2149
2151
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
2150
2152
  */
2151
- function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, 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 }) {
2153
+ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, 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, CodeBlockView, handleRef, children }) {
2152
2154
  const childRef = useRef(null);
2153
2155
  useImperativeHandle(handleRef, () => {
2154
2156
  function getMarkdown() {
@@ -2284,6 +2286,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2284
2286
  onSearchChange,
2285
2287
  timeFormat,
2286
2288
  editorClassName,
2289
+ CodeBlockView,
2287
2290
  children
2288
2291
  })
2289
2292
  });
@@ -2764,10 +2767,7 @@ function renderInline(node, context) {
2764
2767
  resolveFileLink: context.resolveFileLink,
2765
2768
  resolveWikiEmbed: context.resolveWikiEmbed,
2766
2769
  resolveWikilink: context.resolveWikilink
2767
- }, {
2768
- referenceDefinitions: context.referenceDefinitions,
2769
- isReferenceDefinition: context.referenceDefinitionNodes.has(node)
2770
- }).map(([from, to, marks]) => ({
2770
+ }, { referenceDefinitions: context.referenceDefinitions }).map(([from, to, marks]) => ({
2771
2771
  text: text.slice(from, to),
2772
2772
  marks: Mark.setFrom(marks)
2773
2773
  })), 0, context);
@@ -2812,8 +2812,8 @@ function renderCodeBlock(node, key) {
2812
2812
  language
2813
2813
  }, key);
2814
2814
  }
2815
- function renderBlock(node, context) {
2816
- if (context.referenceDefinitionNodes.has(node)) return null;
2815
+ function renderBlock(node, context, parent, index) {
2816
+ if (isReferenceDefinitionNode(node, parent, index)) return null;
2817
2817
  const key = context.keyCounter.value++;
2818
2818
  const typeName = node.type.name;
2819
2819
  let handleTaskClick;
@@ -2827,11 +2827,64 @@ function renderBlock(node, context) {
2827
2827
  const inline = renderInline(node, context);
2828
2828
  return toDOM ? outputSpecToReact(toDOM(node), inline, context) : /* @__PURE__ */ jsx(Fragment, { children: inline }, key);
2829
2829
  }
2830
- const children = node.content.content.map((child) => renderBlock(child, context));
2830
+ const children = node.content.content.map((child, childIndex) => {
2831
+ return renderBlock(child, context, node, childIndex);
2832
+ });
2831
2833
  const reactNode = toDOM ? outputSpecToReact(toDOM(node), children, context) : /* @__PURE__ */ jsx(Fragment, { children }, key);
2832
2834
  if (typeName === "list" && handleTaskClick && typeof reactNode !== "string" && reactNode != null) return cloneElement(reactNode, { onClick: handleTaskClick });
2833
2835
  return reactNode;
2834
2836
  }
2837
+ function isTaskList(node) {
2838
+ return isNodeOfType(node, "list") && node.attrs.kind === "task";
2839
+ }
2840
+ /**
2841
+ * Checkboxes a block renders, in the same pre-order `renderBlock` walks, so a
2842
+ * block's first checkbox index is the sum over the blocks before it.
2843
+ */
2844
+ function countTaskItems(node) {
2845
+ let count = isTaskList(node) ? 1 : 0;
2846
+ node.descendants((child) => {
2847
+ if (isTaskList(child)) count++;
2848
+ return true;
2849
+ });
2850
+ return count;
2851
+ }
2852
+ /**
2853
+ * The effective definitions as one string, so blocks can compare them by
2854
+ * content: `collectReferenceDefinitions` builds a new map on every parse.
2855
+ */
2856
+ function definitionsSignature(definitions) {
2857
+ let signature = "";
2858
+ for (const { key, href, title } of definitions.values()) signature += `${key}\0${href}\0${title}\n`;
2859
+ return signature;
2860
+ }
2861
+ function splitBlocks(doc) {
2862
+ const blocks = [];
2863
+ let taskBase = 0;
2864
+ for (const node of doc.content.content) {
2865
+ blocks.push({
2866
+ node,
2867
+ taskBase
2868
+ });
2869
+ taskBase += countTaskItems(node);
2870
+ }
2871
+ return blocks;
2872
+ }
2873
+ /**
2874
+ * One top-level block. Memoized on the block's own content (`Node.eq`), its
2875
+ * first checkbox index, the shared props object, and the definitions'
2876
+ * content, so a growing document re-renders only the block that changed.
2877
+ */
2878
+ const MarkdownBlock = memo(function MarkdownBlock({ node, taskBase, context, referenceDefinitions }) {
2879
+ return renderBlock(node, {
2880
+ ...context,
2881
+ referenceDefinitions,
2882
+ taskCounter: { value: taskBase },
2883
+ keyCounter: { value: 0 }
2884
+ }, null, 0);
2885
+ }, (previous, next) => {
2886
+ return previous.taskBase === next.taskBase && previous.context === next.context && previous.definitionsKey === next.definitionsKey && (previous.node === next.node || previous.node.eq(next.node));
2887
+ });
2835
2888
  /**
2836
2889
  * Render Markdown to a read-only React tree that looks exactly like the editor
2837
2890
  * in `hide` mark mode: inline marks, wikilink chips, images, tweet/YouTube
@@ -2844,31 +2897,20 @@ function renderBlock(node, context) {
2844
2897
  * `useCallback` to avoid re-rendering the whole tree.
2845
2898
  */
2846
2899
  function MarkdownView({ markdown, markMode = "hide", frontmatter = false, interactive = true, expandCollapsed = false, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }) {
2847
- const content = useMemo(() => {
2848
- const doc = markdownToDoc(markdown, { frontmatter });
2849
- const referenceIndex = collectReferenceDefinitions(doc);
2850
- const context = {
2851
- interactive,
2852
- expandCollapsed,
2853
- resolveImageUrl,
2854
- resolveFileLink,
2855
- resolveWikiEmbed,
2856
- resolveWikilink,
2857
- resolveFileInfo,
2858
- onWikilinkClick: interactive ? onWikilinkClick : void 0,
2859
- onLinkClick: interactive ? onLinkClick : void 0,
2860
- onImageClick: interactive ? onImageClick : void 0,
2861
- onFileClick: interactive ? onFileClick : void 0,
2862
- onTaskClick: interactive ? onTaskClick : void 0,
2863
- referenceDefinitions: referenceIndex.definitions,
2864
- referenceDefinitionNodes: referenceIndex.nodes,
2865
- taskCounter: { value: 0 },
2866
- keyCounter: { value: 0 }
2867
- };
2868
- return doc.content.content.map((node) => renderBlock(node, context));
2869
- }, [
2870
- markdown,
2871
- frontmatter,
2900
+ const context = useMemo(() => ({
2901
+ interactive,
2902
+ expandCollapsed,
2903
+ resolveImageUrl,
2904
+ resolveFileLink,
2905
+ resolveWikiEmbed,
2906
+ resolveWikilink,
2907
+ resolveFileInfo,
2908
+ onWikilinkClick: interactive ? onWikilinkClick : void 0,
2909
+ onLinkClick: interactive ? onLinkClick : void 0,
2910
+ onImageClick: interactive ? onImageClick : void 0,
2911
+ onFileClick: interactive ? onFileClick : void 0,
2912
+ onTaskClick: interactive ? onTaskClick : void 0
2913
+ }), [
2872
2914
  interactive,
2873
2915
  expandCollapsed,
2874
2916
  resolveImageUrl,
@@ -2882,10 +2924,25 @@ function MarkdownView({ markdown, markMode = "hide", frontmatter = false, intera
2882
2924
  onFileClick,
2883
2925
  onTaskClick
2884
2926
  ]);
2927
+ const { blocks, referenceDefinitions, definitionsKey } = useMemo(() => {
2928
+ const doc = markdownToDoc(markdown, { frontmatter });
2929
+ const referenceDefinitions = collectReferenceDefinitions(doc).definitions;
2930
+ return {
2931
+ blocks: splitBlocks(doc),
2932
+ referenceDefinitions,
2933
+ definitionsKey: definitionsSignature(referenceDefinitions)
2934
+ };
2935
+ }, [markdown, frontmatter]);
2885
2936
  return /* @__PURE__ */ jsx("div", {
2886
2937
  className: clsx("ProseMirror", "meowdown-content", className),
2887
2938
  "data-mark-mode": markMode,
2888
- children: content
2939
+ children: blocks.map(({ node, taskBase }, index) => /* @__PURE__ */ jsx(MarkdownBlock, {
2940
+ node,
2941
+ taskBase,
2942
+ context,
2943
+ referenceDefinitions,
2944
+ definitionsKey
2945
+ }, index))
2889
2946
  });
2890
2947
  }
2891
2948
 
package/dist/style.css CHANGED
@@ -533,28 +533,44 @@
533
533
 
534
534
  & > span {
535
535
  color: var(--meowdown-muted);
536
- font-size: .75rem;
537
- font-weight: 600;
536
+ font-size: .8125rem;
537
+ font-weight: 500;
538
538
  }
539
539
  }
540
540
 
541
541
  .meow_Input_IjcqKG {
542
542
  box-sizing: border-box;
543
543
  width: 100%;
544
+ height: 2rem;
544
545
  font: inherit;
545
546
  color: var(--meowdown-text);
546
- border: 1px solid var(--meowdown-border);
547
+ border: 1px solid var(--meowdown-input-border);
547
548
  background: none;
548
549
  border-radius: .5rem;
549
- padding: .5rem .625rem;
550
+ padding: 0 .625rem;
551
+ transition: border-color .1s, box-shadow .1s;
552
+
553
+ &::placeholder {
554
+ color: var(--meowdown-placeholder);
555
+ }
550
556
 
551
557
  &:focus {
552
- outline: 2px solid var(--meowdown-accent);
553
- outline-offset: -1px;
558
+ border-color: var(--meowdown-focus-ring);
559
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
560
+ outline: none;
561
+ }
562
+
563
+ @media (pointer: coarse) {
564
+ height: 2.75rem;
565
+ }
566
+
567
+ @media (prefers-reduced-motion: reduce) {
568
+ transition: none;
554
569
  }
555
570
  }
556
571
 
557
572
  .meow_FormActions_IjcqKG {
573
+ justify-content: flex-end;
558
574
  align-items: center;
559
575
  gap: .375rem;
560
576
  padding-top: .125rem;
@@ -569,19 +585,64 @@
569
585
  border: 0;
570
586
  border-radius: .5rem;
571
587
  padding: .375rem .625rem;
588
+ font-weight: 500;
589
+ transition: background-color .1s;
590
+
591
+ &:focus-visible {
592
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
593
+ outline: none;
594
+ }
572
595
 
573
596
  @media (pointer: coarse) {
574
597
  min-height: 2.75rem;
575
598
  }
599
+
600
+ @media (prefers-reduced-motion: reduce) {
601
+ transition: none;
602
+ }
576
603
  }
577
604
 
578
605
  .meow_RemoveButton_IjcqKG {
579
- color: var(--meowdown-danger, #c33);
606
+ color: var(--meowdown-danger);
607
+ margin-right: auto;
608
+
609
+ &:hover {
610
+ background: color-mix(in oklab, var(--meowdown-danger) 10%, transparent);
611
+ }
580
612
  }
581
613
 
582
614
  .meow_UseTitleButton_IjcqKG {
583
615
  color: var(--meowdown-accent);
584
- margin-left: auto;
616
+
617
+ &:hover {
618
+ background: var(--meowdown-popover-hover-bg);
619
+ }
620
+ }
621
+
622
+ .meow_SaveButton_IjcqKG {
623
+ transition: background-color .1s, transform .1s ease-out;
624
+
625
+ &:hover:not(:disabled) {
626
+ background: color-mix(in oklab, var(--meowdown-accent) 80%, transparent);
627
+ }
628
+
629
+ &:active:not(:disabled) {
630
+ transform: translateY(1px);
631
+ }
632
+
633
+ &:focus-visible {
634
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
635
+ outline: none;
636
+ }
637
+
638
+ &:disabled {
639
+ opacity: .5;
640
+ cursor: default;
641
+ }
642
+
643
+ @media (prefers-reduced-motion: reduce) {
644
+ transition: none;
645
+ }
585
646
  }
586
647
  .meow_Positioner_DPJ5ia {
587
648
  z-index: 50;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.68.2",
4
+ "version": "0.69.0",
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.7.0",
25
- "@ocavue/utils": "^1.7.0",
25
+ "@ocavue/utils": "^1.8.0",
26
26
  "@prosekit/core": "^0.13.2",
27
27
  "@prosekit/pm": "^0.1.19",
28
28
  "@prosekit/react": "^0.8.2",
@@ -31,7 +31,7 @@
31
31
  "github-slugger": "^2.0.0",
32
32
  "lucide-react": "^1.38.0",
33
33
  "react-property": "^2.0.2",
34
- "@meowdown/core": "0.68.2"
34
+ "@meowdown/core": "0.69.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^19.0.0",
@@ -49,12 +49,12 @@
49
49
  "@css-modules-kit/codegen": "^1.4.0",
50
50
  "@ocavue/tsconfig": "^0.7.1",
51
51
  "@tsdown/css": "^0.23.0-rc.1",
52
- "@types/react": "^19.2.18",
53
- "@types/react-dom": "^19.2.5",
52
+ "@types/react": "^19.3.0",
53
+ "@types/react-dom": "^19.3.0",
54
54
  "@vitest/browser-playwright": "^5.0.0",
55
55
  "dedent": "^1.7.2",
56
- "react": "^19.2.8",
57
- "react-dom": "^19.2.8",
56
+ "react": "^19.3.0",
57
+ "react-dom": "^19.3.0",
58
58
  "tsdown": "^0.23.0-rc.1",
59
59
  "vitest": "^5.0.0",
60
60
  "vitest-browser-commands": "^0.4.1",