npm package: @pariharshyamu/morph
Description: Interrupt-safe DOM morphing with spring physics, FLIP animations, Hungarian algorithm matching, and auto-animate via MutationObserver. Works with htmx, Alpine.js, Turbo, or vanilla JS.
Author: pariharshyamu
Latest version: 2.0.1

# @pariharshyamu/morph

> Interrupt-safe DOM morphing with spring physics, FLIP animations, and auto-animate.

**morph.js v2** patches your DOM like `morphdom`, but adds silky smooth animations — spring physics, FLIP transitions, choreographed enter/exit/move, and mid-animation interruption handling.

## Features

- ✦ **Interrupted animation handling** — morphs from current visual position, never snaps
- ✦ **Scroll-aware FLIP** — corrects `getBoundingClientRect` across scrolled containers
- ✦ **Stacking-context correction** — handles CSS transform ancestors
- ✦ **Hungarian algorithm matching** — optimal global assignment, not greedy
- ✦ **Spring physics engine** — position/velocity integrator, replaces cubic-bezier
- ✦ **Simultaneous move + content morph** — FLIP + crossfade in one animation track
- ✦ **Choreography model** — sequence exits, moves, enters independently
- ✦ **MorphObserver** — auto-animate any DOM mutations on a container
- ✦ **htmx extension** — hooks into htmx:beforeSwap / htmx:afterSwap
- ✦ **Confidence-gated matching** — low-confidence pairs become enter/exit instead

## Install

```bash
npm install @pariharshyamu/morph
```

Or use a CDN:

```html
<script type="module">
  import morph from 'https://unpkg.com/@pariharshyamu/morph';
</script>
```

## Quick Start

```js
import morph from '@pariharshyamu/morph';

// Morph an element to new HTML
const result = morph(element, '<div class="card">New content</div>');

// Wait for all animations to complete
await result.finished;
```

## API

### `morph(fromEl, toElOrHTML, options?)`

Patches `fromEl` to match `toElOrHTML` (a DOM element or HTML string), animating the transition.

**Returns:** `{ animations, finished, debug, cancel() }`

### Options

| Option | Default | Description |
|--------|---------|-------------|
| `duration` | `400` | Base duration in ms |
| `easing` | `'spring'` | `'spring'` or any CSS easing string |
| `spring` | `{ stiffness: 280, damping: 24, mass: 1 }` | Spring physics parameters |
| `stagger` | `25` | Stagger delay between elements (ms) |
| `keyAttribute` | `'data-morph-key'` | Attribute for explicit element matching |
| `matchConfidenceThreshold` | `1.5` | Below this → treat as enter/exit |
| `respectReducedMotion` | `true` | Skip animation if user prefers reduced motion |
| `morphContent` | `true` | Cross-fade content changes during move |
| `debug` | `false` | Log matching info to console |

### `morph.text(el, newText, options?)`

Cross-fade text content change with a fade-out → swap → fade-in sequence.

```js
await morph.text(heading, 'New Title').finished;
```

### `morph.prepare(fromEl, toEl, options?)`

Create a controllable transition that can be triggered later.

```js
const transition = morph.prepare(el, newEl, { duration: 500 });
transition.play(); // triggers when ready
```

### `MorphObserver`

Auto-animate any DOM mutations on a container:

```js
import { MorphObserver } from '@pariharshyamu/morph';

const observer = new MorphObserver(container, { duration: 350 });
observer.observe();

// Now any mutation to container's children will auto-animate
container.innerHTML = '<div>New content</div>'; // animated!

observer.disconnect();
```

### htmx Extension

```html
<script src="https://unpkg.com/htmx.org"></script>
<script type="module">
  import morph from '@pariharshyamu/morph';
  morph.htmx({ duration: 350 });
</script>

<div hx-ext="morph" hx-get="/api/items" hx-swap="innerHTML">
  <!-- content swaps are now animated -->
</div>
```

### Choreography

Control the timing of exit, move, and enter phases:

```js
morph(el, newHTML, {
  choreography: {
    exit:  { at: 0,    duration: 0.5  },  // fractions of total duration
    move:  { at: 0.15, duration: 0.85 },
    enter: { at: 0.4,  duration: 0.6  },
  }
});
```

### Custom Hooks

```js
morph(el, newHTML, {
  onEnter: (el) => [
    { opacity: 0, transform: 'translateX(-20px)' },
    { opacity: 1, transform: 'translateX(0)' },
  ],
  onLeave: (el) => [
    { opacity: 1 },
    { opacity: 0, transform: 'scale(0.8)' },
  ],
  onMove: (el, fromRect, toRect) => null, // return keyframes or null
});
```

## License

MIT