@meowdown/react 0.73.1 → 0.75.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ComponentProps, ReactElement, ReactNode, Ref } from "react";
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, XPostMediaClickHandler, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoClickHandler, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
2
+ import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, ImageUrlResolver, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver, XPostMediaClickHandler, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoClickHandler, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
4
  import { ReactNodeViewComponent, ReactNodeViewComponent as ReactNodeViewComponent$1, useEditor, useExtension, useKeymap } from "@prosekit/react";
5
5
  //#region src/utils/date-format.d.ts
@@ -122,7 +122,7 @@ interface EditorHandle {
122
122
  * Escape hatch: the underlying ProseKit editor, or `undefined` when the
123
123
  * handle does not wrap one.
124
124
  */
125
- readonly editor: TypedEditor | undefined;
125
+ getEditor: () => TypedEditor | undefined;
126
126
  }
127
127
  /**
128
128
  * One row of host items in the slash menu. The host ranks the rows; the menu does not re-sort.
@@ -372,8 +372,10 @@ interface EditorProps {
372
372
  onExitBoundary?: ExitBoundaryHandler;
373
373
  /**
374
374
  * Maps an image `src` to a displayable URL, or `undefined` to skip that image.
375
- * Defaults to showing http(s) URLs as-is. Pass a stable function (e.g. from
376
- * `useCallback`).
375
+ * May return a `Promise` when the answer depends on data that has not loaded
376
+ * yet; the image appears once it resolves, in a box of its persisted size
377
+ * when one exists. Defaults to showing http(s) URLs as-is. Pass a stable
378
+ * function (e.g. from `useCallback`).
377
379
  */
378
380
  resolveImageUrl?: ImageOptions['resolveImageUrl'];
379
381
  /**
@@ -631,9 +633,11 @@ interface MarkdownViewProps {
631
633
  */
632
634
  expandCollapsed?: boolean;
633
635
  /**
634
- * Map an image `src` to a displayable URL, or `undefined` to skip it.
636
+ * Map an image `src` to a displayable URL, or `undefined` to skip it. May
637
+ * return a `Promise`; the image appears once it resolves, in a box of its
638
+ * persisted size when both dimensions are known.
635
639
  */
636
- resolveImageUrl?: (src: string) => string | undefined;
640
+ resolveImageUrl?: ImageUrlResolver;
637
641
  /**
638
642
  * Claim a `[label](url)` link as a file pill instead of a regular link.
639
643
  * Must be pure; return `false` for links that should render normally.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { clsx } from "clsx/lite";
2
- import { Fragment, ViewTransition, cloneElement, createElement, memo, startTransition, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
- import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineCodeBlockPreviewPlugin, defineEditorExtension, defineLinkEditKeymap, defineLinkHoverHandler, definePendingReplacementHandler, defineSearchStatusHandler, defineVirtualCaret, defineWikilinkHoverHandler, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, parsePostEmbedSnapshot, renderMathInto, updateEditorConfig } from "@meowdown/core";
2
+ import { Fragment, ViewTransition, cloneElement, createElement, memo, startTransition, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
+ import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineCodeBlockPreviewPlugin, defineEditorExtension, defineLinkEditKeymap, defineLinkHoverHandler, definePendingReplacementHandler, defineSearchStatusHandler, defineVirtualCaret, defineWikilinkHoverHandler, dismissLinkHover, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, parsePostEmbedSnapshot, renderMathInto, updateEditorConfig } from "@meowdown/core";
4
4
  import { clamp } from "@ocavue/utils";
5
5
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
6
6
  import { Selection, TextSelection } from "@prosekit/pm/state";
@@ -12,6 +12,7 @@ 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";
14
14
  import { Popover } from "@base-ui/react/popover";
15
+ import { queueExtension } from "@prosekit/web";
15
16
  import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from "@prosekit/react/autocomplete";
16
17
  import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
17
18
  import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
@@ -422,6 +423,44 @@ function EditorExtensions({ config, searchQuery, onDocChange, onSearchChange })
422
423
  return null;
423
424
  }
424
425
 
426
+ //#endregion
427
+ //#region src/components/link-menu-state.ts
428
+ const LINK_MENU_IDLE = { kind: "idle" };
429
+ /**
430
+ * What the popup renders: the open view, or the closing one during the exit
431
+ * animation.
432
+ */
433
+ function getLinkMenuView(state) {
434
+ switch (state.kind) {
435
+ case "idle": return;
436
+ case "closing": return state.view;
437
+ default: return state;
438
+ }
439
+ }
440
+ function reduceLinkMenu(state, event) {
441
+ switch (event.type) {
442
+ case "hover":
443
+ if (state.kind === "edit") return state;
444
+ if (event.link) return {
445
+ kind: "preview",
446
+ link: event.link
447
+ };
448
+ return state.kind === "preview" ? {
449
+ kind: "closing",
450
+ view: state
451
+ } : state;
452
+ case "edit": return {
453
+ kind: "edit",
454
+ edit: event.edit
455
+ };
456
+ case "close": return state.kind === "preview" || state.kind === "edit" ? {
457
+ kind: "closing",
458
+ view: state
459
+ } : state;
460
+ case "closed": return state.kind === "closing" ? LINK_MENU_IDLE : state;
461
+ }
462
+ }
463
+
425
464
  //#endregion
