# @octanejs/motion

[Framer Motion](https://motion.dev) for the [octane](https://github.com/octanejs/octane) UI framework.

## Installation

```sh
npm install @octanejs/motion
pnpm add @octanejs/motion
```

Motion separates a framework-agnostic animation engine (`animate`) and gesture
primitives (`hover`, `press`) from its React components (`motion.div`,
`AnimatePresence`). This package reuses the engine + gestures verbatim and
reimplements the components on octane.

```tsx
// before
import { motion, AnimatePresence } from 'motion/react';
// after
import { motion, AnimatePresence } from '@octanejs/motion';

function Card() @{
  <motion.div
    className="card"
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.3 }}
    whileHover={{ scale: 1.05 }}
    whileTap={{ scale: 0.95 }}
  >
    {'hello'}
  </motion.div>
}

function List(props) @{
  <AnimatePresence>
    @if (props.show) {
      <motion.div exit={{ opacity: 0 }}>{'I fade out when removed'}</motion.div>
    }
  </AnimatePresence>
}
```

## What's bound

- `motion.<tag>` — `initial`, `animate`, `transition`, `whileHover`, `whileTap`,
  `whileFocus`, `whileInView` (+ `viewport`), `exit`, `drag` (+ `dragConstraints`,
  `onDrag*`), `layout`, `layoutId`, `variants`, plus any DOM props (className, style,
  events, …) and `style` MotionValues spread/bound onto the element.
- `AnimatePresence` — exit animations on removal.
- `MotionConfig` — global `transition` / `reducedMotion` defaults via context.
- `useReducedMotion()` — a live `prefers-reduced-motion` subscription; operating-system
  setting changes update mounted consumers.
- `LayoutGroup` — namespaces `layoutId` values so independent shared-layout surfaces
  do not cross-animate.
- `LazyMotion` + `domAnimation` / `domMax` + `m` — feature-gated hosts. The
  `./react-m` entry exposes every HTML/SVG host as a named export.
- `variants` — label resolution (`animate="visible"`) + parent→child propagation +
  `staggerChildren` / `delayChildren` (number or `stagger()` function) / `staggerDirection`.
- `useMotionValue()`, `useScroll()`, `useAnimate()` — MotionValues, scroll-linked
  values, and imperative scoped animation.
- `useTransform()`, `useSpring()`, `useMotionValueEvent()` — MotionValue composition:
  derive a value (range-map / transformer / multi-input combiner), spring toward a
  value or source, and subscribe to a value's events.
- Motion's framework-agnostic helpers (`animate`, `stagger`, value types, …),
  re-exported.

## How it works

octane had no public way for a runtime-proxy component to render a host element
wrapping children, nor to provide context from plain-TS — so this package added two
runtime primitives: `hostComponent` and `provideContext`. `motion.<tag>` renders a
real `<tag>` through `hostComponent`, captures the node, and drives:

- **Animations** from layout effects calling motion's `animate()`; **gestures** via
  `hover()` / `press()` / `inView()`; **MotionValues** (from `useMotionValue` /
  `useScroll`) by subscribing in `style` and writing the element directly.
- **`MotionConfig` + `variants`** through `provideContext`: a plain-TS component
  stamps context for its children (config defaults, active variant labels).
- **`drag`** with pointer events (axis lock + `dragConstraints`).
- **Exit** without any deferred-deletion machinery: octane fires cleanups *before*
  detaching the DOM, so a leaving element's unmount cleanup clones it (outside the
  range octane is about to remove), animates the exit on the clone, and removes it
  when it finishes.
- **`layout` / `layoutId`** via FLIP: measure the box, and if it moved/resized —
  vs the previous commit (`layout`) or a same-id element that just unmounted
  (`layoutId`) — apply the inverse transform then animate it back to identity. The
  same cleanup-before-detach ordering lets a leaving `layoutId` element record its
  box for a same-commit replacement; unused boxes expire after that commit.
  `layout="position"` applies only translation, `layout="size"` only scaling, and
  layout animations use `transition.layout` when provided.

## Not yet ported

The full layout **projection tree** — nested projection, child scale correction, and
continuous shared-layout during drag (the `layout`/`layoutId` here are single-element
FLIPs). Also drag momentum/elastic physics and
`useTransform`'s output-map form (`useTransform(mv, [0, 100], { opacity: [0, 1] })`).

Stagger specifics: `when: 'beforeChildren' | 'afterChildren'` parent/child sequencing
is not implemented, and a child's stagger index is fixed at registration order (a
keyed reorder does not re-stagger).

## Status

Current scope, known divergences, and verification status are tracked in the
generated [bindings status table](../../docs/bindings-status.md), sourced from
this package's [`status.json`](./status.json).
