@meowdown/react 0.68.3 → 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
 
@@ -1925,7 +1925,7 @@ function findHeadingPosition(doc, fragment) {
1925
1925
  });
1926
1926
  return match;
1927
1927
  }
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, 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 }) {
1929
1929
  const [editor] = useState(() => {
1930
1930
  const baseExtension = defineEditorExtension({
1931
1931
  resolveFileLink,
@@ -1933,7 +1933,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1933
1933
  resolveWikilink,
1934
1934
  markMode
1935
1935
  });
1936
- const extension = union(baseExtension, defineCodeBlockView());
1936
+ const extension = CodeBlockView === false ? baseExtension : union(baseExtension, defineCodeBlockView(CodeBlockView));
1937
1937
  const editor = createEditor({ extension });
1938
1938
  if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
1939
1939
  nodes: editor.nodes,
@@ -2150,7 +2150,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
2150
2150
  *
2151
2151
  * Callbacks and resolvers should be stable; pass them via `useCallback`.
2152
2152
  */
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, 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 }) {
2154
2154
  const childRef = useRef(null);
2155
2155
  useImperativeHandle(handleRef, () => {
2156
2156
  function getMarkdown() {
@@ -2286,6 +2286,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2286
2286
  onSearchChange,
2287
2287
  timeFormat,
2288
2288
  editorClassName,
2289
+ CodeBlockView,
2289
2290
  children
2290
2291
  })
2291
2292
  });
@@ -2766,10 +2767,7 @@ function renderInline(node, context) {
2766
2767
  resolveFileLink: context.resolveFileLink,
2767
2768
  resolveWikiEmbed: context.resolveWikiEmbed,
2768
2769
  resolveWikilink: context.resolveWikilink
2769
- }, {
2770
- referenceDefinitions: context.referenceDefinitions,
2771
- isReferenceDefinition: context.referenceDefinitionNodes.has(node)
2772
- }).map(([from, to, marks]) => ({
2770
+ }, { referenceDefinitions: context.referenceDefinitions }).map(([from, to, marks]) => ({
2773
2771
  text: text.slice(from, to),
2774
2772
  marks: Mark.setFrom(marks)
2775
2773
  })), 0, context);
@@ -2814,8 +2812,8 @@ function renderCodeBlock(node, key) {
2814
2812
  language
2815
2813
  }, key);
2816
2814
  }
2817
- function renderBlock(node, context) {
2818
- if (context.referenceDefinitionNodes.has(node)) return null;
2815
+ function renderBlock(node, context, parent, index) {
2816
+ if (isReferenceDefinitionNode(node, parent, index)) return null;
2819
2817
  const key = context.keyCounter.value++;
2820
2818
  const typeName = node.type.name;
2821
2819
  let handleTaskClick;
@@ -2829,11 +2827,64 @@ function renderBlock(node, context) {
2829
2827
  const inline = renderInline(node, context);
2830
2828
  return toDOM ? outputSpecToReact(toDOM(node), inline, context) : /* @__PURE__ */ jsx(Fragment, { children: inline }, key);
2831
2829
  }
2832
- 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
+ });
2833
2833
  const reactNode = toDOM ? outputSpecToReact(toDOM(node), children, context) : /* @__PURE__ */ jsx(Fragment, { children }, key);
2834
2834
  if (typeName === "list" && handleTaskClick && typeof reactNode !== "string" && reactNode != null) return cloneElement(reactNode, { onClick: handleTaskClick });
2835
2835
  return reactNode;
2836
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
+ });
2837
2888
  /**
2838
2889
  * Render Markdown to a read-only React tree that looks exactly like the editor
2839
2890
  * in `hide` mark mode: inline marks, wikilink chips, images, tweet/YouTube
@@ -2846,31 +2897,20 @@ function renderBlock(node, context) {
2846
2897
  * `useCallback` to avoid re-rendering the whole tree.
2847
2898
  */
2848
2899
  function MarkdownView({ markdown, markMode = "hide", frontmatter = false, interactive = true, expandCollapsed = false, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }) {
2849
- const content = useMemo(() => {
2850
- const doc = markdownToDoc(markdown, { frontmatter });
2851
- const referenceIndex = collectReferenceDefinitions(doc);
2852
- const context = {
2853
- interactive,
2854
- expandCollapsed,
2855
- resolveImageUrl,
2856
- resolveFileLink,
2857
- resolveWikiEmbed,
2858
- resolveWikilink,
2859
- resolveFileInfo,
2860
- onWikilinkClick: interactive ? onWikilinkClick : void 0,
2861
- onLinkClick: interactive ? onLinkClick : void 0,
2862
- onImageClick: interactive ? onImageClick : void 0,
2863
- onFileClick: interactive ? onFileClick : void 0,
2864
- onTaskClick: interactive ? onTaskClick : void 0,
2865
- referenceDefinitions: referenceIndex.definitions,
2866
- referenceDefinitionNodes: referenceIndex.nodes,
2867
- taskCounter: { value: 0 },
2868
- keyCounter: { value: 0 }
2869
- };
2870
- return doc.content.content.map((node) => renderBlock(node, context));
2871
- }, [
2872
- markdown,
2873
- 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
+ }), [
2874
2914
  interactive,
2875
2915
  expandCollapsed,
2876
2916
  resolveImageUrl,
@@ -2884,10 +2924,25 @@ function MarkdownView({ markdown, markMode = "hide", frontmatter = false, intera
2884
2924
  onFileClick,
2885
2925
  onTaskClick
2886
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]);
2887
2936
  return /* @__PURE__ */ jsx("div", {
2888
2937
  className: clsx("ProseMirror", "meowdown-content", className),
2889
2938
  "data-mark-mode": markMode,
2890
- children: content
2939
+ children: blocks.map(({ node, taskBase }, index) => /* @__PURE__ */ jsx(MarkdownBlock, {
2940
+ node,
2941
+ taskBase,
2942
+ context,
2943
+ referenceDefinitions,
2944
+ definitionsKey
2945
+ }, index))
2891
2946
  });
2892
2947
  }
2893
2948
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.68.3",
4
+ "version": "0.69.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -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.3"
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",