@suzumiyaaoba/mdxr 0.4.0 → 0.5.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/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Ap as parseLineRange, Bp as urlScheme, Fp as isComponent, Gd as reportModel, Ip as isRecord, Jf as isStatus, Kd as comparisonModel, Kf as STATUSES, Lp as nonEmpty, Mp as SKIP_LANGS, Op as fenceFilename, Pp as langForPath, Rp as own, Tp as DocContext, V as shadcnModules, Vd as calculate, Wd as REPORT_SPECS, _l as PLOT_NAMES, _p as numOf, af as display, ap as numList, cf as parseJson, dc as queryPlanModel, df as tableRecords, fc as diagramModel, gp as attrTrue, if as columnsOf, jl as PlanHeader, jp as splitPathLines, kp as fenceLang, lc as bundleModel, lf as readRecords, of as isDataRecord, op as textList, pc as flameModel, qd as schemaRows, sf as numberValue, t as builtinComponents, uc as dependencyMatrixModel, uf as records, vl as plotModel, wp as takeUsedIcons, xf as parseDiff, zp as safeHref } from "./ui-D89i0Cq-.mjs";
3
- import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-DQeZaek7.mjs";
2
+ import { $d as reportModel, Bp as splitPathLines, Cu as CLAIM_LABELS, Du as performanceDetails, Eu as RESEARCH_CLAIM_SCHEMA, Fp as parseProps, Gp as isRecord, Jp as safeHref, Kp as nonEmpty, Lp as fenceFilename, Ml as PlanHeader, Mp as takeUsedIcons, Np as DocContext, Ou as performanceResult, Qd as REPORT_SPECS, Rp as fenceLang, Tp as numOf, Tu as PERFORMANCE_TARGET_SCHEMA, Up as langForPath, V as shadcnModules, Vp as SKIP_LANGS, Wp as isComponent, Yd as calculate, Yp as urlScheme, _f as readRecords, dc as queryPlanModel, ef as comparisonModel, ep as STATUSES, fc as diagramModel, ff as columnsOf, gf as parseJson, hf as numberValue, kf as parseDiff, lc as bundleModel, mf as isDataRecord, mp as textList, np as isStatus, pc as flameModel, pf as display, pp as numList, qp as own, t as builtinComponents, tf as schemaRows, uc as dependencyMatrixModel, vf as records, vl as PLOT_NAMES, wp as attrTrue, wu as PERFORMANCE_LABELS, yf as tableRecords, yl as plotModel, zp as parseLineRange } from "./ui-k-VrmhPJ.mjs";
3
+ import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-C-pcUAoU.mjs";
4
4
  import module from "node:module";
5
5
  import fsp, { access, readFile, readdir, writeFile } from "node:fs/promises";
6
6
  import path from "node:path";
@@ -23,18 +23,16 @@ import { visit } from "unist-util-visit";
23
23
  import fs, { existsSync, readFileSync, realpathSync } from "node:fs";
24
24
  import { visitParents } from "unist-util-visit-parents";
25
25
  import * as valibot from "valibot";
26
- import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
27
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
26
+ import { Fragment, createElement } from "react";
28
27
  import { icons } from "@iconify-json/lucide";
29
28
  import { icons as icons$1 } from "@iconify-json/vscode-icons";
30
29
  import { differenceInCalendarDays, isValid, parseISO } from "date-fns";
31
- import { cn } from "cn";
32
30
  import { build } from "esbuild";
33
31
  import os from "node:os";
34
32
  import { spawn } from "node:child_process";
35
33
  import { once } from "node:events";
36
- import { renderToStaticMarkup, renderToString } from "react-dom/server";
37
34
  import { compile } from "@mdx-js/mdx";
35
+ import { renderToStaticMarkup, renderToString } from "react-dom/server";
38
36
  import rehypeKatex from "rehype-katex";
39
37
  import { transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRemoveNotationEscape } from "@shikijs/transformers";
40
38
  import { createHighlighter } from "shiki";
