import { S as SubscribeOptions, a as ConductorLane, F as FrameFn, g as getConductor$1 } from './conductor-D83xroU5.js'; import { d as SensorBus, B as BudgetState, A as AdaptiveState, c as PressureState, f as clamp01$1, g as damp$1, h as getAdaptiveQuality$1, i as getAnimationBudget$1, k as getSensorBus$1, r as rayRectIntersect$1 } from './FramePressure-BI3t0lXG.js'; export { M as MountCost, a as MountCostThresholds, b as SAFE_TO_MOUNT_COST, c as SceneGate, S as SceneState, U as UseSafeToMountOptions, d as UseSceneGateOptions, u as useSafeToMount, e as useSceneGate } from './useSceneGate-C-4jJfT2.js'; import React from 'react'; /** * A React hook that retains the unified `SensorBus` instance for the lifetime * of the component, automatically managing event-listener attachments and reference counting. * * ### โšก Performance Optimization Contract * `useSensorBus` deliberately returns a **stable reference** to the bus object and **does not trigger React state re-renders** * when mouse moves, scroll offsets change, or window resizes. This is to avoid severe rendering bottlenecks (60 FPS layout thrashing). * Read and apply values inside the global `FrameConductor` frame loop instead. * * ### ๐Ÿ“š Usage Example: * ```tsx * import { useEffect, useRef } from "react"; * import { useSensorBus, getConductor } from "@vectorvesper/motion"; * * export function InteractiveCard() { * const elementRef = useRef(null); * const bus = useSensorBus(); * * useEffect(() => { * // Subscribe to the render lane of the central loop * return getConductor().subscribe("render", () => { * if (!elementRef.current) return; * * // Read fresh sensor data per-frame without re-rendering the component * const { x, y, speed } = bus.state.pointer; * const scale = 1 + Math.min(0.1, speed / 1000); * * elementRef.current.style.transform = `translate3d(${x * 0.05}px, ${y * 0.05}px, 0) scale(${scale})`; * }); * }, [bus]); * * return
Move pointer here
; * } * ``` * * @returns {SensorBus} The singleton SensorBus instance, with automatic lifecycle hook attachments. */ declare function useSensorBus(): SensorBus; /** * Subscribe to the frame-headroom governor. * * Returns a {@link BudgetState} snapshot that updates on tier changes and on * a slow ~2 Hz heartbeat (useful for HUD `avgFrameMs` displays). * * ### Tier contract * | tier | label | meaning | * |------|----------|---------------------------------------------| * | `0` | `"high"` | Frames are healthy โ€” run the full look | * | `1` | `"medium"`| Sustained drops below ~54 fps โ€” shed extras | * | `2` | `"low"` | Sustained drops below ~30 fps โ€” survival | * * ### Why React state * Tier changes are hysteresis-gated (fast to degrade, 8 s to recover), so * they are rare. Using React state here is intentional: conditional rendering * of expensive effects is exactly the right consumption pattern. Do **not** * read this inside a `requestAnimationFrame` loop โ€” poll `getAnimationBudget().state` * there instead. * * ### Usage patterns * * **1. Gate an expensive layer** * ```tsx * const { tier } = useAnimationBudget(); * {tier === 0 && } * ``` * * **2. Conditional CSS class** * ```tsx * const { tier } = useAnimationBudget(); *
* ``` * * **3. Live telemetry HUD** * ```tsx * const { avgFrameMs, slowRatio, label } = useAnimationBudget(); * {avgFrameMs.toFixed(1)} ms ยท {label} * ``` * * @returns The current {@link BudgetState} โ€” stable reference until the next * tier change or heartbeat tick. */ declare function useAnimationBudget(): BudgetState; interface InteractionScopeProps { children?: React.ReactNode; /** * A human-readable name for this region, shown in devtools. It has no effect * on behaviour, and it does not have to be unique. */ label?: string; /** * Control activation yourself instead of using pointer input. Pass `true` * while the visitor is working in this region โ€” a camera engaged by keyboard, * an open modal. * * Setting this at all turns pointer activation off. Leave it out for the * normal case. * * Worth knowing: this runs through React state, so it arrives a render later * than pointer activation, which is a direct DOM listener. That is fine for * a modal or a keyboard mode. It is not a drop-in replacement for a drag. */ active?: boolean; /** * Use the single child element instead of rendering a wrapper `div`. Use it * when an extra element would break a flex or grid layout. */ asChild?: boolean; className?: string; style?: React.CSSProperties; } /** * Mark the region the visitor is working in. * * ```tsx * * * * ``` * * While a pointer is down inside it, VV's own non-essential work *outside* the * region yields earlier when the page runs out of frame time. On a healthy * frame nothing changes at all. * * Reach for it around a draggable, a scrubber, or an interactive canvas that * shares a page with other VV visuals. Two things it cannot do: pause another * library's animation, or undo GPU work that has already been submitted. It * only coordinates work registered with VV. * * Every `useTick` rendered inside joins this region automatically. Nesting * works, and the innermost provider wins. * * ## How activation tracks a drag * * Three details, each because the obvious version got it wrong: * * - **Pointer down is caught in the capture phase**, on the DOM node rather * than through a React prop. Drag code very often calls `stopPropagation()` * on pointerdown so an outer handler does not also react to the press. A * bubble-phase listener never runs when that happens, and the region would * silently do nothing in the exact case it is for. * - **The end of the drag is watched on `window`, not on the region.** A drag * routinely travels outside the element it started in, and that is still the * same drag. * - **Activation lasts until the last pointer lifts.** Pointers are counted by * id, so lifting a second finger does not end a one-finger drag. * * Pointer capture is deliberately not used. Calling `setPointerCapture` on this * wrapper would fight any child that captures the pointer for its own drag. */ declare function InteractionScope({ children, label, active, asChild, className, style, }: InteractionScopeProps): React.JSX.Element; /** The scope this subtree belongs to, or `null` outside any provider. */ declare function useInteractionScope(): string | null; interface UseTickOptions extends Omit { /** * Keep this work in the background even when it is rendered inside an * {@link InteractionScope} โ€” an ambient layer behind a gallery, say, which * should yield like everything else while the gallery is being used. */ background?: boolean; /** Skip subscribing entirely while false. Default `true`. */ enabled?: boolean; } /** * Run a function on the shared frame loop. * * ```tsx * useTick("render", (dt) => { * x.current += (target.current - x.current) * (1 - Math.exp(-18 * dt)); * el.current.style.transform = `translate3d(${x.current}px,0,0)`; * }); * ``` * * Prefer this over calling `getConductor().subscribe` from a component. The * callback is held in a ref, so it can read current props without * resubscribing on every render, and it joins the surrounding * {@link InteractionScope} on its own. * * The subscription is removed on unmount. Changing `lane`, `priority`, `hz` or * `background` resubscribes; changing the function alone does not. */ declare function useTick(lane: ConductorLane, fn: FrameFn, options?: UseTickOptions): void; /** * The complete quality signal โ€” device floor fused with the live budget. * * const quality = useAdaptiveQuality(); * {quality.tier === 0 && } * {quality.reducedMotion ? : } * * Tier changes are rare (hysteresis + static floor), so this is React * state by design โ€” the gatekeeper for conditional rendering of expensive * work. `reasons` explains the device verdict for HUDs and support. */ declare function useAdaptiveQuality(): AdaptiveState; /** * What is currently eating the frame, and how sure the runtime is about it. * * ```tsx * const { source, confidence } = useFramePressure(); * * // Only reduce the scene when rendering is what is actually slow. * const dpr = source === "render" && confidence > 0.5 ? 1 : 2; * ``` * * Verdicts change slowly and are emitted at most twice a second, so React * state is the right shape here. Do not read it inside a frame callback โ€” * use `getFramePressure().state` there. * * Read the meaning of each `source` on {@link PressureState}. The short * version: `"runtime"` means shedding our own work will help, `"main-thread"` * means delay mounts rather than dropping quality, `"render"` means lower the * quality, and `"unknown"` means change nothing. * * Nothing in the runtime acts on this yet. It reports so that it can be * checked against real pages before it is allowed to make decisions. */ declare function useFramePressure(): PressureState; /** * @vectorvesper/motion/react โ€” React adapters ("./react") * * Thin hooks over the core runtime. Requires react + react-dom as peers. * * ## This entry also re-exports the core singletons * * Framer cannot load the root entry โ€” `Module @vectorvesper/motion is not a * valid npm package (f3)`. That blocked the whole Framer port, because 21 * components need `getConductor` and it lived only on the path Framer refuses. * The workaround people reached for was a private `requestAnimationFrame` loop, * which is the exact thing the conductor exists to replace. * * So the core value exports are mirrored here. Framer components import * everything from "@vectorvesper/motion/react". * * ## Why `const` re-binding, and why no type re-export * * Both are the same constraint, learned the hard way across 1.1.0 and 1.1.1. * * Framer's validator rejects a module whose declarations contain **forwarded * re-exports pointing at a relative chunk file**. Not barrels in general โ€” that * specific shape. Counting them in the published builds: * * 1.0.4 react.js none ยท react.d.ts none โ†’ Framer loads it * 1.1.0 react.js two ยท react.d.ts two โ†’ rejected (added `export *`) * 1.1.1 react.js none ยท react.d.ts two โ†’ still rejected (fixed JS only) * * `export *` compiles to that shape in the JavaScript. Assigning to a `const` * does not โ€” it is a local binding, emitted as ordinary code. * * `export type *` compiles to that shape in the **.d.ts**, which is what an * editor validates. That is why 1.1.1 still failed after the JS was clean, and * why core *types* are deliberately not re-exported here. They remain available * from "@vectorvesper/motion"; only the callable surface is mirrored. * * The singleton is unaffected either way: a const alias holds the same function * object, so `getConductor()` through this entry and through the root return the * one shared instance. A second conductor would mean a second frame loop, which * would defeat the entire runtime. * * ## Regression test * * After any build-config change, both `dist/react.js` and `dist/react.d.ts` * must contain **zero** forwarded re-exports pointing at a chunk file. Check * both โ€” 1.1.1 shipped broken because only the first was checked. * * Note this file is `"use client"`, so core helpers reached through it are * client-only. Server or framework-agnostic code should import from * "@vectorvesper/motion" directly. */ declare const VERSION: string; declare const clamp01: typeof clamp01$1; declare const damp: typeof damp$1; declare const getAdaptiveQuality: typeof getAdaptiveQuality$1; declare const getAnimationBudget: typeof getAnimationBudget$1; declare const getConductor: typeof getConductor$1; declare const getSensorBus: typeof getSensorBus$1; declare const rayRectIntersect: typeof rayRectIntersect$1; export { InteractionScope, type InteractionScopeProps, type UseTickOptions, VERSION, clamp01, damp, getAdaptiveQuality, getAnimationBudget, getConductor, getSensorBus, rayRectIntersect, useAdaptiveQuality, useAnimationBudget, useFramePressure, useInteractionScope, useSensorBus, useTick };