426
465
  //#region src/components/link-menu.module.css
427
466
  var link_menu_module_default = {
@@ -735,41 +774,39 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
735
774
  }
736
775
  function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = false }) {
737
776
  const editor = useEditor$1();
738
- const [infoOpen, setInfoOpen] = useState(false);
739
- const [editOpen, setEditOpen] = useState(false);
740
- const [link, setLink] = useState();
741
- const [edit, setEdit] = useState();
777
+ const [state, dispatch] = useReducer(reduceLinkMenu, LINK_MENU_IDLE);
742
778
  const isPointerOverPopupRef = useRef(false);
743
- const linkHoverExtension = useMemo(() => {
744
- return defineLinkHoverHandler((nextHit) => {
745
- setInfoOpen(nextHit != null);
746
- if (nextHit) setLink(nextHit.payload);
747
- }, { canLeave: () => !isPointerOverPopupRef.current });
748
- }, []);
749
- useExtension$1(linkHoverExtension);
750
- const linkEditExtension = useMemo(() => {
751
- return readOnly ? null : defineLinkEditKeymap((options) => {
752
- setEdit(options);
753
- setEditOpen(true);
754
- setInfoOpen(false);
755
- });
756
- }, [readOnly]);
757
- useExtension$1(linkEditExtension);
779
+ useEffect(() => {
780
+ const canLeave = () => !isPointerOverPopupRef.current;
781
+ const linkHoverExtension = defineLinkHoverHandler((hit) => dispatch({
782
+ type: "hover",
783
+ link: hit?.payload
784
+ }), { canLeave });
785
+ return queueExtension(editor, linkHoverExtension);
786
+ }, [editor]);
787
+ useEffect(() => {
788
+ if (readOnly) return;
789
+ const linkEditExtension = defineLinkEditKeymap((edit) => dispatch({
790
+ type: "edit",
791
+ edit
792
+ }));
793
+ return queueExtension(editor, linkEditExtension);
794
+ }, [editor, readOnly]);
758
795
  const handlePointerHover = useCallback((over) => {
759
796
  isPointerOverPopupRef.current = over;
760
797
  }, []);
761
- const closeInfo = useCallback(() => {
798
+ const close = useCallback(() => {
762
799
  isPointerOverPopupRef.current = false;
763
- setInfoOpen(false);
764
- }, []);
765
- const closeEdit = useCallback(() => {
766
- setEditOpen(false);
767
- closeInfo();
768
- }, [closeInfo]);
800
+ dismissLinkHover(editor.state);
801
+ dispatch({ type: "close" });
802
+ }, [editor]);
803
+ const view = getLinkMenuView(state);
804
+ const edit = view?.kind === "edit" ? view.edit : void 0;
805
+ const link = view?.kind === "preview" ? view.link : void 0;
769
806
  const handleEditRemove = useCallback(() => {
770
807
  editor.commands.removeLink();
771
- closeEdit();
772
- }, [editor, closeEdit]);
808
+ close();
809
+ }, [editor, close]);
773
810
  const handleEditSubmit = useCallback((text, href) => {
774
811
  if (!edit) return;
775
812
  if (edit.link) editor.commands.updateLink({
@@ -781,52 +818,52 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
781
818
  text,
782
819
  href
783
820
  });
784
- closeEdit();
821
+ close();
785
822
  }, [
786
823
  edit,
787
824
  editor,
788
- closeEdit
825
+ close
789
826
  ]);
790
827
  const mutable = link != null && !readOnly && link.form !== "reference";
791
828
  const linkText = useMemo(() => link ? getLinkText(link) : "", [link]);
