@owomark/react 0.1.4 → 0.1.6

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
@@ -135,6 +135,8 @@ For hosts that still need `onChange` callbacks (e.g. for local draft persistence
135
135
  />
136
136
  ```
137
137
 
138
+ When `OwoMarkPreview` runs in `strategy="mdx"`, syntax detection may use internal probe-only masking. The MDX compile path may also apply a private compatibility normalization for indented code blocks, but compile errors and `data-source-line-*` anchors are remapped back to the original editor lines.
139
+
138
140
  ### Custom Block Renderer
139
141
 
140
142
  Provide a host-side rendering function for full Markdown fidelity (GFM, math, syntax highlighting):
@@ -268,7 +270,55 @@ Reference form:
268
270
  ### Minimal (use package presets)
269
271
 
270
272
  ```ts
271
- import '@owomark/view/style.css'; // includes mdx-components.css + owomark.css + side-annotation.css + slash-menu.css + light.css + dark.css
273
+ import '@owomark/view/style.css'; // includes owomark.css + side-annotation.css + slash-menu.css + light.css + dark.css
274
+ import '@owomark/processor/mdx-components.css'; // built-in MDX component styles
275
+ ```
276
+
277
+ Built-in MDX components currently include `Callout`, `Note`, `CodeDemo`, `Steps`, `Tabs`, `FileTree`, and `Kbd`.
278
+
279
+ Use `Note` for supplemental explanation, editor notes, and neutral guidance. Keep `Callout` for stronger feedback semantics such as `info`, `warn`, `error`, or `success`.
280
+
281
+ | Component | Use for | Example |
282
+ |---|---|---|
283
+ | `Callout` | Strong feedback blocks with explicit severity | `<Callout type="warn">Read this first.</Callout>` |
284
+ | `Note` | Neutral explanation, editor notes, low-emphasis guidance | `<Note title="Editor note">Background context.</Note>` |
285
+ | `CodeDemo` | Code sample + optional title wrapper | `<CodeDemo title="demo" code={"const x = 1;"} />` |
286
+ | `Steps` + `Step` | Ordered task or tutorial flows | `<Steps><Step title="Install">...</Step></Steps>` |
287
+ | `Tabs` + `Tab` | Small tabbed content groups | `<Tabs><Tab label="React">...</Tab></Tabs>` |
288
+ | `FileTree` | File/folder structure snippets | `<FileTree>{"src/\\n app.ts"}</FileTree>` |
289
+ | `Kbd` | Keyboard keycaps in inline prose | `Press <Kbd>Cmd</Kbd> + <Kbd>K</Kbd>` |
290
+
291
+ Complete example matrix:
292
+
293
+ ```mdx
294
+ <Note title="Editor note">
295
+ This paragraph is background context, not a warning or status message.
296
+ </Note>
297
+
298
+ <Callout type="info">
299
+ MDX components ship in the default registry.
300
+ </Callout>
301
+
302
+ <CodeDemo title="Minimal example" code={`const x = 1;`} />
303
+
304
+ <Steps>
305
+ <Step title="Install">Add the package.</Step>
306
+ <Step title="Render">Mount the preview.</Step>
307
+ </Steps>
308
+
309
+ <Tabs>
310
+ <Tab label="React">Use the React bindings.</Tab>
311
+ <Tab label="Core">Use the framework-agnostic core.</Tab>
312
+ </Tabs>
313
+
314
+ <FileTree>
315
+ {`src/
316
+ app.tsx
317
+ routes/
318
+ mdx-preview.tsx`}
319
+ </FileTree>
320
+
321
+ Press <Kbd>Cmd</Kbd> + <Kbd>K</Kbd>.
272
322
  ```
273
323
 
274
324
  ### Selective imports
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ import * as react from 'react';
2
2
  import { Ref, MutableRefObject, CSSProperties, ReactNode, ComponentType } from 'react';
3
3
  import { IndentMode, OwoMarkSelection, OwoMarkCommands, OwoMarkCore, OwoMarkSharedStateController, OwoMarkSharedStateStore, PreviewBlock, CreateSharedStateOptions, OwoMarkSharedState, OwoMarkDocument, SlashState, BlockContext, BlockNode, VisibleRange, VirtualRow, Decorator } from '@owomark/core';
4
4
  export { BlockContext, BlockContextType, BlockInsertType, CommandDefinition, OwoMarkCommands, OwoMarkCore, OwoMarkEditorInstance, OwoMarkSelection, OwoMarkSharedState, OwoMarkSharedStateController, OwoMarkSharedStateStore, PreviewBlock, PreviewBlockKind, SlashState } from '@owomark/core';
5
- import { OwoMarkProcessorOptions, OwoMarkThemeName, PreviewStrategy, PreviewRendererRegistry, PreviewRenderContext } from '@owomark/view';
5
+ import { OwoMarkThemeName, PreviewStrategy, PreviewRendererRegistry, PreviewRenderContext } from '@owomark/view';
6
6
  export { OwoMarkThemeName, PreviewRenderContext, PreviewRenderResult, PreviewRendererDefinition, PreviewRendererRegistry, THEME_DARK_CLASS, THEME_LIGHT_CLASS, getThemeClassName } from '@owomark/view';
7
+ import { OwoMarkProcessorOptions, PluginDescriptor, PluginEntry, MdxComponentMap } from '@owomark/processor';
8
+ export { registerPlugin } from '@owomark/processor';
7
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
8
10
 
9
11
  type OwoMarkEditorConfig = {
@@ -56,6 +58,18 @@ type OwoMarkEditorProps = {
56
58
  };
57
59
  declare const OwoMarkEditor: react.ForwardRefExoticComponent<OwoMarkEditorProps & react.RefAttributes<HTMLDivElement>>;
58
60
 
61
+ type OwoMarkMdxOptions = {
62
+ enableMath?: boolean;
63
+ enableSideAnnotation?: boolean;
64
+ enableCodeHighlight?: boolean;
65
+ sourceAnchors?: boolean;
66
+ extraRemarkDescriptors?: PluginDescriptor[];
67
+ extraRehypeDescriptors?: PluginDescriptor[];
68
+ extraRemarkPlugins?: PluginEntry[];
69
+ extraRehypePlugins?: PluginEntry[];
70
+ components?: MdxComponentMap;
71
+ };
72
+
59
73
  type OwoMarkPreviewProps = {
60
74
  state: OwoMarkSharedStateStore;
61
75
  className?: string;
@@ -74,6 +88,8 @@ type OwoMarkPreviewProps = {
74
88
  * the latest reference is always used via a stable ref.
75
89
  */
76
90
  renderBlock?: (block: PreviewBlock, context: PreviewRenderContext) => Promise<string>;
91
+ /** MDX runtime configuration used by `strategy="mdx"`. */
92
+ mdx?: OwoMarkMdxOptions;
77
93
  /** Called after each successful DOM update. Use to trigger scroll sync. */
78
94
  onContentUpdate?: () => void;
79
95
  };
@@ -210,38 +226,28 @@ declare function computeMenuPosition(caret: CaretRect, menuHeight: number, menuW
210
226
  };
211
227
 
212
228
  type MdxSkeletonProps = {
213
- /** Height in pixels for the skeleton placeholder. */
214
229
  height?: number;
215
- /** Number of shimmer lines to show. Default: 3. */
216
230
  lines?: number;
217
231
  className?: string;
218
232
  style?: CSSProperties;
219
233
  };
220
- /**
221
- * Lightweight skeleton loading indicator for MDX component placeholders.
222
- * Pure CSS animation — no external UI library dependency.
223
- */
224
234
  declare function MdxSkeleton({ height, lines, className, style }: MdxSkeletonProps): react_jsx_runtime.JSX.Element;
225
235
 
226
236
  type MdxComponentShellProps = {
227
- /** Component display name, used as cache key for height estimation. */
228
237
  name: string;
238
+ propsFingerprint?: string;
229
239
  children: ReactNode;
230
240
  };
231
- /**
232
- * Wraps an MDX component to provide:
233
- * 1. Skeleton placeholder with estimated height before first paint
234
- * 2. Real-height measurement after mount via ResizeObserver
235
- * 3. Height caching so subsequent renders of the same component type
236
- * use a better estimate, reducing layout shift.
237
- */
238
- declare function MdxComponentShell({ name, children }: MdxComponentShellProps): react_jsx_runtime.JSX.Element;
239
- /**
240
- * Create a wrapped version of an MDX component that renders inside
241
- * MdxComponentShell for skeleton loading + height management.
242
- */
241
+ declare function MdxComponentShell({ name, propsFingerprint, children }: MdxComponentShellProps): react_jsx_runtime.JSX.Element;
243
242
  declare function wrapWithShell<P extends Record<string, any>>(name: string, Component: ComponentType<P>): ComponentType<P>;
244
243
  /** Clear the height cache (useful for testing or theme changes). */
245
244
  declare function clearComponentHeightCache(): void;
246
245
 
247
- export { type CaretRect, DEFAULT_EDITOR_CONFIG, EditorBlock, type EditorBlockProps, EditorDecorator, type EditorDecoratorProps, EditorLeaf, type EditorLeafProps, MdxComponentShell, type MdxComponentShellProps, MdxSkeleton, type MdxSkeletonProps, OwoMarkEditor, type OwoMarkEditorConfig, type OwoMarkEditorProps, OwoMarkPreview, type OwoMarkPreviewProps, SlashMenu, type SlashMenuProps, type UseOwoMarkCoreOptions, type UseOwoMarkCoreReturn, type UseOwoMarkSharedStateOptions, type UseVirtualListOptions, type UseVirtualListResult, clearComponentHeightCache, computeMenuPosition, deriveProcessorOptions, getCaretRect, resolveEditorConfig, useBlockContext, useOwoMarkCore, useOwoMarkSharedState, useSharedStateSnapshot, useVirtualList, wrapWithShell };
246
+ type CancellableCompile = {
247
+ promise: Promise<string>;
248
+ cancel: () => void;
249
+ };
250
+ declare function acquireMdxWorker(): void;
251
+ declare function releaseMdxWorker(): void;
252
+
253
+ export { type CancellableCompile, type CaretRect, DEFAULT_EDITOR_CONFIG, EditorBlock, type EditorBlockProps, EditorDecorator, type EditorDecoratorProps, EditorLeaf, type EditorLeafProps, MdxComponentShell, type MdxComponentShellProps, MdxSkeleton, type MdxSkeletonProps, OwoMarkEditor, type OwoMarkEditorConfig, type OwoMarkEditorProps, type OwoMarkMdxOptions, OwoMarkPreview, type OwoMarkPreviewProps, SlashMenu, type SlashMenuProps, type UseOwoMarkCoreOptions, type UseOwoMarkCoreReturn, type UseOwoMarkSharedStateOptions, type UseVirtualListOptions, type UseVirtualListResult, acquireMdxWorker, clearComponentHeightCache, computeMenuPosition, deriveProcessorOptions, getCaretRect, releaseMdxWorker, resolveEditorConfig, useBlockContext, useOwoMarkCore, useOwoMarkSharedState, useSharedStateSnapshot, useVirtualList, wrapWithShell };
package/dist/index.js CHANGED
@@ -588,11 +588,754 @@ var OwoMarkEditor = forwardRef(
588
588
  );
589
589
 
590
590
  // src/OwoMarkPreview.tsx
591
- import { useRef as useRef4, useEffect as useEffect3 } from "react";
591
+ import { useRef as useRef8, useEffect as useEffect6 } from "react";
592
592
  import {
593
593
  createOwoMarkPreviewEngine
594
594
  } from "@owomark/view";
595
+
596
+ // src/MdxPreview.tsx
597
+ import {
598
+ Component,
599
+ useEffect as useEffect5,
600
+ useMemo as useMemo4,
601
+ useRef as useRef7,
602
+ useState as useState5,
603
+ useSyncExternalStore
604
+ } from "react";
605
+ import {
606
+ DEFAULT_MDX_COMPONENTS
607
+ } from "@owomark/processor";
608
+
609
+ // src/MdxSkeleton.tsx
610
+ import { useMemo as useMemo2, useRef as useRef4 } from "react";
611
+ import { createSkeletonHtml, ensureSkeletonStyles } from "@owomark/view";
595
612
  import { jsx as jsx6 } from "react/jsx-runtime";
613
+ function MdxSkeleton({ height, lines, className, style }) {
614
+ const injected = useRef4(false);
615
+ if (!injected.current && typeof document !== "undefined") {
616
+ ensureSkeletonStyles(document);
617
+ injected.current = true;
618
+ }
619
+ const html = useMemo2(
620
+ () => createSkeletonHtml({ height, lines: lines ?? (height == null ? 3 : void 0) }),
621
+ [height, lines]
622
+ );
623
+ return /* @__PURE__ */ jsx6(
624
+ "div",
625
+ {
626
+ className: className ?? void 0,
627
+ style,
628
+ dangerouslySetInnerHTML: { __html: html }
629
+ }
630
+ );
631
+ }
632
+
633
+ // src/MdxComponentShell.tsx
634
+ import {
635
+ useRef as useRef5,
636
+ useState as useState3,
637
+ useEffect as useEffect3,
638
+ useLayoutEffect as useLayoutEffect3,
639
+ useCallback as useCallback2
640
+ } from "react";
641
+ import { jsx as jsx7 } from "react/jsx-runtime";
642
+ var componentHeightCache = /* @__PURE__ */ new Map();
643
+ var DEFAULT_ESTIMATED_HEIGHT = 80;
644
+ function shallowPropsFingerprint(props) {
645
+ const keys = Object.keys(props).sort();
646
+ const parts = [];
647
+ for (const key of keys) {
648
+ const val = props[key];
649
+ if (typeof val === "function" || typeof val === "object") continue;
650
+ parts.push(`${key}=${String(val)}`);
651
+ }
652
+ return parts.join("&");
653
+ }
654
+ function MdxComponentShell({ name, propsFingerprint, children }) {
655
+ const containerRef = useRef5(null);
656
+ const [status, setStatus] = useState3("skeleton");
657
+ const cacheKey = propsFingerprint ? `${name}:${propsFingerprint}` : name;
658
+ const estimatedHeight = componentHeightCache.get(cacheKey) ?? DEFAULT_ESTIMATED_HEIGHT;
659
+ useEffect3(() => {
660
+ const id = requestAnimationFrame(() => setStatus("mounting"));
661
+ return () => cancelAnimationFrame(id);
662
+ }, []);
663
+ useLayoutEffect3(() => {
664
+ if (status !== "mounting") return;
665
+ const id = requestAnimationFrame(() => setStatus("ready"));
666
+ return () => cancelAnimationFrame(id);
667
+ }, [status]);
668
+ const measureHeight = useCallback2(() => {
669
+ const el = containerRef.current;
670
+ if (!el) return;
671
+ const h = el.getBoundingClientRect().height;
672
+ if (h > 0) {
673
+ componentHeightCache.set(cacheKey, h);
674
+ }
675
+ }, [cacheKey]);
676
+ useEffect3(() => {
677
+ const el = containerRef.current;
678
+ if (!el || status !== "ready") return;
679
+ measureHeight();
680
+ const ro = new ResizeObserver(() => measureHeight());
681
+ ro.observe(el);
682
+ return () => ro.disconnect();
683
+ }, [status, measureHeight]);
684
+ if (status === "skeleton") {
685
+ return /* @__PURE__ */ jsx7(MdxSkeleton, { height: estimatedHeight });
686
+ }
687
+ return /* @__PURE__ */ jsx7("div", { ref: containerRef, children });
688
+ }
689
+ function wrapWithShell(name, Component2) {
690
+ function ShellWrapped(props) {
691
+ const fingerprint = shallowPropsFingerprint(props);
692
+ return /* @__PURE__ */ jsx7(MdxComponentShell, { name, propsFingerprint: fingerprint, children: /* @__PURE__ */ jsx7(Component2, { ...props }) });
693
+ }
694
+ ShellWrapped.displayName = `MdxShell(${name})`;
695
+ return ShellWrapped;
696
+ }
697
+ function clearComponentHeightCache() {
698
+ componentHeightCache.clear();
699
+ }
700
+
701
+ // src/mdx-error.ts
702
+ function parseMdxErrorDetails(error) {
703
+ if (error && typeof error === "object") {
704
+ const value = error;
705
+ if (typeof value.line === "number") {
706
+ return {
707
+ message: value.message ?? String(error),
708
+ line: value.line,
709
+ column: typeof value.column === "number" ? value.column : void 0
710
+ };
711
+ }
712
+ const text = value.message ?? String(error);
713
+ const match = text.match(/\((\d+):(\d+)/);
714
+ if (match) {
715
+ return {
716
+ message: text,
717
+ line: Number(match[1]),
718
+ column: Number(match[2])
719
+ };
720
+ }
721
+ return { message: text };
722
+ }
723
+ return { message: String(error) };
724
+ }
725
+ function parseCompileError(error, kind) {
726
+ return {
727
+ ...parseMdxErrorDetails(error),
728
+ kind
729
+ };
730
+ }
731
+
732
+ // src/useMdxPreviewCompilation.ts
733
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef6, useState as useState4 } from "react";
734
+ import { deriveRenderKey } from "@owomark/core";
735
+
736
+ // src/mdx-runtime.ts
737
+ import { compile, run } from "@mdx-js/mdx";
738
+ import * as jsxRuntime from "react/jsx-runtime";
739
+ import {
740
+ getOwoMarkPlugins,
741
+ allBuiltinDescriptors,
742
+ inspectMdxSource,
743
+ remapMdxErrorDetails
744
+ } from "@owomark/processor";
745
+ var workerInstance = null;
746
+ var workerRefCount = 0;
747
+ var requestCounter = 0;
748
+ var MAX_CRASHES = 3;
749
+ var CRASH_COOLDOWN_MS = 5e3;
750
+ var CRASH_WINDOW_MS = 3e4;
751
+ var permanentlyFailed = false;
752
+ var crashTimestamps = [];
753
+ var pendingRequests = /* @__PURE__ */ new Map();
754
+ function recordCrash() {
755
+ const now = Date.now();
756
+ crashTimestamps.push(now);
757
+ while (crashTimestamps.length > 0 && now - crashTimestamps[0] > CRASH_WINDOW_MS) {
758
+ crashTimestamps.shift();
759
+ }
760
+ if (crashTimestamps.length >= MAX_CRASHES) {
761
+ permanentlyFailed = true;
762
+ }
763
+ }
764
+ function canCreateWorker() {
765
+ if (permanentlyFailed) return false;
766
+ if (crashTimestamps.length === 0) return true;
767
+ return Date.now() - crashTimestamps[crashTimestamps.length - 1] >= CRASH_COOLDOWN_MS;
768
+ }
769
+ function tryCreateWorker() {
770
+ if (workerInstance) return workerInstance;
771
+ if (!canCreateWorker()) return null;
772
+ try {
773
+ if (typeof Worker === "undefined") {
774
+ permanentlyFailed = true;
775
+ return null;
776
+ }
777
+ const worker = new Worker(
778
+ new URL("./mdx.worker.js", import.meta.url),
779
+ { type: "module" }
780
+ );
781
+ worker.onmessage = (e) => {
782
+ const response = e.data;
783
+ const pending = pendingRequests.get(response.id);
784
+ if (!pending) return;
785
+ pendingRequests.delete(response.id);
786
+ if (response.ok) {
787
+ pending.resolve(response.code);
788
+ } else {
789
+ pending.reject({ ...response.error, kind: "compile" });
790
+ }
791
+ };
792
+ worker.onerror = () => {
793
+ recordCrash();
794
+ worker.terminate();
795
+ workerInstance = null;
796
+ for (const [id, pending] of pendingRequests) {
797
+ pendingRequests.delete(id);
798
+ pending.reject({ message: "MDX compile worker crashed", kind: "compile" });
799
+ }
800
+ };
801
+ workerInstance = worker;
802
+ return worker;
803
+ } catch {
804
+ recordCrash();
805
+ return null;
806
+ }
807
+ }
808
+ function acquireMdxWorker() {
809
+ workerRefCount++;
810
+ }
811
+ function releaseMdxWorker() {
812
+ if (--workerRefCount <= 0) {
813
+ workerRefCount = 0;
814
+ if (workerInstance) {
815
+ workerInstance.terminate();
816
+ workerInstance = null;
817
+ }
818
+ for (const [, pending] of pendingRequests) {
819
+ pending.reject({ message: "Worker released", kind: "compile" });
820
+ }
821
+ pendingRequests.clear();
822
+ }
823
+ }
824
+ function compileInWorker(markdown, options, inspection) {
825
+ const maybeWorker = tryCreateWorker();
826
+ if (!maybeWorker) {
827
+ return {
828
+ promise: Promise.reject(new Error("Worker unavailable")),
829
+ cancel: () => {
830
+ }
831
+ };
832
+ }
833
+ const workerRef = maybeWorker;
834
+ const id = ++requestCounter;
835
+ let cancelled = false;
836
+ const promise = new Promise((resolve, reject) => {
837
+ pendingRequests.set(id, { resolve, reject });
838
+ workerRef.postMessage({
839
+ type: "compile",
840
+ id,
841
+ markdown,
842
+ options: {
843
+ enableMath: options.enableMath,
844
+ enableSideAnnotation: options.enableSideAnnotation,
845
+ enableCodeHighlight: options.enableCodeHighlight,
846
+ sourceAnchors: options.sourceAnchors,
847
+ extraRemarkDescriptors: options.extraRemarkDescriptors,
848
+ extraRehypeDescriptors: options.extraRehypeDescriptors,
849
+ preparedSource: inspection?.compileSource,
850
+ sourceMap: inspection?.sourceMap
851
+ }
852
+ });
853
+ });
854
+ function cancel() {
855
+ if (cancelled) return;
856
+ cancelled = true;
857
+ const pending = pendingRequests.get(id);
858
+ if (pending) {
859
+ pendingRequests.delete(id);
860
+ pending.reject({ message: "Compilation cancelled", kind: "compile" });
861
+ }
862
+ workerRef.postMessage({ type: "cancel", id });
863
+ }
864
+ return { promise, cancel };
865
+ }
866
+ async function compileOnMainThread(markdown, options, inspection) {
867
+ const resolvedInspection = inspection ?? inspectMdxSource(markdown, {
868
+ enableMath: options.enableMath,
869
+ enableSideAnnotation: options.enableSideAnnotation,
870
+ extraRemarkDescriptors: options.extraRemarkDescriptors,
871
+ extraRemarkPlugins: options.extraRemarkPlugins
872
+ });
873
+ const { remarkPlugins, rehypePlugins } = getOwoMarkPlugins({
874
+ mode: "production",
875
+ enableMath: options.enableMath,
876
+ enableSideAnnotation: options.enableSideAnnotation,
877
+ enableCodeHighlight: options.enableCodeHighlight,
878
+ sourceAnchors: options.sourceAnchors,
879
+ extraRemarkDescriptors: options.extraRemarkDescriptors,
880
+ extraRehypeDescriptors: options.extraRehypeDescriptors,
881
+ extraRemarkPlugins: options.extraRemarkPlugins,
882
+ extraRehypePlugins: options.extraRehypePlugins,
883
+ mdxSourceMap: resolvedInspection.sourceMap
884
+ });
885
+ try {
886
+ const result = await compile(resolvedInspection.compileSource, {
887
+ outputFormat: "function-body",
888
+ remarkPlugins,
889
+ rehypePlugins
890
+ });
891
+ return String(result);
892
+ } catch (error) {
893
+ throw remapMdxErrorDetails(error, resolvedInspection.sourceMap);
894
+ }
895
+ }
896
+ function requiresMainThread(options) {
897
+ if (options.extraRemarkPlugins?.length || options.extraRehypePlugins?.length) return true;
898
+ if (options.extraRemarkDescriptors?.length && !allBuiltinDescriptors(options.extraRemarkDescriptors)) return true;
899
+ if (options.extraRehypeDescriptors?.length && !allBuiltinDescriptors(options.extraRehypeDescriptors)) return true;
900
+ return false;
901
+ }
902
+ function compileMdx(markdown, options, inspection) {
903
+ if (requiresMainThread(options)) {
904
+ return {
905
+ promise: compileOnMainThread(markdown, options, inspection),
906
+ cancel: () => {
907
+ }
908
+ };
909
+ }
910
+ const workerCompile = compileInWorker(markdown, options, inspection);
911
+ let settled = false;
912
+ const promise = workerCompile.promise.catch(() => {
913
+ if (settled) throw new Error("Compilation cancelled");
914
+ return compileOnMainThread(markdown, options, inspection);
915
+ });
916
+ return {
917
+ promise,
918
+ cancel: () => {
919
+ settled = true;
920
+ workerCompile.cancel();
921
+ }
922
+ };
923
+ }
924
+ async function runMdxCode(code) {
925
+ const module = await run(code, {
926
+ ...jsxRuntime,
927
+ baseUrl: import.meta.url
928
+ });
929
+ return {
930
+ Content: module.default
931
+ };
932
+ }
933
+
934
+ // src/mdx-preview-utils.tsx
935
+ import { renderBlockDefault } from "@owomark/view";
936
+ import { jsx as jsx8 } from "react/jsx-runtime";
937
+ var objectIdentityMap = /* @__PURE__ */ new WeakMap();
938
+ var nextObjectIdentity = 1;
939
+ function getObjectIdentity(value) {
940
+ if (!value) return "0";
941
+ const existing = objectIdentityMap.get(value);
942
+ if (existing) return String(existing);
943
+ const id = nextObjectIdentity++;
944
+ objectIdentityMap.set(value, id);
945
+ return String(id);
946
+ }
947
+ function buildRuntimeComponents(mdxAnalysis, baseComponents) {
948
+ const components = { ...baseComponents };
949
+ function registerMissingComponentPath(componentName) {
950
+ const parts = componentName.split(".");
951
+ let cursor = components;
952
+ for (let index = 0; index < parts.length; index += 1) {
953
+ const part = parts[index];
954
+ const isLeaf = index === parts.length - 1;
955
+ const existing = cursor[part];
956
+ if (isLeaf) {
957
+ if (existing === void 0) {
958
+ cursor[part] = function MissingComponent(props) {
959
+ const { children, ...restProps } = props;
960
+ return /* @__PURE__ */ jsx8("div", { "data-mdx-missing": componentName, ...restProps, children });
961
+ };
962
+ }
963
+ return;
964
+ }
965
+ if (existing == null) {
966
+ const nested = {};
967
+ cursor[part] = nested;
968
+ cursor = nested;
969
+ continue;
970
+ }
971
+ if (typeof existing === "function") {
972
+ return;
973
+ }
974
+ cursor = existing;
975
+ }
976
+ }
977
+ for (const name of mdxAnalysis.componentNames) {
978
+ registerMissingComponentPath(name);
979
+ }
980
+ return components;
981
+ }
982
+ async function renderMarkdownBlocksCached(blocks, themeKey, cache, renderBlock) {
983
+ const nextCache = /* @__PURE__ */ new Map();
984
+ const results = await Promise.all(blocks.map(async (block) => {
985
+ const cached = cache.get(block.renderKey);
986
+ if (cached !== void 0) {
987
+ nextCache.set(block.renderKey, cached);
988
+ return { blockId: block.blockId, html: cached };
989
+ }
990
+ const context = {
991
+ version: 0,
992
+ themeKey,
993
+ sourceLineOffset: block.startLine - 1
994
+ };
995
+ const html = renderBlock ? await renderBlock(block, context) : await renderBlockDefault(block);
996
+ nextCache.set(block.renderKey, html);
997
+ return { blockId: block.blockId, html };
998
+ }));
999
+ cache.clear();
1000
+ for (const [k, v] of nextCache) cache.set(k, v);
1001
+ return results;
1002
+ }
1003
+
1004
+ // src/useMdxPreviewCompilation.ts
1005
+ import { inspectMdxSource as inspectMdxSource2 } from "@owomark/processor";
1006
+ function useMdxPreviewCompilation({
1007
+ snapshot,
1008
+ themeKey,
1009
+ renderBlock,
1010
+ mdx,
1011
+ wrappedComponents
1012
+ }) {
1013
+ const requestIdRef = useRef6(0);
1014
+ const cancelRef = useRef6(null);
1015
+ const blockCacheRef = useRef6(/* @__PURE__ */ new Map());
1016
+ const [mdxDisplay, setMdxDisplay] = useState4({
1017
+ code: null,
1018
+ Content: null,
1019
+ runtimeComponents: null,
1020
+ compileKey: null,
1021
+ renderKey: null,
1022
+ pending: false,
1023
+ error: null
1024
+ });
1025
+ const [markdownHtml, setMarkdownHtml] = useState4(null);
1026
+ useEffect4(() => {
1027
+ acquireMdxWorker();
1028
+ return () => {
1029
+ cancelRef.current?.();
1030
+ cancelRef.current = null;
1031
+ releaseMdxWorker();
1032
+ };
1033
+ }, []);
1034
+ const mdxInspection = useMemo3(
1035
+ () => inspectMdxSource2(snapshot.markdown, {
1036
+ enableMath: mdx?.enableMath,
1037
+ enableSideAnnotation: mdx?.enableSideAnnotation,
1038
+ extraRemarkDescriptors: mdx?.extraRemarkDescriptors,
1039
+ extraRemarkPlugins: mdx?.extraRemarkPlugins
1040
+ }),
1041
+ [
1042
+ mdx?.enableMath,
1043
+ mdx?.enableSideAnnotation,
1044
+ mdx?.extraRemarkDescriptors,
1045
+ mdx?.extraRemarkPlugins,
1046
+ snapshot.markdown
1047
+ ]
1048
+ );
1049
+ const hasMdx = mdxInspection.hasMdxSyntax;
1050
+ const optionsFingerprint = useMemo3(() => {
1051
+ const b = (v) => v === true ? "1" : v === false ? "0" : "_";
1052
+ const ds = (arr) => arr?.length ? arr.map((d) => d.options ? `${d.name}(${JSON.stringify(d.options)})` : d.name).join(",") : "";
1053
+ return [
1054
+ b(mdx?.enableMath),
1055
+ b(mdx?.enableSideAnnotation),
1056
+ b(mdx?.enableCodeHighlight),
1057
+ b(mdx?.sourceAnchors),
1058
+ ds(mdx?.extraRemarkDescriptors),
1059
+ ds(mdx?.extraRehypeDescriptors),
1060
+ `rp${getObjectIdentity(mdx?.extraRemarkPlugins)}`,
1061
+ `hp${getObjectIdentity(mdx?.extraRehypePlugins)}`
1062
+ ].join("|");
1063
+ }, [
1064
+ mdx?.enableMath,
1065
+ mdx?.enableSideAnnotation,
1066
+ mdx?.enableCodeHighlight,
1067
+ mdx?.sourceAnchors,
1068
+ mdx?.extraRemarkDescriptors,
1069
+ mdx?.extraRehypeDescriptors,
1070
+ mdx?.extraRemarkPlugins,
1071
+ mdx?.extraRehypePlugins
1072
+ ]);
1073
+ const compileKey = useMemo3(
1074
+ () => deriveRenderKey(`${snapshot.markdown}:${optionsFingerprint}`, "custom", ""),
1075
+ [snapshot.markdown, optionsFingerprint]
1076
+ );
1077
+ const componentFingerprint = useMemo3(
1078
+ () => `cp${getObjectIdentity(mdx?.components)}`,
1079
+ [mdx?.components]
1080
+ );
1081
+ const renderKey = useMemo3(
1082
+ () => `${compileKey}:${componentFingerprint}`,
1083
+ [compileKey, componentFingerprint]
1084
+ );
1085
+ useEffect4(() => {
1086
+ if (!snapshot.markdown.trim()) {
1087
+ cancelRef.current?.();
1088
+ cancelRef.current = null;
1089
+ setMarkdownHtml(null);
1090
+ setMdxDisplay({
1091
+ code: null,
1092
+ Content: null,
1093
+ runtimeComponents: null,
1094
+ compileKey: null,
1095
+ renderKey: null,
1096
+ pending: false,
1097
+ error: null
1098
+ });
1099
+ return;
1100
+ }
1101
+ const requestId = ++requestIdRef.current;
1102
+ if (!hasMdx) {
1103
+ cancelRef.current?.();
1104
+ cancelRef.current = null;
1105
+ void renderMarkdownBlocksCached(snapshot.previewBlocks, themeKey, blockCacheRef.current, renderBlock).then((blocks) => {
1106
+ if (requestIdRef.current !== requestId) return;
1107
+ setMarkdownHtml(blocks);
1108
+ setMdxDisplay({
1109
+ code: null,
1110
+ Content: null,
1111
+ runtimeComponents: null,
1112
+ compileKey: null,
1113
+ renderKey: null,
1114
+ pending: false,
1115
+ error: null
1116
+ });
1117
+ });
1118
+ return;
1119
+ }
1120
+ if (mdxDisplay.compileKey === compileKey && mdxDisplay.code) {
1121
+ if (mdxDisplay.renderKey !== renderKey) {
1122
+ const rerunId = ++requestIdRef.current;
1123
+ const cachedCode = mdxDisplay.code;
1124
+ setMdxDisplay((prev) => ({ ...prev, pending: true, error: null }));
1125
+ void (async () => {
1126
+ try {
1127
+ const { Content } = await runMdxCode(cachedCode);
1128
+ if (requestIdRef.current !== rerunId) return;
1129
+ setMdxDisplay((prev) => {
1130
+ if (prev.compileKey !== compileKey || prev.code !== cachedCode) return prev;
1131
+ return {
1132
+ ...prev,
1133
+ Content,
1134
+ runtimeComponents: buildRuntimeComponents(mdxInspection, wrappedComponents),
1135
+ renderKey,
1136
+ pending: false,
1137
+ error: null
1138
+ };
1139
+ });
1140
+ } catch (error) {
1141
+ if (requestIdRef.current !== rerunId) return;
1142
+ setMdxDisplay((prev) => ({
1143
+ ...prev,
1144
+ pending: false,
1145
+ error: parseCompileError(error, "runtime")
1146
+ }));
1147
+ }
1148
+ })();
1149
+ }
1150
+ return;
1151
+ }
1152
+ setMdxDisplay((prev) => ({ ...prev, pending: true, error: null }));
1153
+ setMarkdownHtml(null);
1154
+ cancelRef.current?.();
1155
+ cancelRef.current = null;
1156
+ const timer = setTimeout(() => {
1157
+ const { promise, cancel } = compileMdx(snapshot.markdown, {
1158
+ enableMath: mdx?.enableMath,
1159
+ enableSideAnnotation: mdx?.enableSideAnnotation,
1160
+ enableCodeHighlight: mdx?.enableCodeHighlight,
1161
+ sourceAnchors: mdx?.sourceAnchors,
1162
+ extraRemarkDescriptors: mdx?.extraRemarkDescriptors,
1163
+ extraRehypeDescriptors: mdx?.extraRehypeDescriptors,
1164
+ extraRemarkPlugins: mdx?.extraRemarkPlugins,
1165
+ extraRehypePlugins: mdx?.extraRehypePlugins
1166
+ }, mdxInspection);
1167
+ cancelRef.current = cancel;
1168
+ void (async () => {
1169
+ try {
1170
+ const code = await promise;
1171
+ if (requestIdRef.current !== requestId) return;
1172
+ const { Content } = await runMdxCode(code);
1173
+ if (requestIdRef.current !== requestId) return;
1174
+ setMdxDisplay({
1175
+ code,
1176
+ Content,
1177
+ runtimeComponents: buildRuntimeComponents(mdxInspection, wrappedComponents),
1178
+ compileKey,
1179
+ renderKey,
1180
+ pending: false,
1181
+ error: null
1182
+ });
1183
+ } catch (error) {
1184
+ if (requestIdRef.current !== requestId) return;
1185
+ setMdxDisplay((prev) => ({
1186
+ ...prev,
1187
+ pending: false,
1188
+ error: parseCompileError(error, "compile")
1189
+ }));
1190
+ }
1191
+ })();
1192
+ }, 200);
1193
+ return () => {
1194
+ clearTimeout(timer);
1195
+ cancelRef.current?.();
1196
+ cancelRef.current = null;
1197
+ };
1198
+ }, [
1199
+ compileKey,
1200
+ hasMdx,
1201
+ mdx?.enableCodeHighlight,
1202
+ mdx?.enableMath,
1203
+ mdx?.enableSideAnnotation,
1204
+ mdx?.extraRehypeDescriptors,
1205
+ mdx?.extraRehypePlugins,
1206
+ mdx?.extraRemarkDescriptors,
1207
+ mdx?.extraRemarkPlugins,
1208
+ mdxInspection,
1209
+ mdx?.sourceAnchors,
1210
+ mdxDisplay.compileKey,
1211
+ mdxDisplay.Content,
1212
+ mdxDisplay.renderKey,
1213
+ renderBlock,
1214
+ renderKey,
1215
+ snapshot.markdown,
1216
+ snapshot.previewBlocks,
1217
+ themeKey,
1218
+ wrappedComponents
1219
+ ]);
1220
+ return {
1221
+ hasMdx,
1222
+ markdownHtml,
1223
+ mdxDisplay
1224
+ };
1225
+ }
1226
+
1227
+ // src/MdxPreview.tsx
1228
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1229
+ function locationText(error) {
1230
+ if (error.line == null) return null;
1231
+ if (error.column == null) return `\u884C ${error.line}`;
1232
+ return `\u884C ${error.line}:${error.column}`;
1233
+ }
1234
+ function ErrorNotice({ error }) {
1235
+ const location = locationText(error);
1236
+ const label = error.kind === "compile" ? "MDX Compile" : "MDX Runtime";
1237
+ return /* @__PURE__ */ jsx9("div", { className: "sticky top-0 z-10 mb-4 rounded-md border border-[var(--owo-cmp-danger-border)] bg-[var(--owo-cmp-danger-bg)] px-4 py-3 text-sm text-[var(--owo-cmp-danger-text)]", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-2", children: [
1238
+ /* @__PURE__ */ jsx9("span", { className: "mt-0.5 shrink-0 font-mono text-[11px] font-bold uppercase", children: label }),
1239
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
1240
+ location && /* @__PURE__ */ jsx9("span", { className: "mr-2 rounded bg-[var(--owo-cmp-danger-border)] px-1.5 py-0.5 font-mono text-xs", children: location }),
1241
+ /* @__PURE__ */ jsx9("span", { className: "break-words", children: error.message })
1242
+ ] })
1243
+ ] }) });
1244
+ }
1245
+ var RuntimeErrorBoundary = class extends Component {
1246
+ state = { error: null };
1247
+ static getDerivedStateFromError(error) {
1248
+ return { error: parseCompileError(error, "runtime") };
1249
+ }
1250
+ componentDidCatch(error) {
1251
+ this.props.onError(parseCompileError(error, "runtime"));
1252
+ }
1253
+ render() {
1254
+ if (this.state.error) {
1255
+ return /* @__PURE__ */ jsx9(ErrorNotice, { error: this.state.error });
1256
+ }
1257
+ return this.props.children;
1258
+ }
1259
+ };
1260
+ function useStoreSnapshot(store) {
1261
+ return useSyncExternalStore(store.subscribe, store.getState, store.getState);
1262
+ }
1263
+ function MdxPreview(props) {
1264
+ const {
1265
+ state,
1266
+ className,
1267
+ themeKey = "",
1268
+ ariaLabel,
1269
+ renderBlock,
1270
+ mdx,
1271
+ onContentUpdate
1272
+ } = props;
1273
+ const snapshot = useStoreSnapshot(state);
1274
+ const rootRef = useRef7(null);
1275
+ const [runtimeError, setRuntimeError] = useState5(null);
1276
+ const wrappedComponents = useMemo4(() => {
1277
+ const wrapComponentMap = (componentMap) => {
1278
+ const wrapped = {};
1279
+ for (const [name, component] of Object.entries(componentMap)) {
1280
+ if (typeof component === "function") {
1281
+ wrapped[name] = wrapWithShell(name, component);
1282
+ continue;
1283
+ }
1284
+ wrapped[name] = wrapComponentMap(component);
1285
+ }
1286
+ return wrapped;
1287
+ };
1288
+ const merged = {
1289
+ ...DEFAULT_MDX_COMPONENTS,
1290
+ ...mdx?.components ?? {}
1291
+ };
1292
+ return wrapComponentMap(merged);
1293
+ }, [mdx?.components]);
1294
+ const { markdownHtml, mdxDisplay } = useMdxPreviewCompilation({
1295
+ snapshot,
1296
+ themeKey,
1297
+ renderBlock,
1298
+ mdx,
1299
+ wrappedComponents
1300
+ });
1301
+ useEffect5(() => {
1302
+ setRuntimeError(null);
1303
+ }, [mdxDisplay.renderKey]);
1304
+ useEffect5(() => {
1305
+ const root = rootRef.current;
1306
+ if (!root || !onContentUpdate) return;
1307
+ const observer = new ResizeObserver(() => onContentUpdate());
1308
+ observer.observe(root);
1309
+ return () => observer.disconnect();
1310
+ }, [onContentUpdate]);
1311
+ useEffect5(() => {
1312
+ if (!onContentUpdate) return;
1313
+ const id = requestAnimationFrame(() => onContentUpdate());
1314
+ return () => cancelAnimationFrame(id);
1315
+ }, [mdxDisplay, markdownHtml, runtimeError, onContentUpdate]);
1316
+ const hasContent = mdxDisplay.Content !== null || markdownHtml !== null;
1317
+ return /* @__PURE__ */ jsxs3(
1318
+ "div",
1319
+ {
1320
+ ref: rootRef,
1321
+ className,
1322
+ role: "document",
1323
+ "aria-label": ariaLabel ?? "Preview",
1324
+ "aria-live": "polite",
1325
+ children: [
1326
+ mdxDisplay.pending && hasContent && /* @__PURE__ */ jsx9("div", { className: "pointer-events-none sticky top-0 z-10 mb-3 h-0.5 w-full animate-pulse rounded-full bg-p400" }),
1327
+ mdxDisplay.error && /* @__PURE__ */ jsx9(ErrorNotice, { error: mdxDisplay.error }),
1328
+ runtimeError && /* @__PURE__ */ jsx9(ErrorNotice, { error: runtimeError }),
1329
+ !hasContent && mdxDisplay.pending && /* @__PURE__ */ jsx9(MdxSkeleton, { lines: 8 }),
1330
+ markdownHtml !== null && markdownHtml.map(({ blockId, html }) => /* @__PURE__ */ jsx9("div", { dangerouslySetInnerHTML: { __html: html } }, blockId)),
1331
+ mdxDisplay.Content && mdxDisplay.runtimeComponents && /* @__PURE__ */ jsx9(RuntimeErrorBoundary, { onError: setRuntimeError, children: /* @__PURE__ */ jsx9(mdxDisplay.Content, { components: mdxDisplay.runtimeComponents }) }, mdxDisplay.renderKey)
1332
+ ]
1333
+ }
1334
+ );
1335
+ }
1336
+
1337
+ // src/OwoMarkPreview.tsx
1338
+ import { jsx as jsx10 } from "react/jsx-runtime";
596
1339
  function isController(store) {
597
1340
  return typeof store.updateVisibleBlockIds === "function";
598
1341
  }
@@ -606,16 +1349,31 @@ var OwoMarkPreview = function OwoMarkPreview2(props) {
606
1349
  viewportFirst,
607
1350
  ariaLabel,
608
1351
  renderBlock,
1352
+ mdx,
609
1353
  onContentUpdate
610
1354
  } = props;
611
- const onContentUpdateRef = useRef4(onContentUpdate);
1355
+ if (strategy === "mdx") {
1356
+ return /* @__PURE__ */ jsx10(
1357
+ MdxPreview,
1358
+ {
1359
+ state,
1360
+ className,
1361
+ themeKey,
1362
+ ariaLabel,
1363
+ renderBlock,
1364
+ mdx,
1365
+ onContentUpdate
1366
+ }
1367
+ );
1368
+ }
1369
+ const onContentUpdateRef = useRef8(onContentUpdate);
612
1370
  onContentUpdateRef.current = onContentUpdate;
613
- const renderBlockRef = useRef4(renderBlock);
1371
+ const renderBlockRef = useRef8(renderBlock);
614
1372
  renderBlockRef.current = renderBlock;
615
1373
  const hasRenderBlock = !!renderBlock;
616
- const containerRef = useRef4(null);
617
- const engineRef = useRef4(null);
618
- useEffect3(() => {
1374
+ const containerRef = useRef8(null);
1375
+ const engineRef = useRef8(null);
1376
+ useEffect6(() => {
619
1377
  if (!containerRef.current) return;
620
1378
  const engineOptions = {
621
1379
  strategy,
@@ -635,7 +1393,7 @@ var OwoMarkPreview = function OwoMarkPreview2(props) {
635
1393
  engineRef.current = null;
636
1394
  };
637
1395
  }, [strategy, themeKey, registry, viewportFirst, hasRenderBlock]);
638
- useEffect3(() => {
1396
+ useEffect6(() => {
639
1397
  const unsub = state.subscribe((newState) => {
640
1398
  const engine = engineRef.current;
641
1399
  if (engine) {
@@ -644,7 +1402,7 @@ var OwoMarkPreview = function OwoMarkPreview2(props) {
644
1402
  });
645
1403
  return unsub;
646
1404
  }, [state]);
647
- useEffect3(() => {
1405
+ useEffect6(() => {
648
1406
  const container = containerRef.current;
649
1407
  if (!viewportFirst || !container || !isController(state)) return;
650
1408
  const controller = state;
@@ -686,7 +1444,7 @@ var OwoMarkPreview = function OwoMarkPreview2(props) {
686
1444
  mutation.disconnect();
687
1445
  };
688
1446
  }, [viewportFirst, state]);
689
- return /* @__PURE__ */ jsx6(
1447
+ return /* @__PURE__ */ jsx10(
690
1448
  "div",
691
1449
  {
692
1450
  ref: containerRef,
@@ -699,33 +1457,33 @@ var OwoMarkPreview = function OwoMarkPreview2(props) {
699
1457
  };
700
1458
 
701
1459
  // src/useOwoMarkSharedState.ts
702
- import { useRef as useRef5, useSyncExternalStore, useCallback as useCallback2 } from "react";
1460
+ import { useRef as useRef9, useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback3 } from "react";
703
1461
  import {
704
1462
  createSharedStateStore
705
1463
  } from "@owomark/core";
706
1464
  function useOwoMarkSharedState(options) {
707
- const controllerRef = useRef5(null);
1465
+ const controllerRef = useRef9(null);
708
1466
  if (!controllerRef.current) {
709
1467
  controllerRef.current = createSharedStateStore(options);
710
1468
  }
711
1469
  return controllerRef.current;
712
1470
  }
713
1471
  function useSharedStateSnapshot(controller) {
714
- const subscribe = useCallback2(
1472
+ const subscribe = useCallback3(
715
1473
  (onStoreChange) => controller.subscribe(onStoreChange),
716
1474
  [controller]
717
1475
  );
718
- const getSnapshot = useCallback2(() => controller.getState(), [controller]);
719
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1476
+ const getSnapshot = useCallback3(() => controller.getState(), [controller]);
1477
+ return useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
720
1478
  }
721
1479
 
722
1480
  // src/useBlockContext.ts
723
- import { useState as useState3, useEffect as useEffect5 } from "react";
1481
+ import { useState as useState6, useEffect as useEffect8 } from "react";
724
1482
  function useBlockContext(core) {
725
- const [blockContext, setBlockContext] = useState3(
1483
+ const [blockContext, setBlockContext] = useState6(
726
1484
  () => core.getBlockContext()
727
1485
  );
728
- useEffect5(() => {
1486
+ useEffect8(() => {
729
1487
  setBlockContext(core.getBlockContext());
730
1488
  return core.onBlockContextChange((ctx) => {
731
1489
  setBlockContext(ctx);
@@ -738,23 +1496,23 @@ function useBlockContext(core) {
738
1496
  import { getThemeClassName as getThemeClassName2, THEME_LIGHT_CLASS, THEME_DARK_CLASS } from "@owomark/view";
739
1497
 
740
1498
  // src/useVirtualList.ts
741
- import { useState as useState4, useCallback as useCallback3, useRef as useRef6, useEffect as useEffect6, useMemo as useMemo2 } from "react";
1499
+ import { useState as useState7, useCallback as useCallback4, useRef as useRef10, useEffect as useEffect9, useMemo as useMemo5 } from "react";
742
1500
  import {
743
1501
  buildVirtualRows,
744
1502
  computeVisibleRange
745
1503
  } from "@owomark/core";
746
1504
  function useVirtualList(options) {
747
1505
  const { blocks, containerRef, overscan = 5, charsPerLine = 80 } = options;
748
- const heightCacheRef = useRef6(/* @__PURE__ */ new Map());
749
- const [scrollTop, setScrollTop] = useState4(0);
750
- const [viewportHeight, setViewportHeight] = useState4(0);
751
- const [heightCacheVersion, setHeightCacheVersion] = useState4(0);
752
- const rafRef = useRef6(0);
753
- const [container, setContainer] = useState4(null);
754
- useEffect6(() => {
1506
+ const heightCacheRef = useRef10(/* @__PURE__ */ new Map());
1507
+ const [scrollTop, setScrollTop] = useState7(0);
1508
+ const [viewportHeight, setViewportHeight] = useState7(0);
1509
+ const [heightCacheVersion, setHeightCacheVersion] = useState7(0);
1510
+ const rafRef = useRef10(0);
1511
+ const [container, setContainer] = useState7(null);
1512
+ useEffect9(() => {
755
1513
  setContainer(containerRef.current);
756
1514
  });
757
- useEffect6(() => {
1515
+ useEffect9(() => {
758
1516
  if (!container) return;
759
1517
  setViewportHeight(container.clientHeight);
760
1518
  const ro = new ResizeObserver((entries) => {
@@ -765,7 +1523,7 @@ function useVirtualList(options) {
765
1523
  ro.observe(container);
766
1524
  return () => ro.disconnect();
767
1525
  }, [container]);
768
- useEffect6(() => {
1526
+ useEffect9(() => {
769
1527
  if (!container) return;
770
1528
  const onScroll = () => {
771
1529
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
@@ -779,27 +1537,27 @@ function useVirtualList(options) {
779
1537
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
780
1538
  };
781
1539
  }, [container]);
782
- const rows = useMemo2(
1540
+ const rows = useMemo5(
783
1541
  () => buildVirtualRows(blocks, heightCacheRef.current, charsPerLine),
784
1542
  // eslint-disable-next-line react-hooks/exhaustive-deps
785
1543
  [blocks, charsPerLine, heightCacheVersion]
786
1544
  );
787
- const visibleRange = useMemo2(
1545
+ const visibleRange = useMemo5(
788
1546
  () => computeVisibleRange(rows, scrollTop, viewportHeight, overscan),
789
1547
  [rows, scrollTop, viewportHeight, overscan]
790
1548
  );
791
- const updateBlockHeight = useCallback3((blockId, height) => {
1549
+ const updateBlockHeight = useCallback4((blockId, height) => {
792
1550
  const cache = heightCacheRef.current;
793
1551
  if (cache.get(blockId) !== height) {
794
1552
  cache.set(blockId, height);
795
1553
  setHeightCacheVersion((v) => v + 1);
796
1554
  }
797
1555
  }, []);
798
- const isBlockVisible = useCallback3(
1556
+ const isBlockVisible = useCallback4(
799
1557
  (blockIndex) => blockIndex >= visibleRange.startIndex && blockIndex <= visibleRange.endIndex,
800
1558
  [visibleRange]
801
1559
  );
802
- const invalidateAllHeights = useCallback3(() => {
1560
+ const invalidateAllHeights = useCallback4(() => {
803
1561
  heightCacheRef.current.clear();
804
1562
  setHeightCacheVersion((v) => v + 1);
805
1563
  }, []);
@@ -813,73 +1571,8 @@ function useVirtualList(options) {
813
1571
  };
814
1572
  }
815
1573
 
816
- // src/MdxSkeleton.tsx
817
- import { jsx as jsx7 } from "react/jsx-runtime";
818
- function MdxSkeleton({ height, lines = 3, className, style }) {
819
- const combinedStyle = {
820
- ...style,
821
- ...height != null ? { height, minHeight: height } : {}
822
- };
823
- return /* @__PURE__ */ jsx7(
824
- "div",
825
- {
826
- className: `owo-mdx-skeleton-block ${className ?? ""}`,
827
- style: combinedStyle,
828
- "aria-hidden": "true",
829
- children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx7("div", { className: "owo-mdx-skeleton owo-mdx-skeleton-line" }, i))
830
- }
831
- );
832
- }
833
-
834
- // src/MdxComponentShell.tsx
835
- import {
836
- useRef as useRef7,
837
- useState as useState5,
838
- useEffect as useEffect7,
839
- useCallback as useCallback4
840
- } from "react";
841
- import { jsx as jsx8 } from "react/jsx-runtime";
842
- var componentHeightCache = /* @__PURE__ */ new Map();
843
- var DEFAULT_ESTIMATED_HEIGHT = 80;
844
- function MdxComponentShell({ name, children }) {
845
- const containerRef = useRef7(null);
846
- const [ready, setReady] = useState5(false);
847
- const estimatedHeight = componentHeightCache.get(name) ?? DEFAULT_ESTIMATED_HEIGHT;
848
- useEffect7(() => {
849
- const id = requestAnimationFrame(() => setReady(true));
850
- return () => cancelAnimationFrame(id);
851
- }, []);
852
- const measureHeight = useCallback4(() => {
853
- const el = containerRef.current;
854
- if (!el) return;
855
- const h = el.getBoundingClientRect().height;
856
- if (h > 0) {
857
- componentHeightCache.set(name, h);
858
- }
859
- }, [name]);
860
- useEffect7(() => {
861
- const el = containerRef.current;
862
- if (!el || !ready) return;
863
- measureHeight();
864
- const ro = new ResizeObserver(() => measureHeight());
865
- ro.observe(el);
866
- return () => ro.disconnect();
867
- }, [ready, measureHeight]);
868
- if (!ready) {
869
- return /* @__PURE__ */ jsx8(MdxSkeleton, { height: estimatedHeight });
870
- }
871
- return /* @__PURE__ */ jsx8("div", { ref: containerRef, children });
872
- }
873
- function wrapWithShell(name, Component) {
874
- function ShellWrapped(props) {
875
- return /* @__PURE__ */ jsx8(MdxComponentShell, { name, children: /* @__PURE__ */ jsx8(Component, { ...props }) });
876
- }
877
- ShellWrapped.displayName = `MdxShell(${name})`;
878
- return ShellWrapped;
879
- }
880
- function clearComponentHeightCache() {
881
- componentHeightCache.clear();
882
- }
1574
+ // src/index.ts
1575
+ import { registerPlugin } from "@owomark/processor";
883
1576
  export {
884
1577
  DEFAULT_EDITOR_CONFIG,
885
1578
  EditorBlock,
@@ -892,11 +1585,14 @@ export {
892
1585
  SlashMenu,
893
1586
  THEME_DARK_CLASS,
894
1587
  THEME_LIGHT_CLASS,
1588
+ acquireMdxWorker,
895
1589
  clearComponentHeightCache,
896
1590
  computeMenuPosition,
897
1591
  deriveProcessorOptions,
898
1592
  getCaretRect,
899
1593
  getThemeClassName2 as getThemeClassName,
1594
+ registerPlugin,
1595
+ releaseMdxWorker,
900
1596
  resolveEditorConfig,
901
1597
  useBlockContext,
902
1598
  useOwoMarkCore,
@@ -0,0 +1,97 @@
1
+ // src/mdx.worker.ts
2
+ import { compile } from "@mdx-js/mdx";
3
+ import {
4
+ getOwoMarkPlugins,
5
+ inspectMdxSource,
6
+ remapMdxErrorDetails
7
+ } from "@owomark/processor";
8
+
9
+ // src/mdx-error.ts
10
+ function parseMdxErrorDetails(error) {
11
+ if (error && typeof error === "object") {
12
+ const value = error;
13
+ if (typeof value.line === "number") {
14
+ return {
15
+ message: value.message ?? String(error),
16
+ line: value.line,
17
+ column: typeof value.column === "number" ? value.column : void 0
18
+ };
19
+ }
20
+ const text = value.message ?? String(error);
21
+ const match = text.match(/\((\d+):(\d+)/);
22
+ if (match) {
23
+ return {
24
+ message: text,
25
+ line: Number(match[1]),
26
+ column: Number(match[2])
27
+ };
28
+ }
29
+ return { message: text };
30
+ }
31
+ return { message: String(error) };
32
+ }
33
+
34
+ // src/mdx.worker.ts
35
+ var cancelledIds = /* @__PURE__ */ new Set();
36
+ self.onmessage = (e) => {
37
+ const msg = e.data;
38
+ if (msg.type === "cancel") {
39
+ cancelledIds.add(msg.id);
40
+ return;
41
+ }
42
+ void handleCompile(msg);
43
+ };
44
+ async function handleCompile(data) {
45
+ const { id, markdown, options } = data;
46
+ const inspection = options.preparedSource && options.sourceMap ? null : inspectMdxSource(markdown, {
47
+ enableMath: options.enableMath,
48
+ enableSideAnnotation: options.enableSideAnnotation,
49
+ extraRemarkDescriptors: options.extraRemarkDescriptors
50
+ });
51
+ const preparedSource = options.preparedSource ?? inspection.compileSource;
52
+ const sourceMap = options.sourceMap ?? inspection.sourceMap;
53
+ try {
54
+ if (cancelledIds.has(id)) {
55
+ cancelledIds.delete(id);
56
+ return;
57
+ }
58
+ const { remarkPlugins, rehypePlugins } = getOwoMarkPlugins({
59
+ mode: "production",
60
+ enableMath: options.enableMath,
61
+ enableSideAnnotation: options.enableSideAnnotation,
62
+ enableCodeHighlight: options.enableCodeHighlight,
63
+ sourceAnchors: options.sourceAnchors,
64
+ extraRemarkDescriptors: options.extraRemarkDescriptors,
65
+ extraRehypeDescriptors: options.extraRehypeDescriptors,
66
+ mdxSourceMap: sourceMap
67
+ });
68
+ if (cancelledIds.has(id)) {
69
+ cancelledIds.delete(id);
70
+ return;
71
+ }
72
+ const result = await compile(preparedSource, {
73
+ outputFormat: "function-body",
74
+ remarkPlugins,
75
+ rehypePlugins
76
+ });
77
+ if (cancelledIds.has(id)) {
78
+ cancelledIds.delete(id);
79
+ return;
80
+ }
81
+ self.postMessage({
82
+ id,
83
+ ok: true,
84
+ code: String(result)
85
+ });
86
+ } catch (error) {
87
+ if (cancelledIds.has(id)) {
88
+ cancelledIds.delete(id);
89
+ return;
90
+ }
91
+ self.postMessage({
92
+ id,
93
+ ok: false,
94
+ error: parseMdxErrorDetails(remapMdxErrorDetails(error, sourceMap))
95
+ });
96
+ }
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owomark/react",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "React bindings and components for the OwoMark editor stack.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,8 +33,10 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@owomark/core": "^0.1.4",
37
- "@owomark/view": "^0.1.4"
36
+ "@mdx-js/mdx": "^3.0.0",
37
+ "@owomark/core": "^0.1.6",
38
+ "@owomark/processor": "^0.1.6",
39
+ "@owomark/view": "^0.1.6"
38
40
  },
39
41
  "peerDependencies": {
40
42
  "react": ">=18",
package/dist/index.css DELETED
@@ -1,32 +0,0 @@
1
- /* src/MdxSkeleton.css */
2
- .owo-mdx-skeleton {
3
- background:
4
- linear-gradient(
5
- 90deg,
6
- var(--owo-skeleton-base, #e5e7eb) 25%,
7
- var(--owo-skeleton-shine, #f3f4f6) 50%,
8
- var(--owo-skeleton-base, #e5e7eb) 75%);
9
- background-size: 200% 100%;
10
- animation: owo-mdx-skeleton-shimmer 1.5s ease-in-out infinite;
11
- border-radius: 4px;
12
- }
13
- @keyframes owo-mdx-skeleton-shimmer {
14
- 0% {
15
- background-position: 200% 0;
16
- }
17
- 100% {
18
- background-position: -200% 0;
19
- }
20
- }
21
- .owo-mdx-skeleton-block {
22
- display: flex;
23
- flex-direction: column;
24
- gap: 8px;
25
- padding: 12px 0;
26
- }
27
- .owo-mdx-skeleton-line {
28
- height: 14px;
29
- }
30
- .owo-mdx-skeleton-line:last-child {
31
- width: 60%;
32
- }