@@ -1003,12 +1001,14 @@ const CONTAINER_COMPONENTS = {
1003
1001
  package: "Package",
1004
1002
  packages: "Packages",
1005
1003
  pathway: "Pathway",
1004
+ performancetarget: "PerformanceTarget",
1006
1005
  phase: "Phase",
1007
1006
  piechart: "PieChart",
1008
1007
  plan: "Plan",
1009
1008
  quadrant: "Quadrant",
1010
1009
  radar: "Radar",
1011
1010
  release: "Release",
1011
+ researchclaim: "ResearchClaim",
1012
1012
  review: "Review",
1013
1013
  sankey: "Sankey",
1014
1014
  scatter: "Scatter",
@@ -1180,11 +1180,8 @@ const toList = (items) => ({
1180
1180
  * 2. `<Toc>` elements (`:::toc` included) get a nested link list injected as
1181
1181
  * children — `depth`/`min` attributes bound the heading levels included
1182
1182
  * (defaults: h2–h3, i.e. min=2 depth=3).
1183
- *
1184
- * The flat heading list is also left on `file.data.mdxrHeadings` for the
1185
- * page-level sidebar ToC.
1186
1183
  */
1187
- const remarkMdxrHeadings = () => (tree, file) => {
1184
+ const remarkMdxrHeadings = () => (tree) => {
1188
1185
  const counts = /* @__PURE__ */ new Map();
1189
1186
  const used = /* @__PURE__ */ new Set();
1190
1187
  const headings = [];
@@ -1215,11 +1212,6 @@ const remarkMdxrHeadings = () => (tree, file) => {
1215
1212
  const items = headings.filter((h) => h.depth >= minDepth && h.depth <= maxDepth && h.text !== "");
1216
1213
  node.children = items.length === 0 ? [] : [toList(nest(items))];
1217
1214
  });
1218
- file.data.mdxrHeadings = headings.filter((h) => h.text !== "").map(({ depth, slug, text }) => ({
1219
- depth,
1220
- slug,
1221
- text
1222
- }));
1223
1215
  };
1224
1216
  //#endregion
1225
1217
  //#region src/remark/no-js.ts
@@ -1347,6 +1339,7 @@ const INLINE_REFERENCES = /* @__PURE__ */ new Set([
1347
1339
  const REFERENCE_FEATURES = /* @__PURE__ */ new Set([
1348
1340
  ...INLINE_REFERENCES,
1349
1341
  "Source",
1342
+ "ResearchClaim",
1350
1343
  "TableOfFigures",
1351
1344
  "NumberedEquation",
1352
1345
  "Theorem"
@@ -1407,6 +1400,26 @@ const resolveCitation = (node, state) => {
1407
1400
  ids.push(id);
1408
1401
  state.backlinks.set(target, ids);
1409
1402
  };
1403
+ /** Claims reuse the bibliography's numbering and backlink contract. */
1404
+ const resolveClaimCitation = (node, state) => {
1405
+ const source = jsxAttr(node, "source");
1406
+ if (source === void 0) return;
1407
+ const citation = {
1408
+ attributes: [{
1409
+ name: "source",
1410
+ type: "mdxJsxAttribute",
1411
+ value: source
1412
+ }],
1413
+ children: [],
1414
+ name: "Cite",
1415
+ position: node.position,
1416
+ type: "mdxJsxTextElement"
1417
+ };
1418
+ resolveCitation(citation, state);
1419
+ set(node, "citationId", jsxAttr(citation, "id") ?? "");
1420
+ set(node, "sourceHref", jsxAttr(citation, "href") ?? "");
1421
+ set(node, "sourceLabel", jsxAttr(citation, "label") ?? "");
1422
+ };
1410
1423
  const resolveCrossReference = (node, state) => {
1411
1424
  const target = jsxAttr(node, "target") ?? (node.name === "TermRef" ? `term-${jsxAttr(node, "term") ?? ""}` : "");
1412
1425
  const reference = state.references.get(target);
@@ -1453,6 +1466,13 @@ const addLegacyAnchor = (node) => {
1453
1466
  type: "mdxJsxTextElement"
1454
1467
  });
1455
1468
  };
1469
+ const resolveReference = (node, state) => {
1470
+ if (node.name === "ResearchClaim") resolveClaimCitation(node, state);
1471
+ if (node.name === "Cite") resolveCitation(node, state);
1472
+ if (node.name === "CrossRef" || node.name === "TermRef") resolveCrossReference(node, state);
1473
+ if (node.name === "TableOfFigures") listFigures(node, state.references);
1474
+ addLegacyAnchor(node);
1475
+ };
1456
1476
  /** Two passes support forward references without changing older documents. */
1457
1477
  const remarkReferences = () => (tree, file) => {
1458
1478
  const elements = [];
@@ -1469,12 +1489,7 @@ const remarkReferences = () => (tree, file) => {
1469
1489
  sources: /* @__PURE__ */ new Map()
1470
1490
  };
1471
1491
  for (const node of elements) registerReference(node, state);
1472
- for (const node of elements) {
1473
- if (node.name === "Cite") resolveCitation(node, state);
1474
- if (node.name === "CrossRef" || node.name === "TermRef") resolveCrossReference(node, state);
1475
- if (node.name === "TableOfFigures") listFigures(node, state.references);
1476
- addLegacyAnchor(node);
1477
- }
1492
+ for (const node of elements) resolveReference(node, state);
1478
1493
  for (const [source, ids] of state.backlinks) {
1479
1494
  const reference = state.sources.get(source);
1480
1495
  if (reference) set(reference.node, "backlinks", ids.join(","));
@@ -3712,6 +3727,48 @@ const reportRenderers = {
3712
3727
  ]), ...ctx.children(n)] }
3713
3728
  };
3714
3729
  //#endregion
3730
+ //#region src/ascii/research.ts
3731
+ const attributes = (node, names) => Object.fromEntries(names.map((name) => [name, attr(node, name)]));
3732
+ const researchRenderers = {
3733
+ PerformanceTarget: { flow: (node, ctx) => {
3734
+ const props = parseProps(PERFORMANCE_TARGET_SCHEMA, attributes(node, [
3735
+ "actual",
3736
+ "better",
3737
+ "conditions",
3738
+ "measuredAt",
3739
+ "name",
3740
+ "statistic",
3741
+ "status",
3742
+ "target",
3743
+ "unit"
3744
+ ]), "PerformanceTarget");
3745
+ return [
3746
+ ...caption(props.name, ` — ${PERFORMANCE_LABELS[performanceResult(props)]}`),
3747
+ table(["Field", "Value"], performanceDetails(props)),
3748
+ ...ctx.children(node)
3749
+ ];
3750
+ } },
3751
+ ResearchClaim: { flow: (node, ctx) => {
3752
+ const props = parseProps(RESEARCH_CLAIM_SCHEMA, attributes(node, [
3753
+ "checked",
3754
+ "citationId",
3755
+ "id",
3756
+ "kind",
3757
+ "source",
3758
+ "sourceHref",
3759
+ "sourceLabel",
3760
+ "title"
3761
+ ]), "ResearchClaim");
3762
+ return [
3763
+ ...caption(props.title, ` — ${CLAIM_LABELS[props.kind]}`),
3764
+ ...ctx.children(node),
3765
+ ...props.source === void 0 || props.citationId === void 0 ? [] : [html(`<a id="${props.citationId.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")}"></a>`)],
3766
+ ...props.source === void 0 ? [] : [para([txt("Source: "), link(props.sourceHref ?? `#${props.source}`, [txt(props.sourceLabel ?? props.source)])])],
3767
+ ...props.checked === void 0 ? [] : [para([txt(`Checked: ${props.checked}`)])]
3768
+ ];
3769
+ } }
3770
+ };
3771
+ //#endregion
3715
3772
  //#region src/ascii/shadcn.ts
3716
3773
  /** `ctx.inline` result wrapped so empty children fall back to `alt`. */
3717
3774
  const inlineOr = (node, ctx, alt) => {
@@ -4111,6 +4168,7 @@ const asciiRenderers = {
4111
4168
  ...investigationRenderers,
4112
4169
  ...outputRenderers,
4113
4170
  ...reportRenderers,
4171
+ ...researchRenderers,
4114
4172
  ...chartRenderers,
4115
4173
  ...layoutRenderers,
4116
4174
  ...formRenderers,
@@ -4551,7 +4609,7 @@ const openInBrowser = async (target) => {
4551
4609
  };
4552
4610
  //#endregion
4553
4611
  //#region src/client-js.ts
4554
- let cache$1;
4612
+ let cache;
4555
4613
  const buildClientJs = async () => {
4556
4614
  return (await build({
4557
4615
  absWorkingDir: pkgRoot,
@@ -4579,11 +4637,11 @@ const buildClientJs = async () => {
4579
4637
  * bundle is identical for every render in a process.
4580
4638
  */
4581
4639
  const clientJs = async () => {
4582
- cache$1 ??= buildClientJs();
4640
+ cache ??= buildClientJs();
4583
4641
  try {
4584
- return await cache$1;
4642
+ return await cache;
4585
4643
  } catch (error) {
4586
- cache$1 = void 0;
4644
+ cache = void 0;
4587
4645
  throw error;
4588
4646
  }
4589
4647
  };
@@ -4677,338 +4735,6 @@ addEventListener('DOMContentLoaded', function () {
4677
4735
  });
4678
4736
  `;
4679
4737
  //#endregion
4680
- //#region src/components/ui/toc.tsx
4681
- /**
4682
- * Table of contents with active-anchor tracking and depth-aware line styling.
4683
- * Adapted from the Crux UI / ABUI registry (`@abui/toc`), itself derived from
4684
- * fumadocs' TOC. Two list variants: `TOCItems` (straight line) and
4685
- * `ClerkTOCItems` (line that follows the heading hierarchy).
4686
- */
4687
- const ActiveAnchorContext = createContext([]);
4688
- const ScrollContext = createContext({ current: null });
4689
- const TOCContext = createContext([]);
4690
- /** The ids of visible anchors. */
4691
- const useActiveAnchors = () => useContext(ActiveAnchorContext);
4692
- const useTOCItems = () => useContext(TOCContext);
4693
- const mergeRefs = (...refs) => (value) => {
4694
- for (const ref of refs) if (typeof ref === "function") ref(value);
4695
- else if (ref !== null && ref !== void 0) ref.current = value;
4696
- };
4697
- /** Track which of the `watch` heading ids are on screen. */
4698
- const useAnchorObserver = (watch, single) => {
4699
- const observerRef = useRef(null);
4700
- const visibleRef = useRef(/* @__PURE__ */ new Set());
4701
- const [activeAnchor, setActiveAnchor] = useState([]);
4702
- const onChange = useCallback((entries) => {
4703
- const visible = visibleRef.current;
4704
- for (const entry of entries) if (entry.isIntersecting) visible.add(entry.target.id);
4705
- else visible.delete(entry.target.id);
4706
- if (visible.size > 0) {
4707
- const items = watch.filter((item) => visible.has(item));
4708
- setActiveAnchor(single ? items.slice(0, 1) : items);
4709
- return;
4710
- }
4711
- const viewTop = entries[0]?.rootBounds?.top ?? 0;
4712
- let fallback;
4713
- let min = -1;
4714
- for (const id of watch) {
4715
- const element = document.getElementById(id);
4716
- if (element === null) continue;
4717
- const d = Math.abs(viewTop - element.getBoundingClientRect().top);
4718
- if (min === -1 || d < min) {
4719
- fallback = element;
4720
- min = d;
4721
- }
4722
- }
4723
- setActiveAnchor(fallback === void 0 ? [] : [fallback.id]);
4724
- }, [watch, single]);
4725
- useEffect(() => {
4726
- if (observerRef.current !== null) return;
4727
- observerRef.current = new IntersectionObserver(onChange, {
4728
- rootMargin: "0px",
4729
- threshold: .98
4730
- });
4731
- return () => {
4732
- observerRef.current?.disconnect();
4733
- observerRef.current = null;
4734
- };
4735
- }, [onChange]);
4736
- useEffect(() => {
4737
- const observer = observerRef.current;
4738
- if (observer === null) return;
4739
- const elements = watch.flatMap((heading) => document.getElementById(heading) ?? []);
4740
- for (const element of elements) observer.observe(element);
4741
- return () => {
4742
- for (const element of elements) observer.unobserve(element);
4743
- };
4744
- }, [watch]);
4745
- return activeAnchor;
4746
- };
4747
- const TOCProvider = ({ toc, single = false, children }) => {
4748
- const headings = useMemo(() => toc.map((item) => item.url.split("#")[1] ?? ""), [toc]);
4749
- const activeAnchors = useAnchorObserver(headings, single);
4750
- return /* @__PURE__ */ jsx(TOCContext.Provider, {
4751
- value: toc,
4752
- children: /* @__PURE__ */ jsx(ActiveAnchorContext.Provider, {
4753
- value: activeAnchors,
4754
- children
4755
- })
4756
- });
4757
- };
4758
- /** Scrollable container; the first active item is kept in view. */
4759
- const TOCScrollArea = ({ ref, className, children, ...props }) => {
4760
- const viewRef = useRef(null);
4761
- return /* @__PURE__ */ jsx("div", {
4762
- className: cn("relative ms-px min-h-0 [scrollbar-width:none] overflow-auto [mask-image:linear-gradient(to_bottom,transparent,white_16px,white_calc(100%-16px),transparent)] py-3 text-sm", className),
4763
- ref: mergeRefs(viewRef, ref),
4764
- ...props,
4765
- children: /* @__PURE__ */ jsx(ScrollContext.Provider, {
4766
- value: viewRef,
4767
- children
4768
- })
4769
- });
4770
- };
4771
- const calcThumb = (container, active) => {
4772
- if (active.length === 0 || container.clientHeight === 0) return [0, 0];
4773
- let upper = Number.MAX_VALUE;
4774
- let lower = 0;
4775
- for (const item of active) {
4776
- const element = container.querySelector(`a[href="#${CSS.escape(item)}"]`);
4777
- if (element === null) continue;
4778
- const styles = getComputedStyle(element);
4779
- upper = Math.min(upper, element.offsetTop + Number.parseFloat(styles.paddingTop));
4780
- lower = Math.max(lower, element.offsetTop + element.clientHeight - Number.parseFloat(styles.paddingBottom));
4781
- }
4782
- return [upper, lower - upper];
4783
- };
4784
- const updateThumb = (element, [top, height]) => {
4785
- element.style.setProperty("--toc-top", `${top}px`);
4786
- element.style.setProperty("--toc-height", `${height}px`);
4787
- };
4788
- /** Active indicator that slides alongside the visible entries. */
4789
- const TocThumb = ({ containerRef, ...props }) => {
4790
- const thumbRef = useRef(null);
4791
- const active = useActiveAnchors();
4792
- useEffect(() => {
4793
- const container = containerRef.current;
4794
- if (container === null) return;
4795
- const onUpdate = () => {
4796
- if (thumbRef.current !== null) updateThumb(thumbRef.current, calcThumb(container, active));
4797
- };
4798
- const observer = new ResizeObserver(onUpdate);
4799
- observer.observe(container);
4800
- onUpdate();
4801
- return () => {
4802
- observer.disconnect();
4803
- };
4804
- }, [containerRef, active]);
4805
- return /* @__PURE__ */ jsx("div", {
4806
- ref: thumbRef,
4807
- role: "none",
4808
- ...props
4809
- });
4810
- };
4811
- const TOCItem = ({ ref, onActiveChange, children, ...props }) => {
4812
- const containerRef = useContext(ScrollContext);
4813
- const anchorRef = useRef(null);
4814
- const activeOrder = useActiveAnchors().indexOf(props.href.slice(1));
4815
- const isActive = activeOrder !== -1;
4816
- const shouldScroll = activeOrder === 0;
4817
- useLayoutEffect(() => {
4818
- const anchor = anchorRef.current;
4819
- const container = containerRef.current;
4820
- if (container === null || anchor === null || !shouldScroll || !anchor.isConnected) return;
4821
- const containerRect = container.getBoundingClientRect();
4822
- const anchorRect = anchor.getBoundingClientRect();
4823
- if (!(anchorRect.top < containerRect.top || anchorRect.bottom > containerRect.bottom)) return;
4824
- const anchorCenter = anchor.offsetTop - container.offsetTop + anchor.offsetHeight / 2;
4825
- container.scrollTo({
4826
- behavior: "smooth",
4827
- top: Math.max(0, anchorCenter - container.clientHeight / 2)
4828
- });
4829
- }, [containerRef, shouldScroll]);
4830
- useEffect(() => {
4831
- onActiveChange?.(isActive);
4832
- }, [isActive, onActiveChange]);
4833
- return /* @__PURE__ */ jsx("a", {
4834
- "data-active": isActive,
4835
- ref: mergeRefs(anchorRef, ref),
4836
- ...props,
4837
- children
4838
- });
4839
- };
4840
- const EMPTY_CLS = "rounded-lg border bg-card p-3 text-xs text-muted-foreground";
4841
- const ITEM_CLS = "py-1.5 text-sm text-muted-foreground transition-colors [overflow-wrap:anywhere] first:pt-0 last:pb-0 hover:text-accent-foreground data-[active=true]:text-primary";
4842
- const SimpleTOCItem = ({ item }) => /* @__PURE__ */ jsx(TOCItem, {
4843
- className: cn(ITEM_CLS, item.depth <= 2 && "ps-3", item.depth === 3 && "ps-6", item.depth >= 4 && "ps-8"),
4844
- href: item.url,
4845
- children: item.title
4846
- });
4847
- /** TOC list with a straight border line. */
4848
- const TOCItems = ({ ref, className, emptyText = "No Headings", ...props }) => {
4849
- const containerRef = useRef(null);
4850
- const items = useTOCItems();
4851
- if (items.length === 0) return /* @__PURE__ */ jsx("div", {
4852
- className: EMPTY_CLS,
4853
- children: emptyText
4854
- });
4855
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TocThumb, {
4856
- className: "bg-primary absolute top-(--toc-top) h-(--toc-height) w-px transition-all",
4857
- containerRef
4858
- }), /* @__PURE__ */ jsx("div", {
4859
- className: cn("border-foreground/10 flex flex-col border-s", className),
4860
- ref: mergeRefs(ref, containerRef),
4861
- ...props,
4862
- children: items.map((item) => /* @__PURE__ */ jsx(SimpleTOCItem, { item }, item.url))
4863
- })] });
4864
- };
4865
- const getItemOffset = (depth) => {
4866
- if (depth <= 2) return 14;
4867
- return depth === 3 ? 26 : 36;
4868
- };
4869
- const getLineOffset = (depth) => depth >= 3 ? 10 : 0;
4870
- /** One vertical segment per entry, shifted right for nested headings. */
4871
- const clerkLine = (container, items) => {
4872
- let width = 0;
4873
- let height = 0;
4874
- const d = [];
4875
- for (const [i, item] of items.entries()) {
4876
- const element = container.querySelector(`a[href="${CSS.escape(item.url)}"]`);
4877
- if (element === null) continue;
4878
- const styles = getComputedStyle(element);
4879
- const offset = getLineOffset(item.depth) + 1;
4880
- const top = element.offsetTop + Number.parseFloat(styles.paddingTop);
4881
- const bottom = element.offsetTop + element.clientHeight - Number.parseFloat(styles.paddingBottom);
4882
- width = Math.max(offset, width);
4883
- height = Math.max(height, bottom);
4884
- d.push(`${i === 0 ? "M" : "L"}${offset} ${top}`, `L${offset} ${bottom}`);
4885
- }
4886
- return {
4887
- height,
4888
- path: d.join(" "),
4889
- width: width + 1
4890
- };
4891
- };
4892
- const ClerkTOCItemElement = ({ item, upper = item.depth, lower = item.depth }) => {
4893
- const offset = getLineOffset(item.depth);
4894
- const upperOffset = getLineOffset(upper);
4895
- const lowerOffset = getLineOffset(lower);
4896
- return /* @__PURE__ */ jsxs(TOCItem, {
4897
- className: cn("relative", ITEM_CLS),
4898
- href: item.url,
4899
- style: { paddingInlineStart: getItemOffset(item.depth) },
4900
- children: [
4901
- offset === upperOffset ? null : /* @__PURE__ */ jsx("svg", {
4902
- "aria-hidden": "true",
4903
- className: "absolute start-0 -top-1.5 size-4 rtl:-scale-x-100",
4904
- viewBox: "0 0 16 16",
4905
- xmlns: "http://www.w3.org/2000/svg",
4906
- children: /* @__PURE__ */ jsx("line", {
4907
- className: "stroke-foreground/10",
4908
- strokeWidth: "1",
4909
- x1: upperOffset,
4910
- x2: offset,
4911
- y1: "0",
4912
- y2: "12"
4913
- })
4914
- }),
4915
- /* @__PURE__ */ jsx("div", {
4916
- className: cn("bg-foreground/10 absolute inset-y-0 w-px", offset !== upperOffset && "top-1.5", offset !== lowerOffset && "bottom-1.5"),
4917
- style: { insetInlineStart: offset }
4918
- }),
4919
- item.title
4920
- ]
4921
- });
4922
- };
4923
- /** TOC list whose line bends to follow the heading hierarchy. */
4924
- const ClerkTOCItems = ({ ref, className, emptyText = "No Headings", ...props }) => {
4925
- const containerRef = useRef(null);
4926
- const items = useTOCItems();
4927
- const [svg, setSvg] = useState();
4928
- useEffect(() => {
4929
- const container = containerRef.current;
4930
- if (container === null) return;
4931
- const onResize = () => {
4932
- if (container.clientHeight > 0) setSvg(clerkLine(container, items));
4933
- };
4934
- const observer = new ResizeObserver(onResize);
4935
- onResize();
4936
- observer.observe(container);
4937
- return () => {
4938
- observer.disconnect();
4939
- };
4940
- }, [items]);
4941
- if (items.length === 0) return /* @__PURE__ */ jsx("div", {
4942
- className: EMPTY_CLS,
4943
- children: emptyText
4944
- });
4945
- const mask = svg && {
4946
- height: svg.height,
4947
- maskImage: `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`)}")`,
4948
- width: svg.width
4949
- };
4950
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [mask ? /* @__PURE__ */ jsx("div", {
4951
- className: "absolute start-0 top-0 rtl:-scale-x-100",
4952
- style: mask,
4953
- children: /* @__PURE__ */ jsx(TocThumb, {
4954
- className: "bg-primary mt-(--toc-top) h-(--toc-height) transition-all",
4955
- containerRef
4956
- })
4957
- }) : null, /* @__PURE__ */ jsx("div", {
4958
- className: cn("flex flex-col", className),
4959
- ref: mergeRefs(containerRef, ref),
4960
- ...props,
4961
- children: items.map((item, i) => /* @__PURE__ */ jsx(ClerkTOCItemElement, {
4962
- item,
4963
- lower: items[i + 1]?.depth,
4964
- upper: items[i - 1]?.depth
4965
- }, item.url))
4966
- })] });
4967
- };
4968
- const PageTOC = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
4969
- className: cn("flex flex-col gap-3", className),
4970
- ...props
4971
- });
4972
- const PageTOCItems = ({ variant = "default", ...props }) => /* @__PURE__ */ jsx(TOCScrollArea, { children: variant === "clerk" ? /* @__PURE__ */ jsx(ClerkTOCItems, { ...props }) : /* @__PURE__ */ jsx(TOCItems, { ...props }) });
4973
- //#endregion
4974
- //#region src/ui/page-toc.tsx
4975
- /** `<aside>` holding the sidebar and the JSON island with its headings. */
4976
- const PAGE_TOC_ID = "mdxr-page-toc";
4977
- const PAGE_TOC_DATA_ID = "mdxr-page-toc-data";
4978
- /** Heading levels listed in the sidebar (h2–h3, like `:::toc`). */
4979
- const MIN_DEPTH = 2;
4980
- const MAX_DEPTH = 3;
4981
- /** A single entry isn't worth a sidebar. */
4982
- const MIN_ENTRIES = 2;
4983
- /** The headings the sidebar lists — empty when the page gets no sidebar. */
4984
- const pageTocHeadings = (headings) => {
4985
- const items = headings.filter((h) => h.depth >= MIN_DEPTH && h.depth <= MAX_DEPTH);
4986
- return items.length < MIN_ENTRIES ? [] : items;
4987
- };
4988
- /**
4989
- * Page-level "On this page" sidebar built on the Crux UI ToC: a clerk-style
4990
- * outline whose thumb follows the headings currently on screen. Rendered next
4991
- * to `<main>` by `htmlDocument` (not part of the MDX tree) and hydrated by its
4992
- * own root, so it works with or without the document hydration bundle.
4993
- */
4994
- const PageToc = ({ headings }) => {
4995
- const toc = useMemo(() => headings.map((h) => ({
4996
- depth: h.depth,
4997
- title: h.text,
4998
- url: `#${h.slug}`
4999
- })), [headings]);
5000
- return /* @__PURE__ */ jsx(TOCProvider, {
5001
- toc,
5002
- children: /* @__PURE__ */ jsxs(PageTOC, {
5003
- className: "min-h-0",
5004
- children: [/* @__PURE__ */ jsx("p", {
5005
- className: "text-muted-foreground m-0 text-xs font-semibold",
5006
- children: "On this page"
5007
- }), /* @__PURE__ */ jsx(PageTOCItems, { variant: "clerk" })]
5008
- })
5009
- });
5010
- };
5011
- //#endregion
5012
4738
  //#region src/html.ts
5013
4739
  /**
5014
4740
  * JS destined for an inline `<script>` element: neutralize the two byte
@@ -5044,10 +4770,6 @@ const iconSvg = (name) => {
5044
4770
  * `handleDocEvent`); THEME_JS restores the stored choice before paint.
5045
4771
  */
5046
4772
  const THEME_TOGGLE_HTML = `<button type="button" class="mdxr-theme" data-mdxr-theme data-mode="auto" title="Theme: auto" aria-label="Switch theme (current: auto)"><span class="mdxr-theme-i mdxr-theme-i-auto">${iconSvg("sun-moon")}</span><span class="mdxr-theme-i mdxr-theme-i-light">${iconSvg("sun")}</span><span class="mdxr-theme-i mdxr-theme-i-dark">${iconSvg("moon")}</span></button>`;
5047
- /** JSON for an inline `<script type="application/json">` island. */
5048
- const jsonIsland = (data) => JSON.stringify(data).replaceAll("<", String.raw`\u003c`);
5049
- const pageTocHtml = (toc) => toc === void 0 ? "" : `<aside id="${PAGE_TOC_ID}" aria-label="On this page" class="fixed top-20 left-[calc(50%+25.5rem)] hidden max-h-[calc(100vh-7rem)] w-52 flex-col xl:flex print:hidden">${toc.html}</aside>
5050
- <script type="application/json" id="${PAGE_TOC_DATA_ID}">${jsonIsland(toc.headings)}<\/script>`;
5051
4773
  const htmlDocument = (o) => `<!doctype html>
5052
4774
  <html lang="en">
5053
4775
  <head>
@@ -5062,12 +4784,10 @@ ${o.needsKatex === true ? `<link rel="stylesheet" href="${KATEX_CDN_URL}">` : ""
5062
4784
  <body class="bg-white text-neutral-900 antialiased dark:bg-neutral-950 dark:text-neutral-100">
5063
4785
  ${THEME_TOGGLE_HTML}
5064
4786
  <main id="mdxr-root" class="prose prose-neutral dark:prose-invert mx-auto max-w-3xl px-6 py-10">${o.body}</main>
5065
- ${pageTocHtml(o.pageToc)}
5066
4787
  <script>${inlineScript(o.clientJs)}<\/script>
5067
4788
  ${o.needsMermaid ? `<script type="module">${inlineScript(MERMAID_JS)}<\/script>` : ""}
5068
4789
  ${o.liveReload === true ? `<script>${inlineScript(LIVE_RELOAD_JS)}<\/script>` : ""}
5069
4790
  ${o.hydrateJs === void 0 ? "" : `<script>${inlineScript(o.hydrateJs)}<\/script>`}
5070
- ${o.pageTocJs === void 0 ? "" : `<script>${inlineScript(o.pageTocJs)}<\/script>`}
5071
4791
  </body>
5072
4792
  </html>
5073
4793
  `;
@@ -5428,8 +5148,7 @@ const scanMdxrImports = async (importer) => {
5428
5148
  const EXTRA_MODULES = {
5429
5149
  builtinComponents: path.join(srcDir, "ui/index.js"),
5430
5150
  defineConfig: path.join(srcDir, "config.js"),
5431
- mountDocument: path.join(srcDir, "hydrate-runtime.js"),
5432
- mountPageToc: path.join(srcDir, "page-toc-runtime.js")
5151
+ mountDocument: path.join(srcDir, "hydrate-runtime.js")
5433
5152
  };
5434
5153
  /**
5435
5154
  * The virtual module's `v` export. `export * as v` would materialize the
@@ -5527,7 +5246,6 @@ const buildHydrateScript = async (spec) => {
5527
5246
  };
5528
5247
  const mountDocument = bind("mountDocument");
5529
5248
  const planHeader = spec.header === void 0 ? "undefined" : bind("PlanHeader");
5530
- const mountPageToc = spec.pageToc === true ? `${bind("mountPageToc")}();` : "";
5531
5249
  for (const name of spec.usedComponents) {
5532
5250
  if (!Object.hasOwn(builtinComponents, name)) continue;
5533
5251
  const exported = name === "pre" ? "Pre" : name;
@@ -5546,7 +5264,6 @@ ${mountDocument}({
5546
5264
  planHeader: ${planHeader},
5547
5265
  userModule: ${spec.componentsPath === void 0 ? "undefined" : "__mdxrUser"},
5548
5266
  });
5549
- ${mountPageToc}
5550
5267
  `;
5551
5268
  const result = await build({
5552
5269
  absWorkingDir: pkgRoot,
@@ -6048,7 +5765,6 @@ const mdxToHtml = async (source, components, filePath = "document.mdx", opts = {
6048
5765
  context,
6049
5766
  fileLinks: Object.fromEntries(fileLinks),
6050
5767
  frontmatter,
6051
- headings: compiled.data.mdxrHeadings ?? [],
6052
5768
  hydrated,
6053
5769
  renderWithHeader: (header) => {
6054
5770
  try {
@@ -6068,44 +5784,6 @@ const mdxToHtml = async (source, components, filePath = "document.mdx", opts = {
6068
5784
  };
6069
5785
  };
6070
5786
  //#endregion
6071
- //#region src/page-toc-js.ts
6072
- let cache;
6073
- const buildPageTocJs = async () => {
6074
- return (await build({
6075
- absWorkingDir: pkgRoot,
6076
- bundle: true,
6077
- define: { "process.env.NODE_ENV": "\"production\"" },
6078
- format: "iife",
6079
- jsx: "automatic",
6080
- jsxImportSource: "react",
6081
- logLevel: "silent",
6082
- minify: true,
6083
- platform: "browser",
6084
- stdin: {
6085
- contents: "import { mountPageToc } from \"./page-toc-runtime.js\"; mountPageToc();",
6086
- loader: "js",
6087
- resolveDir: path.join(pkgRoot, "src"),
6088
- sourcefile: "mdxr-page-toc.js"
6089
- },
6090
- target: "es2022",
6091
- write: false
6092
- })).outputFiles[0]?.text ?? "";
6093
- };
6094
- /**
6095
- * Standalone bundle that hydrates the sidebar ToC — used only when the
6096
- * document has no hydration bundle of its own (that one mounts the ToC with
6097
- * the React copy it already ships). Memoized: identical for every render.
6098
- */
6099
- const pageTocJs = async () => {
6100
- cache ??= buildPageTocJs();
6101
- try {
6102
- return await cache;
6103
- } catch (error) {
6104
- cache = void 0;
6105
- throw error;
6106
- }
6107
- };
6108
- //#endregion
6109
5787
  //#region src/assets/css.ts
6110
5788
  /** Base CSS appended after Tailwind utilities (covers what utilities can't). */
6111
5789
  const BASE_CSS = `
@@ -6115,6 +5793,20 @@ const BASE_CSS = `
6115
5793
  .task-list-item { list-style: none; }
6116
5794
  ul.contains-task-list { padding-left: 1.25rem; }
6117
5795
  .task-list-item input[type='checkbox'] { margin-right: 0.4em; }
5796
+ /* Graph fitting is native SVG sizing: readable minimum scale on screen,
5797
+ * full-width containment in print, and a keyboard-operable actual-size toggle. */
5798
+ .mdxr-graph-view:has(.mdxr-graph-actual:checked) .mdxr-graph-image {
5799
+ max-width: none !important;
5800
+ }
5801
+ .mdxr-graph-tools:focus-within { outline: 2px solid rgb(14 165 233); outline-offset: -2px; }
5802
+ @media print {
5803
+ .mdxr-graph-tools { display: none; }
5804
+ .mdxr-graph-scroll { overflow: visible; }
5805
+ .mdxr-graph-view:has(.mdxr-graph-actual:checked) .mdxr-graph-image,
5806
+ .mdxr-graph-view .mdxr-graph-image {
5807
+ max-width: 100% !important; min-width: 0 !important; height: auto !important;
5808
+ }
5809
+ }
6118
5810
  /* --- Interaction feedback -------------------------------------------
6119
5811
  * Every [data-copy] button carries an idle and a done icon
6120
5812
  * (.mdxr-copy-idle/.mdxr-copy-done); the delegated event handler
@@ -6391,7 +6083,7 @@ details[open] > summary .mdxr-chev { transform: rotate(90deg); }
6391
6083
  .dark .mdxr-toc-body > ul > li > a { color: rgb(212 212 212); }
6392
6084
  .mdxr-toc-body > ul > li > p > a::before,
6393
6085
  .mdxr-toc-body > ul > li > a::before {
6394
- content: counter(mdxr-toc); min-width: 1em;
6086
+ content: counter(mdxr-toc); min-width: 1em; flex-shrink: 0; white-space: nowrap;
6395
6087
  font-size: 0.72rem; font-weight: 400; font-variant-numeric: tabular-nums;
6396
6088
  color: rgb(163 163 163);
6397
6089
  }
@@ -6403,6 +6095,27 @@ details[open] > summary .mdxr-chev { transform: rotate(90deg); }
6403
6095
  }
6404
6096
  .dark .mdxr-toc-body ul ul { border-color: rgb(64 64 64); }
6405
6097
  .mdxr-toc-body ul ul a { font-size: 0.8125rem; padding: 0.2rem 0.45rem; }
6098
+ /* A 64rem viewport fits the usual 45rem article, a 14rem ToC, a 2rem
6099
+ * gap, and 1.5rem outer gutters. Only the first document-level ToC docks;
6100
+ * additional or nested tables stay in the document flow. Print stays inline. */
6101
+ @media screen and (min-width: 64rem) {
6102
+ #mdxr-root:has(> .mdxr-toc, > article:only-of-type > .mdxr-toc) {
6103
+ max-width: 64rem;
6104
+ padding-right: 17.5rem;
6105
+ }
6106
+ #mdxr-root > .mdxr-toc:nth-child(1 of .mdxr-toc),
6107
+ #mdxr-root:not(:has(> .mdxr-toc)) > article:only-of-type > .mdxr-toc:nth-child(1 of .mdxr-toc) {
6108
+ position: fixed;
6109
+ top: 4rem;
6110
+ right: max(1.5rem, calc((100vw - 64rem) / 2 + 1.5rem));
6111
+ width: 14rem;
6112
+ max-height: calc(100dvh - 5.5rem);
6113
+ margin: 0;
6114
+ overflow-y: auto;
6115
+ overscroll-behavior: contain;
6116
+ overflow-wrap: anywhere;
6117
+ }
6118
+ }
6406
6119
  /* Ask: native form controls stay interactive without hydration. The real
6407
6120
  * inputs are visually hidden; state is styled through :checked/~ siblings. */
6408
6121
  .mdxr-choice {
@@ -6613,7 +6326,6 @@ const buildHydrateBundle = async (args) => {
6613
6326
  fileLinks: args.fileLinks,
6614
6327
  header: args.headerProps,
6615
6328
  now: args.now,
6616
- pageToc: args.pageToc,
6617
6329
  usedComponents: args.usedComponents,
6618
6330
  usedIcons: args.usedIcons
6619
6331
  });
@@ -6650,23 +6362,6 @@ const frontmatterHeader = (fm, body) => {
6650
6362
  version: fm("version")
6651
6363
  };
6652
6364
  };
6653
- /** Sidebar ToC entries — frontmatter `toc: false` opts the page out. */
6654
- const sidebarHeadings = (frontmatter, headings) => frontmatter.toc === false || frontmatter.toc === "false" ? [] : pageTocHeadings(headings);
6655
- /**
6656
- * SSR markup for the sidebar ToC plus the script that hydrates it. The
6657
- * document bundle mounts the ToC itself when there is one; otherwise the
6658
- * standalone bundle does. Without JS the sidebar stays plain anchor links.
6659
- */
6660
- const renderPageToc = async (headings, interactive, hasDocumentBundle) => {
6661
- if (headings.length === 0) return {};
6662
- return {
6663
- pageToc: {
6664
- headings,
6665
- html: (interactive ? renderToString : renderToStaticMarkup)(createElement(PageToc, { headings }))
6666
- },
6667
- pageTocJs: interactive && !hasDocumentBundle ? await pageTocJs() : void 0
6668
- };
6669
- };
6670
6365
  /** Render MDX source text to a standalone HTML document. */
6671
6366
  const render = async (source, opts = {}) => {
6672
6367
  const dir = path.resolve(opts.dir ?? process.cwd());
@@ -6679,7 +6374,7 @@ const render = async (source, opts = {}) => {
6679
6374
  ...builtinComponents,
6680
6375
  ...user.components
6681
6376
  };
6682
- const { body, code, fileLinks, frontmatter, headings, renderedAt, renderWithHeader, usedComponents, usedIcons } = await mdxToHtml(source, components, filePath, {
6377
+ const { body, code, fileLinks, frontmatter, renderedAt, renderWithHeader, usedComponents, usedIcons } = await mdxToHtml(source, components, filePath, {
6683
6378
  editor: config.editor,
6684
6379
  hydrate: opts.hydrate
6685
6380
  });
@@ -6728,7 +6423,6 @@ const render = async (source, opts = {}) => {
6728
6423
  ];
6729
6424
  const { css, dependencies } = await buildCss(sources, config.themePath);
6730
6425
  opts.onDependencies?.(dependencies);
6731
- const tocHeadings = sidebarHeadings(frontmatter, headings);
6732
6426
  const [js, hydrateJs] = await Promise.all([clientJs(), buildHydrateBundle({
6733
6427
  code,
6734
6428
  config,
@@ -6736,11 +6430,9 @@ const render = async (source, opts = {}) => {
6736
6430
  headerProps,
6737
6431
  hydrate: opts.hydrate,
6738
6432
  now: renderedAt,
6739
- pageToc: tocHeadings.length > 0,
6740
6433
  usedComponents,
6741
6434
  usedIcons: docIcons
6742
6435
  })]);
6743
- const { pageToc, pageTocJs: tocJs } = await renderPageToc(tocHeadings, opts.hydrate ?? true, hydrateJs !== void 0);
6744
6436
  return htmlDocument({
6745
6437
  body: docBody,
6746
6438
  clientJs: js,
@@ -6749,8 +6441,6 @@ const render = async (source, opts = {}) => {
6749
6441
  liveReload: opts.liveReload,
6750
6442
  needsKatex: /class="[^"]*katex/u.test(docBody),
6751
6443
  needsMermaid: /class="[^"]*mermaid/u.test(docBody),
6752
- pageToc,
6753
- pageTocJs: tocJs,
6754
6444
  title
6755
6445
  });
6756
6446
  };