nimbus-docs 0.1.6 → 0.1.8
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 +23 -0
- package/dist/cli/index.js +20 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/content.d.ts +62 -6
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +11 -5
- package/dist/content.js.map +1 -1
- package/dist/{diagnostic-C6OaBe_o.d.ts → diagnostic-ewiZxpSO.d.ts} +4 -1
- package/dist/diagnostic-ewiZxpSO.d.ts.map +1 -0
- package/dist/index.d.ts +52 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +349 -159
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +350 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +782 -0
- package/dist/react.js.map +1 -0
- package/dist/{rules-DnAP-j89.js → rules-B7o0k3TA.js} +249 -2
- package/dist/rules-B7o0k3TA.js.map +1 -0
- package/dist/schemas.d.ts +85 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +32 -6
- package/dist/schemas.js.map +1 -1
- package/dist/strict-keys-fbKKxxKL.js +141 -0
- package/dist/strict-keys-fbKKxxKL.js.map +1 -0
- package/dist/types.d.ts +49 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +37 -19
- package/dist/diagnostic-C6OaBe_o.d.ts.map +0 -1
- package/dist/rules-DnAP-j89.js.map +0 -1
- package/dist/strict-keys-BiXiT3pq.js +0 -35
- package/dist/strict-keys-BiXiT3pq.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../src/react/cn.ts","../src/react/registry.ts","../src/react/diagram.tsx","../src/react/hooks/use-phase.ts","../src/react/hooks/use-measure.ts","../src/react/hooks/use-tab-indicator.ts","../src/react/hooks/use-ref-setters.ts","../src/react/hooks/use-media-query.ts","../src/react/geometry.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Compose class names with Tailwind conflict resolution. Vendored into\n * lab/diagram so the framework-surface files don't depend on apps/www's\n * `@/lib/cn` alias — keeps the lift to `nimbus-docs/react` verbatim.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","/**\n * Page-scoped Diagram registry.\n *\n * Module-level singleton — shared across Astro islands because Vite\n * de-duplicates module imports per page (both in dev HMR and in prod\n * via shared chunks). React Context cannot bridge separate islands;\n * a plain singleton can.\n *\n * Subscribed via useSyncExternalStore from <DiagramPauseAll>.\n */\n\ninterface RegistryEntry {\n id: string;\n /** Set the user-paused flag directly (idempotent). */\n setPaused: (paused: boolean) => void;\n /** Read the current user-paused flag at call time. */\n userPaused: () => boolean;\n}\n\nclass DiagramRegistry {\n private entries = new Map<string, RegistryEntry>();\n private listeners = new Set<() => void>();\n /** Bumped on every membership change — useSyncExternalStore snapshot key. */\n private version = 0;\n\n register(entry: RegistryEntry): () => void {\n this.entries.set(entry.id, entry);\n this.version++;\n this.notify();\n return () => {\n this.entries.delete(entry.id);\n this.version++;\n this.notify();\n };\n }\n\n /**\n * Pause everything when at least one diagram is unpaused; resume\n * everything otherwise. A naive per-entry toggle would *invert* mixed\n * states — resuming the diagrams a reader had deliberately paused.\n */\n toggleAll(): void {\n const anyUnpaused = [...this.entries.values()].some((e) => !e.userPaused());\n for (const entry of this.entries.values()) {\n entry.setPaused(anyUnpaused);\n }\n }\n\n // Arrow fields so these stay bound when passed to useSyncExternalStore.\n count = (): number => this.entries.size;\n\n getVersion = (): number => this.version;\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n private notify(): void {\n for (const listener of this.listeners) listener();\n }\n}\n\nexport const diagramRegistry = new DiagramRegistry();\n","\"use client\";\n\nimport {\n Component,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useReducer,\n useRef,\n useState,\n type CSSProperties,\n type ErrorInfo,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"./cn\";\nimport { diagramRegistry } from \"./registry\";\n\n// ─── Types ──────────────────────────────────────────────────\n\nexport type Depth = \"intuition\" | \"working\" | \"mechanism\";\nexport type ReducedMotionMode = \"respect\" | \"ignore\";\nexport type DiagramPhase = \"idle\" | \"ready\" | \"playing\" | \"paused\";\n\nexport interface DiagramContextValue {\n /** Stable per-instance id from useId. */\n id: string;\n /** Derived lifecycle state. */\n phase: DiagramPhase;\n /** True when the diagram should be advancing animation. */\n playing: boolean;\n /** Element intersects the viewport (direct IntersectionObserver). */\n visible: boolean;\n /** Browser tab is visible (Page Visibility API). */\n tabVisible: boolean;\n /** Reduced-motion is in effect (OS + reducedMotion=\"respect\" mode). */\n reducedMotion: boolean;\n /** Reader's depth setting. Stub until the depth dial ships. */\n depth: Depth;\n /** Theme tokens. Stub until row 4 wires real values. */\n theme: { id: string };\n /** Trigger a reset — bumps the subtree key; children's useState resets. */\n reset: () => void;\n /** Toggle play/pause from user action. */\n toggle: () => void;\n}\n\n// ─── Lifecycle reducer ──────────────────────────────────────\n\ninterface ReducerState {\n /** Margin-observer pre-warm has fired and scroll has settled. */\n ready: boolean;\n /** Direct IntersectionObserver says the diagram intersects the viewport. */\n visible: boolean;\n /** Page Visibility API says the tab is visible. */\n tabVisible: boolean;\n /** OS prefers-reduced-motion is set. */\n reducedMotion: boolean;\n /** User clicked Pause; persists across visibility changes. */\n userPaused: boolean;\n}\n\ntype ReducerEvent =\n | { type: \"READY\" }\n | { type: \"VISIBILITY\"; visible: boolean }\n | { type: \"TAB_VISIBILITY\"; tabVisible: boolean }\n | { type: \"REDUCED_MOTION\"; reduced: boolean }\n | { type: \"TOGGLE\" }\n | { type: \"SET_PAUSED\"; paused: boolean };\n\nfunction reducer(state: ReducerState, event: ReducerEvent): ReducerState {\n switch (event.type) {\n case \"READY\":\n return state.ready ? state : { ...state, ready: true };\n case \"VISIBILITY\":\n return state.visible === event.visible ? state : { ...state, visible: event.visible };\n case \"TAB_VISIBILITY\":\n return state.tabVisible === event.tabVisible\n ? state\n : { ...state, tabVisible: event.tabVisible };\n case \"REDUCED_MOTION\":\n return state.reducedMotion === event.reduced\n ? state\n : { ...state, reducedMotion: event.reduced };\n case \"TOGGLE\":\n return { ...state, userPaused: !state.userPaused };\n case \"SET_PAUSED\":\n return state.userPaused === event.paused\n ? state\n : { ...state, userPaused: event.paused };\n }\n}\n\nfunction derivePhase(\n state: ReducerState,\n reducedMotionMode: ReducedMotionMode,\n pauseWhenOffscreen: boolean,\n): DiagramPhase {\n if (!state.ready) return \"idle\";\n if (reducedMotionMode === \"respect\" && state.reducedMotion) return \"paused\";\n if (pauseWhenOffscreen && !state.visible) return \"paused\";\n if (!state.tabVisible) return \"paused\";\n if (state.userPaused) return \"paused\";\n return \"playing\";\n}\n\n// ─── Context ──────────────────────────────────────────────\n\nconst DiagramContext = createContext<DiagramContextValue | null>(null);\n\nexport function useDiagram(): DiagramContextValue | null {\n return useContext(DiagramContext);\n}\n\nconst DEFAULT_CONTEXT: DiagramContextValue = {\n id: \"__no_wrapper__\",\n phase: \"playing\",\n playing: true,\n visible: true,\n tabVisible: true,\n reducedMotion: false,\n depth: \"working\",\n theme: { id: \"default\" },\n reset: () => {},\n toggle: () => {},\n};\n\nconst WARNED = new Set<string>();\n\n/**\n * Returns the wrapper context, or a default + one-time dev warning when\n * called outside a `<Diagram>`. Used by every hook in nimbus-docs/react\n * so authors can prototype without forgetting the wrapper.\n */\nexport function useDiagramOrDefault(hookName: string): DiagramContextValue {\n const ctx = useContext(DiagramContext);\n if (ctx) return ctx;\n if (\n typeof process !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\" &&\n !WARNED.has(hookName)\n ) {\n WARNED.add(hookName);\n // eslint-disable-next-line no-console\n console.warn(\n `[nimbus-docs/react] ${hookName} called outside a <Diagram> wrapper. ` +\n \"Defaults: playing=true, visible=true, reducedMotion=false. \" +\n \"Wrap with <Diagram> for off-screen / reduced-motion / tab-hidden gating.\",\n );\n }\n return DEFAULT_CONTEXT;\n}\n\n// ─── Diagram component ──────────────────────────────────────\n\nconst MARGIN_ROOTMARGIN_VH = 2;\n/**\n * Debounce window for READY dispatch — only applied when scroll activity\n * was observed in the last `SCROLL_VELOCITY_WINDOW_MS`. On a settled page\n * (initial load, no scrolling), READY fires immediately.\n *\n * Lab finding: the spec's flat 500ms gate caused a visible delay on\n * initial render. Scroll-aware gating preserves the mass-init protection\n * for fast scrolls while keeping page-load latency at zero.\n */\nconst SCROLL_IDLE_MS = 200;\nconst SCROLL_VELOCITY_WINDOW_MS = 200;\n\nexport interface DiagramProps {\n children: ReactNode;\n fallback?: string;\n pauseWhenOffscreen?: boolean;\n reducedMotion?: ReducedMotionMode;\n /** Forwarded; Astro's client:* directive dictates the actual hydration timing. */\n hydration?: \"visible\" | \"idle\" | \"load\";\n /** Wrap children in an error boundary that exposes a Reset button on render failure. Default: true. */\n errorBoundary?: boolean;\n /** Listen for space (toggle) / r (reset) keypresses bubbling from inside the region. Skipped when target is an interactive element. Default: true. */\n keyboard?: boolean;\n /** ARIA region label. */\n label?: string;\n className?: string;\n}\n\n// ─── Error boundary ─────────────────────────────────────────\n\ninterface ErrorBoundaryProps {\n children: ReactNode;\n /** Called when the user clicks Reset in the fallback. Should bump the resetKey on the keyed subtree. */\n onReset: () => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors thrown by the diagram subtree. React 19 still\n * requires class components for error boundaries — no hook equivalent\n * yet. Falls back to a static \"Diagram failed\" message + a Reset button\n * that both clears local error state AND bumps the outer resetKey so\n * children remount fresh.\n */\nclass DiagramErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n override state: ErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: ErrorInfo): void {\n if (typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\"[nimbus-docs/react] Diagram render error:\", error, info);\n }\n }\n\n private handleReset = (): void => {\n this.setState({ error: null });\n this.props.onReset();\n };\n\n override render(): ReactNode {\n if (this.state.error) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-col items-start gap-2 rounded-md border border-red-300 bg-red-50 p-3 text-sm text-red-900 dark:border-red-800 dark:bg-red-950 dark:text-red-200\"\n >\n <span className=\"font-medium\">Diagram failed to render.</span>\n <button\n type=\"button\"\n onClick={this.handleReset}\n className=\"rounded border border-red-400 px-2 py-0.5 text-xs font-medium hover:bg-red-100 dark:border-red-700 dark:hover:bg-red-900\"\n >\n Reset\n </button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── A11y helpers ────────────────────────────────────────────\n\n/** Inline visually-hidden style — avoids depending on `.sr-only` Tailwind class. */\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clip: \"rect(0,0,0,0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/** True when the keypress originated from a native interactive element — skip our shortcut to let the element's own behaviour fire. */\nfunction isInteractiveTarget(t: EventTarget | null): boolean {\n if (!t || !(t instanceof HTMLElement)) return false;\n const tag = t.tagName;\n if (tag === \"BUTTON\" || tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") {\n return true;\n }\n if (tag === \"A\" && t.hasAttribute(\"href\")) return true;\n if (t.isContentEditable) return true;\n return false;\n}\n\nfunction DiagramRoot(props: DiagramProps) {\n const {\n children,\n pauseWhenOffscreen = true,\n reducedMotion: reducedMotionMode = \"respect\",\n errorBoundary = true,\n keyboard = true,\n label,\n className,\n } = props;\n\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const [resetKey, setResetKey] = useState(0);\n\n const [state, dispatch] = useReducer(reducer, {\n ready: false,\n visible: false,\n tabVisible: true,\n reducedMotion: false,\n userPaused: false,\n });\n\n // ─── visibilitychange (tab visibility) — env-aware globals\n useEffect(() => {\n const win = rootRef.current?.ownerDocument.defaultView ?? window;\n const doc = win.document;\n const onChange = () =>\n dispatch({ type: \"TAB_VISIBILITY\", tabVisible: doc.visibilityState === \"visible\" });\n onChange();\n doc.addEventListener(\"visibilitychange\", onChange);\n return () => doc.removeEventListener(\"visibilitychange\", onChange);\n }, []);\n\n // ─── prefers-reduced-motion — env-aware globals\n useEffect(() => {\n const win = rootRef.current?.ownerDocument.defaultView ?? window;\n const mq = win.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const onChange = (e: MediaQueryListEvent | MediaQueryList) =>\n dispatch({ type: \"REDUCED_MOTION\", reduced: e.matches });\n onChange(mq);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n }, []);\n\n // ─── Two IntersectionObservers (pre-warm + direct)\n useEffect(() => {\n const el = rootRef.current;\n if (!el || typeof IntersectionObserver === \"undefined\") return;\n const win = el.ownerDocument.defaultView ?? window;\n const vh = win.innerHeight || 800;\n\n // Scroll-velocity tracker. The margin observer debounces READY only\n // when scroll happened recently; on a settled page, READY fires\n // immediately.\n let lastScrollAt = 0;\n const onScroll = () => {\n lastScrollAt = Date.now();\n };\n win.addEventListener(\"scroll\", onScroll, { passive: true });\n\n let scrollIdleTimer: ReturnType<typeof setTimeout> | null = null;\n let pendingReady = false;\n\n const marginObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const sinceScroll = Date.now() - lastScrollAt;\n if (sinceScroll > SCROLL_VELOCITY_WINDOW_MS) {\n dispatch({ type: \"READY\" });\n continue;\n }\n pendingReady = true;\n if (scrollIdleTimer) clearTimeout(scrollIdleTimer);\n scrollIdleTimer = setTimeout(() => {\n if (pendingReady) {\n dispatch({ type: \"READY\" });\n pendingReady = false;\n }\n }, SCROLL_IDLE_MS);\n }\n }\n },\n { rootMargin: `${MARGIN_ROOTMARGIN_VH * vh}px 0px ${MARGIN_ROOTMARGIN_VH * vh}px 0px` },\n );\n marginObserver.observe(el);\n\n const directObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n dispatch({ type: \"VISIBILITY\", visible: entry.isIntersecting });\n }\n },\n { rootMargin: \"0px\", threshold: [0, 1.0] },\n );\n directObserver.observe(el);\n\n return () => {\n marginObserver.disconnect();\n directObserver.disconnect();\n win.removeEventListener(\"scroll\", onScroll);\n if (scrollIdleTimer) clearTimeout(scrollIdleTimer);\n };\n }, []);\n\n // ─── Event handlers (not effects — see React design rules)\n const toggle = useCallback(() => dispatch({ type: \"TOGGLE\" }), []);\n // Reset also clears the user-paused flag: resetting a paused diagram\n // would otherwise remount children into a frozen frame, making the\n // Reset click appear to do nothing.\n const reset = useCallback(() => {\n setResetKey((k) => k + 1);\n dispatch({ type: \"SET_PAUSED\", paused: false });\n }, []);\n\n const handleKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (!keyboard) return;\n if (isInteractiveTarget(event.target)) return;\n if (event.key === \" \" || event.code === \"Space\") {\n event.preventDefault();\n toggle();\n } else if (event.key === \"r\" || event.key === \"R\") {\n event.preventDefault();\n reset();\n }\n },\n [keyboard, toggle, reset],\n );\n\n // ─── Page-scoped registry membership\n // `userPaused` is read through a ref at call time so the registry can\n // make a coherent pause-all/resume-all decision across entries without\n // re-registering on every state change.\n const userPausedRef = useRef(state.userPaused);\n userPausedRef.current = state.userPaused;\n useEffect(() => {\n return diagramRegistry.register({\n id,\n setPaused: (paused) => dispatch({ type: \"SET_PAUSED\", paused }),\n userPaused: () => userPausedRef.current,\n });\n }, [id]);\n\n // ─── Derived\n const phase = derivePhase(state, reducedMotionMode, pauseWhenOffscreen);\n const playing = phase === \"playing\";\n const reducedMotionEffective =\n reducedMotionMode === \"respect\" ? state.reducedMotion : false;\n const visibleEffective = pauseWhenOffscreen ? state.visible : true;\n\n // ─── Context value (useMemo for re-render perf only, not for semantics)\n const ctx: DiagramContextValue = useMemo(\n () => ({\n id,\n phase,\n playing,\n visible: visibleEffective,\n tabVisible: state.tabVisible,\n reducedMotion: reducedMotionEffective,\n depth: \"working\",\n theme: { id: \"default\" },\n reset,\n toggle,\n }),\n [id, phase, playing, visibleEffective, state.tabVisible, reducedMotionEffective, reset, toggle],\n );\n\n // Live-region content: only narrate the two states a reader cares about\n // (\"playing\", \"paused\"). Idle/ready render as empty string — aria-live\n // doesn't announce initial content, only changes, so silence on mount.\n const liveText =\n phase === \"playing\" ? \"Diagram playing\" :\n phase === \"paused\" ? \"Diagram paused\" :\n \"\";\n\n const renderBody = (\n <div className=\"diagram-render\" key={resetKey}>\n {children}\n </div>\n );\n\n return (\n <DiagramContext.Provider value={ctx}>\n <div\n ref={rootRef}\n data-nb-diagram\n data-diagram-id={id}\n data-phase={phase}\n data-playing={String(playing)}\n data-visible={String(visibleEffective)}\n data-tab-visible={String(state.tabVisible)}\n data-reduced-motion={String(reducedMotionEffective)}\n aria-label={label}\n // ARIA regions require an accessible name — only claim the role\n // when a label was provided.\n role={label ? \"region\" : undefined}\n // Focusable so space/r are reachable on cards with no interactive\n // children; without a tab stop the keydown handler never fires.\n tabIndex={keyboard ? 0 : undefined}\n onKeyDown={handleKeyDown}\n className={cn(\"flex flex-col\", className)}\n >\n <span aria-live=\"polite\" role=\"status\" style={SR_ONLY}>\n {liveText}\n </span>\n {errorBoundary ? (\n <DiagramErrorBoundary onReset={reset}>{renderBody}</DiagramErrorBoundary>\n ) : (\n renderBody\n )}\n </div>\n </DiagramContext.Provider>\n );\n}\n\n// ─── Public component ───────────────────────────────────────\n\nexport const Diagram = DiagramRoot;\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useDiagramOrDefault } from \"../diagram\";\n\nexport interface UsePhaseStepBase {\n /** Stable step id; appears as `current`. */\n id: string;\n /** Milliseconds to hold this step before advancing. */\n hold: number;\n}\n\nexport interface UsePhaseStep<\n C extends Record<string, unknown> = Record<string, never>,\n D = unknown,\n> extends UsePhaseStepBase {\n /**\n * Predicate gate. When false, this step is skipped for the current cycle.\n * Receives `{ cycle, current, ...context }` where `current` is the id of\n * the previous step that survived the predicate pass (empty string at\n * cycle start).\n */\n when?: (ctx: { cycle: number; current: string } & C) => boolean;\n /**\n * Arbitrary payload surfaced as `data` while this step holds — e.g.\n * which node/edge lights up. Replaces per-card `ACTIVE_MAP` lookup\n * tables; `data` is `null` when idle or when an autoplaying walker is\n * frozen by reduced motion.\n */\n data?: D;\n}\n\nexport interface UsePhaseOptions<\n C extends Record<string, unknown> = Record<string, never>,\n D = unknown,\n> {\n steps: UsePhaseStep<C, D>[];\n /** Values made available to each step's `when` predicate. */\n context?: C;\n /** When true (default), the sequence restarts after the last step. */\n loop?: boolean;\n /**\n * When true (default) the walker runs as soon as the diagram plays.\n * When false it starts idle (`current === \"\"`) and runs once per\n * `start()` call — the shape for click-triggered one-shot sequences.\n */\n autoplay?: boolean;\n}\n\nexport interface UsePhaseReturn<D = unknown> {\n /** Id of the currently-active step. Empty string when idle or `steps` is empty. */\n current: string;\n /** Index into the *filtered* sequence for the current cycle. */\n index: number;\n /** Completed passes through the sequence. Starts at 0. */\n cycle: number;\n /** The active step's `data` payload. `null` when idle or motion-frozen. */\n data: D | null;\n /** True while the walker is active. Always true when `autoplay`. */\n running: boolean;\n /** Begin a run from the first step. No-op while already running. */\n start: () => void;\n /** Advance one step (or wrap to next cycle if at the end). */\n advance: () => void;\n /** Jump to a named step in the current cycle's sequence. Wakes an idle walker. */\n goto: (id: string) => void;\n /** Reset cycle and index to 0 (and return to idle when `autoplay: false`). */\n reset: () => void;\n}\n\n/**\n * Predicate-gated phase walker. Reads `playing` / `visible` / `tabVisible`\n * / `reducedMotion` from the surrounding `<Diagram>` and pauses\n * automatically when any gate fails.\n *\n * Steps may carry a `when(ctx)` predicate that filters them out for a\n * given cycle, and a `data` payload surfaced while the step holds. Mode\n * toggles, branches, alternating loops — anything cycle-dependent —\n * composes via the predicate plus user-supplied `context`.\n *\n * Two run modes:\n * - `autoplay: true` (default) — ambient looping animation. Freezes under\n * reduced motion (`data` reads `null`).\n * - `autoplay: false` — idle until `start()`. Started runs are\n * user-initiated *function*, not decoration: they keep advancing under\n * reduced motion (gate your CSS transition durations card-side), and a\n * `start()` while the page is paused arms as soon as play resumes.\n *\n * The scheduler depends on the current step's *values* (id, hold), not\n * on `steps` / `context` identity — inline literals are safe and won't\n * re-arm the hold timer under frequent re-renders.\n */\nexport function usePhase<\n C extends Record<string, unknown> = Record<string, never>,\n D = unknown,\n>({\n steps,\n context,\n loop = true,\n autoplay = true,\n}: UsePhaseOptions<C, D>): UsePhaseReturn<D> {\n const ctx = useDiagramOrDefault(\"usePhase\");\n\n const [cycle, setCycle] = useState(0);\n const [index, setIndex] = useState(0);\n const [running, setRunning] = useState(autoplay);\n\n // Build the filtered sequence for the current cycle. The `current` field\n // in each predicate's ctx is the id of the previous step that survived\n // (empty string at cycle start).\n const sequence = useMemo(() => {\n const seq: UsePhaseStep<C, D>[] = [];\n let prev = \"\";\n const userContext = context ?? ({} as C);\n for (const step of steps) {\n const when = step.when;\n if (!when || when({ cycle, current: prev, ...userContext })) {\n seq.push(step);\n prev = step.id;\n }\n }\n return seq;\n }, [cycle, context, steps]);\n\n const currentStep = sequence[index] ?? sequence[sequence.length - 1] ?? null;\n const currentId = running ? (currentStep?.id ?? \"\") : \"\";\n const currentHold = currentStep?.hold ?? null;\n\n // Pin advancement logic to a ref so the scheduler effect can depend on\n // primitive values only — `steps`/`context` identity churn (inline\n // literals, motion-driven re-renders) must not re-arm the hold timer.\n const advanceRef = useRef<() => void>(() => {});\n const sequenceRef = useRef(sequence);\n const runningRef = useRef(running);\n useEffect(() => {\n advanceRef.current = () => {\n if (index + 1 >= sequence.length) {\n if (loop) {\n setCycle((c) => c + 1);\n setIndex(0);\n } else if (!autoplay) {\n // One-shot run complete: count the pass and return to idle.\n setCycle((c) => c + 1);\n setIndex(0);\n setRunning(false);\n }\n } else {\n setIndex((i) => i + 1);\n }\n };\n sequenceRef.current = sequence;\n runningRef.current = running;\n });\n\n // External-system sync: setTimeout-based scheduler. Pauses when any gate\n // fails. Reduced motion freezes ambient (autoplay) walkers only —\n // started one-shots are user-initiated function and must complete.\n const hasSteps = steps.length > 0;\n useEffect(() => {\n if (!running) return;\n if (!ctx.playing) return;\n if (ctx.reducedMotion && autoplay) return;\n if (currentHold === null) {\n // Every step's `when` rejected this cycle (empty filtered sequence).\n // Skip ahead instead of stalling permanently — `advance` wraps to the\n // next cycle (or ends a one-shot run). No-op when `steps` itself is\n // empty, so a placeholder walker doesn't spin cycles.\n if (!hasSteps) return;\n const t = setTimeout(() => advanceRef.current(), 0);\n return () => clearTimeout(t);\n }\n\n const t = setTimeout(() => advanceRef.current(), currentHold);\n return () => clearTimeout(t);\n }, [index, cycle, running, autoplay, ctx.playing, ctx.reducedMotion, currentId, currentHold, hasSteps]);\n\n // Event handlers (not effects).\n const start = useCallback(() => {\n if (runningRef.current) return;\n setIndex(0);\n setRunning(true);\n }, []);\n\n const advance = useCallback(() => advanceRef.current(), []);\n\n const goto = useCallback((id: string) => {\n const i = sequenceRef.current.findIndex((s) => s.id === id);\n if (i < 0) return;\n setIndex(i);\n // Jumping into an idle (autoplay: false) walker implies intent to show\n // that step — wake it rather than silently no-oping.\n setRunning(true);\n }, []);\n\n const reset = useCallback(() => {\n setCycle(0);\n setIndex(0);\n setRunning(autoplay);\n }, [autoplay]);\n\n const data =\n running && currentStep && !(ctx.reducedMotion && autoplay)\n ? (currentStep.data ?? null)\n : null;\n\n return {\n current: currentId,\n index,\n cycle,\n data,\n running,\n start,\n advance,\n goto,\n reset,\n };\n}\n","\"use client\";\n\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type RefObject,\n} from \"react\";\n\nexport interface UseMeasureOptions {\n /**\n * Additional dependencies that should re-trigger measurement when they\n * change. Use when component state changes the layout in a way the\n * container's ResizeObserver might miss — e.g. an animated CSS `gap`\n * that grows the container only as the transition progresses, or a\n * state-driven layout swap that doesn't change the container's size.\n */\n deps?: unknown[];\n}\n\n/**\n * DOM rect + optional selector callback. The selector receives the\n * observed element and its rect, and returns whatever derived geometry\n * the author needs (NodeRect, EdgeData, etc.).\n *\n * For multi-element measurements (e.g. measuring a container plus 5\n * child nodes), pass the container ref and read the other refs from\n * closure inside the selector. The selector recomputes when the\n * container resizes — which it typically does when children grow\n * because the container's intrinsic size is content-derived. For\n * state-driven re-layouts that the ResizeObserver might miss, pass\n * `options.deps`.\n */\nexport function useMeasure<T extends HTMLElement | SVGElement, R = DOMRectReadOnly>(\n ref: RefObject<T | null>,\n selector?: (el: T, rect: DOMRectReadOnly) => R,\n options: UseMeasureOptions = {},\n): { rect: DOMRectReadOnly | null; selected: R | null } {\n const { deps = [] } = options;\n const [rect, setRect] = useState<DOMRectReadOnly | null>(null);\n const [selected, setSelected] = useState<R | null>(null);\n\n // Pin the latest selector to a ref so the observer effect doesn't\n // re-bind on every render when selector identity changes.\n const selectorRef = useRef(selector);\n selectorRef.current = selector;\n\n // Manual re-sample, used by both the observer effect and the deps\n // layout-effect below.\n const sampleRef = useRef<() => void>(() => {});\n\n const boundElRef = useRef<T | null>(null);\n const unbindRef = useRef<(() => void) | null>(null);\n\n const bind = (el: T): (() => void) => {\n const sample = (r: DOMRectReadOnly) => {\n setRect(r);\n const s = selectorRef.current;\n if (s) setSelected(s(el, r));\n };\n\n sampleRef.current = () => {\n sample(el.getBoundingClientRect());\n };\n\n // Web-font swap can re-flow text-sized children without changing the\n // observed container's box — the ResizeObserver never fires. Re-sample\n // once font metrics settle.\n let cancelled = false;\n el.ownerDocument.fonts?.ready.then(() => {\n if (!cancelled) sampleRef.current();\n });\n\n const win = el.ownerDocument.defaultView ?? window;\n if (typeof win.ResizeObserver === \"undefined\") {\n sampleRef.current();\n return () => {\n cancelled = true;\n };\n }\n\n // Re-sample getBoundingClientRect() rather than reading\n // entry.contentRect: contentRect is content-box with element-local\n // coordinates (top/left ≈ 0), while the initial sample below is a\n // viewport-relative border-box rect. Mixing the two hands consumers\n // a rect whose coordinate space depends on which path fired last.\n const ro = new win.ResizeObserver(() => {\n sample(el.getBoundingClientRect());\n });\n ro.observe(el);\n\n // Synchronous initial sample so selector results land on first paint.\n sampleRef.current();\n\n return () => {\n cancelled = true;\n ro.disconnect();\n };\n };\n\n // Rebind after every commit if the element identity changed — covers a\n // ref that is still null on first commit (element mounts later) and an\n // element swap (key remount) that would otherwise leave the observer on\n // a detached node. No-op when the element is unchanged.\n useLayoutEffect(() => {\n const el = ref.current;\n if (el === boundElRef.current) return;\n unbindRef.current?.();\n boundElRef.current = el;\n unbindRef.current = el ? bind(el) : null;\n });\n\n useLayoutEffect(\n () => () => {\n unbindRef.current?.();\n unbindRef.current = null;\n boundElRef.current = null;\n },\n [],\n );\n\n // Re-sample when caller-supplied deps change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n sampleRef.current();\n }, deps);\n\n return { rect, selected };\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useState,\n type RefObject,\n} from \"react\";\n\nexport interface UseTabIndicatorReturn {\n /** Inline rect of the active tab, relative to the container. Null until first measure. */\n style: { top: number; left: number; width: number; height: number } | null;\n}\n\n/**\n * Headless sliding-indicator measurement for a tab group.\n *\n * Pure state + lifecycle — no DOM produced, no styling assumed. The\n * consumer renders the indicator however it wants, applying `style`\n * positionally.\n *\n * Re-measures on `activeId` change and on container resize. Returns\n * `null` style when `activeId` is null or its tab ref isn't mounted —\n * consumer renders nothing in that case, which also prevents a\n * \"mount-from-zero\" transition the first time the indicator appears.\n */\nexport function useTabIndicator(\n containerRef: RefObject<HTMLElement | null>,\n getTab: (id: string) => HTMLElement | null,\n activeId: string | null,\n): UseTabIndicatorReturn {\n const [style, setStyle] = useState<UseTabIndicatorReturn[\"style\"]>(null);\n\n const measure = useCallback(() => {\n const container = containerRef.current;\n if (!container || activeId == null) {\n setStyle(null);\n return;\n }\n const btn = getTab(activeId);\n if (!btn) {\n setStyle(null);\n return;\n }\n const cRect = container.getBoundingClientRect();\n const bRect = btn.getBoundingClientRect();\n setStyle({\n top: bRect.top - cRect.top,\n left: bRect.left - cRect.left,\n width: bRect.width,\n height: bRect.height,\n });\n }, [containerRef, getTab, activeId]);\n\n useLayoutEffect(() => {\n measure();\n }, [measure]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver(() => measure());\n ro.observe(el);\n return () => ro.disconnect();\n }, [containerRef, measure]);\n\n return { style };\n}\n","\"use client\";\n\nimport { useRef, type RefObject } from \"react\";\n\n/**\n * Stable per-id ref callbacks for a refs map — the ergonomic half of the\n * \"refs map + setter factory\" pattern. Instead of N individual `useRef`s\n * and N inline `ref={(el) => …}` arrows (which churn refs every render),\n * keep one map and hand each element a cached callback:\n *\n * ```tsx\n * const nodeRefs = useRef<Partial<Record<NodeId, HTMLDivElement | null>>>({});\n * const setNode = useRefSetters(nodeRefs);\n *\n * <div ref={setNode(\"prompt\")}>Prompt</div>\n * <div ref={setNode(\"model\")}>Model</div>\n * ```\n *\n * Measurement stays caller-side (read `refs.current` inside a `useMeasure`\n * selector) — this hook owns only the ref plumbing.\n */\nexport function useRefSetters<K extends string, E extends Element = HTMLDivElement>(\n refs: RefObject<Partial<Record<K, E | null>>>,\n) {\n const cache = useRef(new Map<K, (el: E | null) => void>());\n return (id: K) => {\n let cb = cache.current.get(id);\n if (!cb) {\n cb = (el) => {\n refs.current[id] = el;\n };\n cache.current.set(id, cb);\n }\n return cb;\n };\n}\n","\"use client\";\n\nimport { useCallback, useSyncExternalStore } from \"react\";\n\n/**\n * Reactive `window.matchMedia` subscription, SSR-safe.\n *\n * The server snapshot returns `defaultValue` (default `false`), so static\n * builds render the no-match branch and hydrate without mismatch warnings;\n * the first client render corrects it if the query matches.\n *\n * ```ts\n * const isMobile = useMediaQuery(\"(max-width: 640px)\");\n * ```\n */\nexport function useMediaQuery(query: string, defaultValue = false): boolean {\n const subscribe = useCallback(\n (onChange: () => void) => {\n const mq = window.matchMedia(query);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n },\n [query],\n );\n\n return useSyncExternalStore(\n subscribe,\n () => window.matchMedia(query).matches,\n () => defaultValue,\n );\n}\n","/**\n * Pure edge-routing math for diagram cards. No React, no DOM — every\n * function maps numbers to numbers (or an SVG path string). Colors,\n * stroke widths, markers, and node outlines stay user-side.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport type RectSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\n/**\n * Minimal rect shape consumed by `edgePoint`: left/top origin plus size,\n * relative to whatever container the SVG layer is positioned against.\n * Richer rect types (with `r`/`b`/`cx`/`cy`) are structurally compatible.\n */\nexport interface EdgeRect {\n l: number;\n t: number;\n w: number;\n h: number;\n}\n\n/** Anchor point on a rect edge. `frac` runs 0→1 from top/left. */\nexport function edgePoint(rect: EdgeRect, side: RectSide, frac = 0.5): Point {\n if (side === \"top\") return { x: rect.l + rect.w * frac, y: rect.t };\n if (side === \"bottom\") return { x: rect.l + rect.w * frac, y: rect.t + rect.h };\n if (side === \"left\") return { x: rect.l, y: rect.t + rect.h * frac };\n return { x: rect.l + rect.w, y: rect.t + rect.h * frac };\n}\n\n/**\n * - `straight` — direct line; axis-aligned lines are shortened by\n * `arrowOffset` at the destination so an arrowhead marker doesn't\n * overlap the target.\n * - `elbow` — one right-angle corner with a quadratic rounding,\n * radius clamped to half of each segment; end shortened by\n * `arrowOffset`.\n * - `vSplit` — vertical S-path through the midpoint Y (drop, run,\n * drop), corners rounded; no arrow offset (designed for marker-less\n * connectors).\n * - `auto` — `straight` when endpoints are axis-aligned within 2px,\n * else `elbow`.\n */\nexport type EdgeRoute = \"straight\" | \"elbow\" | \"vSplit\" | \"auto\";\n\nexport interface RouteOptions {\n /** End-shortening so arrowheads sit flush. Default 6. */\n arrowOffset?: number;\n /** Corner radius. Defaults: elbow 10, vSplit 6. */\n radius?: number;\n}\n\nfunction straightPath(from: Point, to: Point, arrowOffset: number): string {\n const dx = Math.abs(to.x - from.x);\n const dy = Math.abs(to.y - from.y);\n if (dy < 2) {\n const dir = to.x > from.x ? 1 : -1;\n return `M ${from.x},${from.y} L ${to.x - dir * arrowOffset},${to.y}`;\n }\n if (dx < 2) {\n const dir = to.y > from.y ? 1 : -1;\n return `M ${from.x},${from.y} L ${to.x},${to.y - dir * arrowOffset}`;\n }\n return `M ${from.x},${from.y} L ${to.x},${to.y}`;\n}\n\nfunction elbowPath(from: Point, to: Point, r: number, arrowOffset: number): string {\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const horizontalFirst = Math.abs(dx) > Math.abs(dy);\n const corner = horizontalFirst\n ? { x: to.x, y: from.y }\n : { x: from.x, y: to.y };\n const dx1 = Math.sign(corner.x - from.x);\n const dy1 = Math.sign(corner.y - from.y);\n const dx2 = Math.sign(to.x - corner.x);\n const dy2 = Math.sign(to.y - corner.y);\n const seg1Len = Math.abs(corner.x - from.x) + Math.abs(corner.y - from.y);\n const seg2Len = Math.abs(to.x - corner.x) + Math.abs(to.y - corner.y);\n const rr = Math.max(2, Math.min(r, seg1Len / 2, seg2Len / 2));\n const preX = corner.x - dx1 * rr;\n const preY = corner.y - dy1 * rr;\n const postX = corner.x + dx2 * rr;\n const postY = corner.y + dy2 * rr;\n const endX = to.x - dx2 * arrowOffset;\n const endY = to.y - dy2 * arrowOffset;\n return `M ${from.x},${from.y} L ${preX},${preY} Q ${corner.x},${corner.y} ${postX},${postY} L ${endX},${endY}`;\n}\n\nfunction vSplitPath(from: Point, to: Point, r: number): string {\n if (Math.abs(from.x - to.x) < 1) {\n return `M ${from.x},${from.y} L ${to.x},${to.y}`;\n }\n const midY = (from.y + to.y) / 2;\n const sx = Math.sign(to.x - from.x);\n const sy1 = Math.sign(midY - from.y);\n const sy2 = Math.sign(to.y - midY);\n const rr = Math.min(\n r,\n Math.abs(midY - from.y),\n Math.abs(to.x - from.x) / 2,\n Math.abs(to.y - midY),\n );\n return [\n `M ${from.x},${from.y}`,\n `L ${from.x},${midY - rr * sy1}`,\n `Q ${from.x},${midY} ${from.x + rr * sx},${midY}`,\n `L ${to.x - rr * sx},${midY}`,\n `Q ${to.x},${midY} ${to.x},${midY + rr * sy2}`,\n `L ${to.x},${to.y}`,\n ].join(\" \");\n}\n\n/** Build an SVG path string between two points using the given route. */\nexport function routeEdge(\n from: Point,\n to: Point,\n route: EdgeRoute = \"auto\",\n options: RouteOptions = {},\n): string {\n const arrowOffset = options.arrowOffset ?? 6;\n if (route === \"vSplit\") return vSplitPath(from, to, options.radius ?? 6);\n if (route === \"straight\") return straightPath(from, to, arrowOffset);\n if (route === \"elbow\") return elbowPath(from, to, options.radius ?? 10, arrowOffset);\n const aligned =\n Math.abs(from.x - to.x) < 2 || Math.abs(from.y - to.y) < 2;\n return aligned\n ? straightPath(from, to, arrowOffset)\n : elbowPath(from, to, options.radius ?? 10, arrowOffset);\n}\n\n/** Anchor: `[nodeId, side]` with an optional frac (default 0.5). */\nexport type EdgeAnchor<NodeId extends string = string> =\n | readonly [NodeId, RectSide]\n | readonly [NodeId, RectSide, number];\n\nexport interface EdgeSpec<\n NodeId extends string = string,\n EdgeId extends string = string,\n> {\n id: EdgeId;\n from: EdgeAnchor<NodeId>;\n to: EdgeAnchor<NodeId>;\n route?: EdgeRoute;\n /** Carried through to the resolved edge for styling decisions. */\n ghost?: boolean;\n}\n\nexport interface ResolvedEdge<EdgeId extends string = string> {\n id: EdgeId;\n from: Point;\n to: Point;\n /** Ready-to-render SVG path string. */\n d: string;\n ghost?: boolean;\n}\n\n/**\n * Resolve declarative edge specs against a map of measured rects.\n * Specs whose endpoints aren't in `rects` yet (unmounted nodes,\n * first-paint gaps) are skipped rather than rendered degenerate.\n */\nexport function resolveEdges<\n NodeId extends string = string,\n EdgeId extends string = string,\n>(\n specs: readonly EdgeSpec<NodeId, EdgeId>[],\n rects: Partial<Record<NodeId, EdgeRect>>,\n options: RouteOptions = {},\n): ResolvedEdge<EdgeId>[] {\n const out: ResolvedEdge<EdgeId>[] = [];\n for (const spec of specs) {\n const fromRect = rects[spec.from[0]];\n const toRect = rects[spec.to[0]];\n if (!fromRect || !toRect) continue;\n const from = edgePoint(fromRect, spec.from[1], spec.from[2] ?? 0.5);\n const to = edgePoint(toRect, spec.to[1], spec.to[2] ?? 0.5);\n out.push({\n id: spec.id,\n from,\n to,\n d: routeEdge(from, to, spec.route ?? \"auto\", options),\n ghost: spec.ghost,\n });\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -67,6 +67,7 @@ const RULE_CODES = {
|
|
|
67
67
|
"nimbus/frontmatter-shape": { kind: "authoring" },
|
|
68
68
|
"nimbus/description-required": { kind: "authoring" },
|
|
69
69
|
"nimbus/internal-link": { kind: "authoring" },
|
|
70
|
+
"nimbus/image-ref": { kind: "authoring" },
|
|
70
71
|
"nimbus/orphan-page": { kind: "authoring" },
|
|
71
72
|
"nimbus/sidebar-entry": { kind: "authoring" },
|
|
72
73
|
"nimbus/single-h1": { kind: "authoring" },
|
|
@@ -5045,6 +5046,251 @@ function suggest(target, candidates, maxDist = 3) {
|
|
|
5045
5046
|
return best?.name ?? null;
|
|
5046
5047
|
}
|
|
5047
5048
|
|
|
5049
|
+
//#endregion
|
|
5050
|
+
//#region src/lint/rules/image-ref.ts
|
|
5051
|
+
/**
|
|
5052
|
+
* nimbus/image-ref — image references that don't resolve to a file on
|
|
5053
|
+
* disk. Unlike `internal-link` (whose truth is the emergent route set,
|
|
5054
|
+
* materialized at build time), image truth is just the filesystem: a
|
|
5055
|
+
* site-absolute ref maps to `public/`, a relative ref to the authoring
|
|
5056
|
+
* file's directory. No build prerequisite, no silent-skip machinery.
|
|
5057
|
+
*
|
|
5058
|
+
* Coverage:
|
|
5059
|
+
* - `image` nodes (``)
|
|
5060
|
+
* - `imageReference` nodes (`![alt][ref]` resolved against `definition`s)
|
|
5061
|
+
* - MDX JSX `<img src="...">`
|
|
5062
|
+
* - Extra JSX components opt-in via `components: [{ name, attr }, …]` —
|
|
5063
|
+
* same shape as `internal-link`'s option, same rationale: component
|
|
5064
|
+
* names belong to the user.
|
|
5065
|
+
*
|
|
5066
|
+
* Resolution:
|
|
5067
|
+
* - `/foo.png` → `<root>/public/foo.png` (Astro serves `public/` at the
|
|
5068
|
+
* site root).
|
|
5069
|
+
* - `./shot.png` / `../img.png` → relative to the MDX file (Astro's image
|
|
5070
|
+
* pipeline resolves these as imports — they're valid and checkable).
|
|
5071
|
+
* - `aliases: { "~/assets/": "src/assets/" }` maps prefix → root-relative
|
|
5072
|
+
* directory. The framework ships none by default — `~/` is a per-project
|
|
5073
|
+
* tsconfig path, not a framework concept.
|
|
5074
|
+
* - External URLs (any scheme, incl. `data:`, and `//`) are skipped —
|
|
5075
|
+
* remote existence checks are a network concern, not a lint.
|
|
5076
|
+
* - Dynamic JSX attrs (`<img src={x}>`) are skipped — not checkable.
|
|
5077
|
+
*
|
|
5078
|
+
* A miss whose containing directory exists produces a "did you mean" hint
|
|
5079
|
+
* from that directory's entries via Levenshtein distance.
|
|
5080
|
+
*/
|
|
5081
|
+
const imageRef = {
|
|
5082
|
+
code: "nimbus/image-ref",
|
|
5083
|
+
run(ctx) {
|
|
5084
|
+
const root = inferProjectRoot$1(ctx.file.absPath);
|
|
5085
|
+
const aliases = readAliases(ctx.options.aliases);
|
|
5086
|
+
const ignore = Array.isArray(ctx.options.ignore) ? ctx.options.ignore.filter((s) => typeof s === "string") : [];
|
|
5087
|
+
const extraComponents = readExtraComponents$1(ctx.options.components);
|
|
5088
|
+
const definitions = collectDefinitions$1(ctx.file.tree);
|
|
5089
|
+
for (const occ of collectImageOccurrences(ctx.file.tree, definitions, extraComponents)) {
|
|
5090
|
+
const url = occ.url;
|
|
5091
|
+
if (!url) continue;
|
|
5092
|
+
if (isExternal$1(url)) continue;
|
|
5093
|
+
const cleaned = cleanUrl(url);
|
|
5094
|
+
if (cleaned === "") continue;
|
|
5095
|
+
if (matchesAnyIgnore$1(cleaned, ignore)) continue;
|
|
5096
|
+
const resolved = resolveToDisk(cleaned, root, ctx.file.absPath, aliases);
|
|
5097
|
+
if (resolved === null) continue;
|
|
5098
|
+
if (fileExists(resolved.fullPath)) continue;
|
|
5099
|
+
const hint = suggestSibling(resolved.fullPath);
|
|
5100
|
+
ctx.report({
|
|
5101
|
+
message: hint ? `missing image "${url}" — expected at ${resolved.display}; did you mean "${hint}"?` : `missing image "${url}" — expected at ${resolved.display}.`,
|
|
5102
|
+
line: occ.line,
|
|
5103
|
+
column: occ.column
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
};
|
|
5108
|
+
/**
|
|
5109
|
+
* `<img src>` is always checked — a plain img means the same thing in
|
|
5110
|
+
* every MDX file. Extra components come from the `components` option.
|
|
5111
|
+
*/
|
|
5112
|
+
function readExtraComponents$1(value) {
|
|
5113
|
+
if (!Array.isArray(value)) return [];
|
|
5114
|
+
const out = [];
|
|
5115
|
+
for (const item of value) {
|
|
5116
|
+
if (!item || typeof item !== "object") continue;
|
|
5117
|
+
const obj = item;
|
|
5118
|
+
if (typeof obj.name === "string" && typeof obj.attr === "string") out.push({
|
|
5119
|
+
name: obj.name,
|
|
5120
|
+
attr: obj.attr
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
return out;
|
|
5124
|
+
}
|
|
5125
|
+
function collectImageOccurrences(root, definitions, extraComponents) {
|
|
5126
|
+
const out = [];
|
|
5127
|
+
visit(root, (node) => {
|
|
5128
|
+
if (node.type === "image") {
|
|
5129
|
+
const at = startOf(node);
|
|
5130
|
+
out.push({
|
|
5131
|
+
url: typeof node.url === "string" ? node.url : "",
|
|
5132
|
+
line: at.line,
|
|
5133
|
+
column: at.column
|
|
5134
|
+
});
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
if (node.type === "imageReference") {
|
|
5138
|
+
const identifier = typeof node.identifier === "string" ? node.identifier : "";
|
|
5139
|
+
const url = definitions.get(identifier);
|
|
5140
|
+
if (!url) return;
|
|
5141
|
+
const at = startOf(node);
|
|
5142
|
+
out.push({
|
|
5143
|
+
url,
|
|
5144
|
+
line: at.line,
|
|
5145
|
+
column: at.column
|
|
5146
|
+
});
|
|
5147
|
+
return;
|
|
5148
|
+
}
|
|
5149
|
+
if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
|
|
5150
|
+
if (node.name === "img") {
|
|
5151
|
+
const src = readJsxStringAttr$1(node, "src");
|
|
5152
|
+
if (src === null) return;
|
|
5153
|
+
const at = startOf(node);
|
|
5154
|
+
out.push({
|
|
5155
|
+
url: src,
|
|
5156
|
+
line: at.line,
|
|
5157
|
+
column: at.column
|
|
5158
|
+
});
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
for (const spec of extraComponents) {
|
|
5162
|
+
if (node.name !== spec.name) continue;
|
|
5163
|
+
const src = readJsxStringAttr$1(node, spec.attr);
|
|
5164
|
+
if (src === null) return;
|
|
5165
|
+
const at = startOf(node);
|
|
5166
|
+
out.push({
|
|
5167
|
+
url: src,
|
|
5168
|
+
line: at.line,
|
|
5169
|
+
column: at.column
|
|
5170
|
+
});
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
});
|
|
5175
|
+
return out;
|
|
5176
|
+
}
|
|
5177
|
+
function collectDefinitions$1(root) {
|
|
5178
|
+
const out = /* @__PURE__ */ new Map();
|
|
5179
|
+
for (const def of collect(root, "definition")) {
|
|
5180
|
+
const id = typeof def.identifier === "string" ? def.identifier : "";
|
|
5181
|
+
const url = typeof def.url === "string" ? def.url : "";
|
|
5182
|
+
if (id && url && !out.has(id)) out.set(id, url);
|
|
5183
|
+
}
|
|
5184
|
+
return out;
|
|
5185
|
+
}
|
|
5186
|
+
/**
|
|
5187
|
+
* Read a string-valued JSX attribute. Returns null when the attribute is
|
|
5188
|
+
* absent, dynamic (expression form `<img src={x}>`), or boolean.
|
|
5189
|
+
* Static-only on purpose — dynamic srcs aren't checkable.
|
|
5190
|
+
*/
|
|
5191
|
+
function readJsxStringAttr$1(node, name) {
|
|
5192
|
+
const attrs = node.attributes;
|
|
5193
|
+
if (!Array.isArray(attrs)) return null;
|
|
5194
|
+
for (const a of attrs) {
|
|
5195
|
+
if (!a || typeof a !== "object") continue;
|
|
5196
|
+
const attr = a;
|
|
5197
|
+
if (attr.name !== name) continue;
|
|
5198
|
+
if (typeof attr.value === "string") return attr.value;
|
|
5199
|
+
return null;
|
|
5200
|
+
}
|
|
5201
|
+
return null;
|
|
5202
|
+
}
|
|
5203
|
+
function isExternal$1(url) {
|
|
5204
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
|
|
5205
|
+
}
|
|
5206
|
+
/** Strip query string and hash, then percent-decode. */
|
|
5207
|
+
function cleanUrl(url) {
|
|
5208
|
+
let s = url;
|
|
5209
|
+
const q = s.indexOf("?");
|
|
5210
|
+
if (q !== -1) s = s.slice(0, q);
|
|
5211
|
+
const h = s.indexOf("#");
|
|
5212
|
+
if (h !== -1) s = s.slice(0, h);
|
|
5213
|
+
try {
|
|
5214
|
+
s = decodeURI(s);
|
|
5215
|
+
} catch {}
|
|
5216
|
+
return s;
|
|
5217
|
+
}
|
|
5218
|
+
function readAliases(value) {
|
|
5219
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
5220
|
+
const out = [];
|
|
5221
|
+
for (const [prefix, dir] of Object.entries(value)) if (typeof dir === "string" && prefix.length > 0) out.push([prefix, dir]);
|
|
5222
|
+
out.sort((a, b) => b[0].length - a[0].length);
|
|
5223
|
+
return out;
|
|
5224
|
+
}
|
|
5225
|
+
function resolveToDisk(url, root, fileAbsPath, aliases) {
|
|
5226
|
+
for (const [prefix, dir] of aliases) if (url.startsWith(prefix)) {
|
|
5227
|
+
const rel = path.join(dir, url.slice(prefix.length));
|
|
5228
|
+
return {
|
|
5229
|
+
fullPath: path.join(root, rel),
|
|
5230
|
+
display: rel
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
5233
|
+
if (url.startsWith("/")) {
|
|
5234
|
+
const rel = path.join("public", url.slice(1));
|
|
5235
|
+
return {
|
|
5236
|
+
fullPath: path.join(root, rel),
|
|
5237
|
+
display: rel
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5240
|
+
if (url.startsWith("./") || url.startsWith("../")) {
|
|
5241
|
+
const fullPath = path.resolve(path.dirname(fileAbsPath), url);
|
|
5242
|
+
const rel = path.relative(root, fullPath);
|
|
5243
|
+
return {
|
|
5244
|
+
fullPath,
|
|
5245
|
+
display: rel.startsWith("..") ? fullPath : rel
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
return null;
|
|
5249
|
+
}
|
|
5250
|
+
function fileExists(fullPath) {
|
|
5251
|
+
try {
|
|
5252
|
+
return fs.statSync(fullPath).isFile();
|
|
5253
|
+
} catch {
|
|
5254
|
+
return false;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
/**
|
|
5258
|
+
* Did-you-mean from the missing file's own directory — cheap (one
|
|
5259
|
+
* `readdir`) and covers the dominant failure (typo or wrong extension in
|
|
5260
|
+
* the filename, not the directory).
|
|
5261
|
+
*/
|
|
5262
|
+
function suggestSibling(fullPath) {
|
|
5263
|
+
let entries;
|
|
5264
|
+
try {
|
|
5265
|
+
entries = fs.readdirSync(path.dirname(fullPath));
|
|
5266
|
+
} catch {
|
|
5267
|
+
return null;
|
|
5268
|
+
}
|
|
5269
|
+
return suggest(path.basename(fullPath), new Set(entries), 3);
|
|
5270
|
+
}
|
|
5271
|
+
/**
|
|
5272
|
+
* Minimal glob matcher — exact match or `prefix/**` suffix, same shape as
|
|
5273
|
+
* `internal-link`'s. Patterns are authored against the raw cleaned URL
|
|
5274
|
+
* (e.g. `/images/generated/**`).
|
|
5275
|
+
*/
|
|
5276
|
+
function matchesAnyIgnore$1(url, patterns) {
|
|
5277
|
+
if (patterns.length === 0) return false;
|
|
5278
|
+
for (const pat of patterns) {
|
|
5279
|
+
const stripped = pat.length > 1 && pat.endsWith("/") ? pat.slice(0, -1) : pat;
|
|
5280
|
+
if (stripped.endsWith("/**")) {
|
|
5281
|
+
const prefix = stripped.slice(0, -3);
|
|
5282
|
+
if (url === prefix || url.startsWith(`${prefix}/`)) return true;
|
|
5283
|
+
} else if (url === stripped) return true;
|
|
5284
|
+
}
|
|
5285
|
+
return false;
|
|
5286
|
+
}
|
|
5287
|
+
/** Find the project root from a content file by walking up to the parent of `src`. */
|
|
5288
|
+
function inferProjectRoot$1(absPath) {
|
|
5289
|
+
const norm = absPath.replace(/\\/g, "/");
|
|
5290
|
+
const idx = norm.lastIndexOf("/src/");
|
|
5291
|
+
return idx === -1 ? path.dirname(absPath) : norm.slice(0, idx);
|
|
5292
|
+
}
|
|
5293
|
+
|
|
5048
5294
|
//#endregion
|
|
5049
5295
|
//#region src/lint/rules/internal-link.ts
|
|
5050
5296
|
/**
|
|
@@ -5826,11 +6072,12 @@ const RULES = [
|
|
|
5826
6072
|
listMarkerStyle,
|
|
5827
6073
|
emphasisStyle,
|
|
5828
6074
|
bareUrl,
|
|
5829
|
-
internalLink
|
|
6075
|
+
internalLink,
|
|
6076
|
+
imageRef
|
|
5830
6077
|
];
|
|
5831
6078
|
/** Codes with a wired implementation (a subset of `RULE_CODES`). */
|
|
5832
6079
|
const IMPLEMENTED_CODES = new Set(RULES.map((r) => r.code));
|
|
5833
6080
|
|
|
5834
6081
|
//#endregion
|
|
5835
6082
|
export { resolveRuleForCollection as a, parseSource as i, RULES as n, validateLintOptions as o, suggest as r, isRuleCode as s, IMPLEMENTED_CODES as t };
|
|
5836
|
-
//# sourceMappingURL=rules-
|
|
6083
|
+
//# sourceMappingURL=rules-B7o0k3TA.js.map
|