792
829
  const editLink = useCallback(() => {
793
830
  if (!link) return;
794
831
  selectLinkUnit(editor, link);
795
- setEdit({
796
- from: link.unit.from,
797
- to: link.unit.to,
798
- link,
799
- text: linkText
832
+ dispatch({
833
+ type: "edit",
834
+ edit: {
835
+ from: link.unit.from,
836
+ to: link.unit.to,
837
+ link,
838
+ text: linkText
839
+ }
800
840
  });
801
- setEditOpen(true);
802
- closeInfo();
803
841
  }, [
804
842
  link,
805
843
  editor,
806
- linkText,
807
- closeInfo
844
+ linkText
808
845
  ]);
809
846
  const removeLink = useCallback(() => {
810
847
  if (!link) return;
811
848
  selectLinkUnit(editor, link);
812
849
  editor.commands.removeLink();
813
- closeInfo();
850
+ close();
814
851
  }, [
815
852
  link,
816
853
  editor,
817
- closeInfo
854
+ close
818
855
  ]);
819
856
  const handleUseTitle = useMemo(() => {
820
857
  if (!link || !mutable || !isLinkTextForHref(linkText, link.href)) return;
821
858
  return (title) => {
822
859
  selectLinkUnit(editor, link);
823
860
  editor.commands.updateLink({ text: title });
824
- closeInfo();
861
+ close();
825
862
  };
826
863
  }, [
827
864
  link,
828
865
  editor,
829
- closeInfo,
866
+ close,
830
867
  linkText,
831
868
  mutable
832
869
  ]);
@@ -836,24 +873,21 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
836
873
  if (!range) return;
837
874
  return getVirtualElementFromRange(editor.view, range);
838
875
  }, [range, editor]);
839
- const editing = edit != null && !readOnly;
840
876
  return /* @__PURE__ */ jsx(LinkPopover, {
841
877
  anchor,
842
- open: editing ? editOpen : infoOpen,
843
- onClose: editing ? closeEdit : closeInfo,
878
+ open: state.kind === "preview" || state.kind === "edit",
879
+ onClose: close,
844
880
  onCloseComplete: () => {
845
- if (editing) {
846
- setEdit(void 0);
847
- editor.focus();
848
- } else if (!infoOpen) setLink(void 0);
881
+ if (edit) editor.focus();
882
+ dispatch({ type: "closed" });
849
883
  },
850
884
  onPopupHover: handlePointerHover,
851
- children: editing ? /* @__PURE__ */ jsx(LinkEditContent, {
885
+ children: edit ? /* @__PURE__ */ jsx(LinkEditContent, {
852
886
  edit,
853
887
  resolveLinkPreview,
854
888
  onRemove: edit.link ? handleEditRemove : void 0,
855
889
  onSubmit: handleEditSubmit
856
- }) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
890
+ }, `${edit.from}:${edit.to}`) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
857
891
  href: link.href,
858
892
  previewState,
859
893
  onLinkClick,
@@ -1706,6 +1740,7 @@ function TagMenu({ onTagSearch }) {
1706
1740
  className: autocomplete_menu_module_default.Popup,
1707
1741
  "data-testid": "tag-menu",
1708
1742
  children: [items.map((item) => /* @__PURE__ */ jsxs(AutocompleteItem, {
1743
+ value: item.tag,
1709
1744
  className: autocomplete_menu_module_default.Item,
1710
1745
  onSelect: () => {
1711
1746
  editor.commands.insertText({ text: `#${item.tag} ` });
@@ -1782,6 +1817,7 @@ function WikilinkMenu({ onWikilinkSearch }) {
1782
1817
  className: autocomplete_menu_module_default.Popup,
1783
1818
  "data-testid": "wikilink-menu",
1784
1819
  children: [items.map((item) => /* @__PURE__ */ jsxs(AutocompleteItem, {
1820
+ value: item.target,
1785
1821
  className: autocomplete_menu_module_default.Item,
1786
1822
  onSelect: () => {
1787
1823
  editor.commands.insertText({ text: `[[${item.target}]]` });
@@ -1970,8 +2006,10 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1970
2006
  suppressDocChangeRef.current = true;
1971
2007
  try {
1972
2008
  editor.view.dispatch(transaction);
1973
- } finally {
1974
2009
  suppressDocChangeRef.current = false;
2010
+ } catch (error) {
2011
+ suppressDocChangeRef.current = false;
2012
+ throw error;
1975
2013
  }
1976
2014
  }
1977
2015
  function setState(markdown, selection) {
@@ -2048,7 +2086,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2048
2086
  discardPendingReplacement,
2049
2087
  findNext,
2050
2088
  findPrevious,
2051
- editor
2089
+ getEditor: () => editor
2052
2090
  };
2053
2091
  }, [
2054
2092
  editor,
@@ -2056,11 +2094,14 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2056
2094
  hasSelectionMenu,
2057
2095
  openSelectionMenu
2058
2096
  ]);
2097
+ const mount = useCallback((element) => {
2098
+ return editor.mount(element);
2099
+ }, [editor]);
2059
2100
  return /* @__PURE__ */ jsxs(ProseKit, {
2060
2101
  editor,
2061
2102
  children: [
2062
2103
  /* @__PURE__ */ jsx(VirtualCaret, {}),
2063
- /* @__PURE__ */ jsx("div", { ref: editor.mount }),
2104
+ /* @__PURE__ */ jsx("div", { ref: mount }),
2064
2105
  /* @__PURE__ */ jsx(EditorExtensions, {
2065
2106
  config,
2066
2107
  searchQuery,
@@ -2081,7 +2122,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2081
2122
  onLinkCopy,
2082
2123
  resolveLinkPreview,
2083
2124
  readOnly
2084
- }),
2125
+ }, String(readOnly)),
2085
2126
  onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
2086
2127
  onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
2087
2128
  onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
@@ -2177,6 +2218,9 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2177
2218
  function findPrevious() {
2178
2219
  childRef.current?.findPrevious();
2179
2220
  }
2221
+ function getEditor() {
2222
+ return childRef.current?.getEditor();
2223
+ }
2180
2224
  return {
2181
2225
  getMarkdown,
2182
2226
  setMarkdown,
@@ -2197,9 +2241,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2197
2241
  discardPendingReplacement,
2198
2242
  findNext,
2199
2243
  findPrevious,
2200
- get editor() {
2201
- return childRef.current?.editor;
2202
- }
2244
+ getEditor
2203
2245
  };
2204
2246
  }, []);
2205
2247
  return /* @__PURE__ */ jsx("div", {
@@ -2256,6 +2298,37 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2256
2298
  });
2257
2299
  }
2258
2300
 
2301
+ //#endregion
2302
+ //#region src/hooks/use-maybe-promise.ts
2303
+ /**
2304
+ * The settled value of `input`: `input` itself when it is not a `Promise`,
2305
+ * otherwise `undefined` until it resolves. A rejection stays `undefined` and is
2306
+ * reported with `console.warn`, since the caller has no other way to see it. A
2307
+ * new `input` discards the previous `Promise`'s result at once, so a stale
2308
+ * value never shows for a newer input. Callers memoize `input`, or a fresh
2309
+ * `Promise` on every render never settles.
2310
+ */
2311
+ function useMaybePromise(input) {
2312
+ const [settled, setSettled] = useState(null);
2313
+ useEffect(() => {
2314
+ if (!(input instanceof Promise)) return;
2315
+ let active = true;
2316
+ input.then((value) => {
2317
+ if (active) setSettled({
2318
+ input,
2319
+ value
2320
+ });
2321
+ }, (error) => {
2322
+ if (active) console.warn("[meowdown] useMaybePromise input rejected:", error);
2323
+ });
2324
+ return () => {
2325
+ active = false;
2326
+ };
2327
+ }, [input]);
2328
+ if (!(input instanceof Promise)) return input;
2329
+ return settled?.input === input ? settled.value : void 0;
2330
+ }
2331
+
2259
2332
  //#endregion
2260
2333
  //#region src/components/attributes-to-props.ts
2261
2334
  const UNCONTROLLED_COMPONENT_ATTRIBUTES = ["checked", "value"];
@@ -2412,8 +2485,14 @@ function PostEmbed(props) {
2412
2485
  });
2413
2486
  }
2414
2487
  function ImagePreview(props) {
2415
- const { src, alt, width, snapshot, resolveImageUrl, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onImageClick, interactive } = props;
2488
+ const { src, alt, width, height, snapshot, resolveImageUrl, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onImageClick, interactive } = props;
2416
2489
  const kind = matchEmbed(src);
2490
+ const resolved = useMemo(() => kind ? void 0 : (resolveImageUrl ?? defaultResolveImageUrl)(src), [
2491
+ kind,
2492
+ resolveImageUrl,
2493
+ src
2494
+ ]);
2495
+ const url = useMaybePromise(resolved);
2417
2496
  if (kind) {
2418
2497
  if (!interactive) return null;
2419
2498
  return /* @__PURE__ */ jsx(PostEmbed, {
@@ -2426,8 +2505,23 @@ function ImagePreview(props) {
2426
2505
  resolveYouTubeVideo: resolveYouTubeVideo ?? defaultResolveYouTubeVideo
2427
2506
  }, src);
2428
2507
  }
2429
- const url = (resolveImageUrl ?? defaultResolveImageUrl)(src);
2430
- if (!url) return null;
2508
+ if (!url) {
2509
+ if (resolved instanceof Promise && width != null && height != null) return /* @__PURE__ */ jsx("span", {
2510
+ className: "md-image-view-preview md-atom-view-preview",
2511
+ "data-testid": "image-preview",
2512
+ contentEditable: false,
2513
+ children: /* @__PURE__ */ jsx("span", {
2514
+ className: "md-image-resizable",
2515
+ "data-testid": "image-resizable",
2516
+ "data-loading": "",
2517
+ style: {
2518
+ width: `${width}px`,
2519
+ height: `${height}px`
2520
+ }
2521
+ })
2522
+ });
2523
+ return null;
2524
+ }
2431
2525
  return /* @__PURE__ */ jsx("span", {
2432
2526
  className: "md-image-view-preview md-atom-view-preview",
2433
2527
  "data-testid": "image-preview",
@@ -2450,13 +2544,14 @@ function ImagePreview(props) {
2450
2544
  });
2451
2545
  }
2452
2546
  function ImageView(props) {
2453
- const { src, alt, width, snapshot, context, children } = props;
2547
+ const { src, alt, width, height, snapshot, context, children } = props;
2454
2548
  return /* @__PURE__ */ jsxs("span", {
2455
2549
  className: "md-image-view md-atom-view",
2456
2550
  children: [/* @__PURE__ */ jsx(ImagePreview, {
2457
2551
  src,
2458
2552
  alt,
2459
2553
  width,
2554
+ height,
2460
2555
  snapshot,
2461
2556
  resolveImageUrl: context.resolveImageUrl,
2462
2557
  resolveXPost: context.resolveXPost,
@@ -2558,27 +2653,8 @@ function renderTokens(code, tokens) {
2558
2653
  return out;
2559
2654
  }
2560
2655
  function CodeBlock({ code, language }) {
2561
- const syncTokens = useMemo(() => {
2562
- const result = getCodeTokens(code, language);
2563
- return Array.isArray(result) ? result : null;
2564
- }, [code, language]);
2565
- const [asyncTokens, setAsyncTokens] = useState(null);
2566
- useEffect(() => {
2567
- if (syncTokens) return;
2568
- let active = true;
2569
- const result = getCodeTokens(code, language);
2570
- if (!Array.isArray(result)) result.then((loaded) => {
2571
- if (active) setAsyncTokens(loaded);
2572
- });
2573
- return () => {
2574
- active = false;
2575
- };
2576
- }, [
2577
- code,
2578
- language,
2579
- syncTokens
2580
- ]);
2581
- const tokens = syncTokens ?? asyncTokens ?? [];
2656
+ const result = useMemo(() => getCodeTokens(code, language), [code, language]);
2657
+ const tokens = useMaybePromise(result) ?? [];
2582
2658
  return /* @__PURE__ */ jsx("pre", {
2583
2659
  "data-language": language || void 0,
2584
2660
  children: /* @__PURE__ */ jsx("code", { children: tokens.length > 0 ? renderTokens(code, tokens) : code })
@@ -2657,6 +2733,7 @@ function wrapMark(mark, children, context) {
2657
2733
  src: attrs.src,
2658
2734
  alt: attrs.alt,
2659
2735
  width: attrs.width,
2736
+ height: attrs.height,
2660
2737
  snapshot: attrs.snapshot,
2661
2738
  context,
2662
2739
  children
package/dist/style.css CHANGED
@@ -319,7 +319,9 @@
319
319
  }
320
320
 
321
321
  @media (prefers-reduced-motion: reduce) {
322
- transition: none;
322
+ &, &[data-closed] {
323
+ transition: none;
324
+ }
323
325
  }
324
326
  }
325
327
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.73.1",
4
+ "version": "0.75.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.8.0",
25
- "@meowdown/core": "0.74.1",
25
+ "@meowdown/core": "0.75.0",
26
26
  "@meowdown/embed": "0.2.0",
27
27
  "@meowdown/markdown": "0.72.0",
28
28
  "@ocavue/utils": "^1.8.0",
@@ -30,6 +30,7 @@
30
30
  "@prosekit/core": "^0.13.3",
31
31
  "@prosekit/pm": "^0.1.20",
32
32
  "@prosekit/react": "^0.8.3",
33
+ "@prosekit/web": "^0.9.3",
33
34
  "beautiful-mermaid": "^1.1.3",
34
35
  "clsx": "^2.1.1",
35
36
  "github-slugger": "^2.0.0",