npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@underlying/flip

v1.2.1

Published

Physics-first FLIP for @underlying/core: animate layout and shared-element transitions from old place to new, position and size - interruptible, retargeting mid-flight from live velocity.

Downloads

3,118

Readme

@underlying/flip

Layout and shared-element transitions, physics-first.

Animate an element from its old place to its new one - both position and size - by measuring its box before and after a DOM change and springing the difference away. The play is a real spring, so it is interruptible: change the layout again mid-flight and each element retargets from its live position and velocity, bending into the new layout instead of restarting. Built on @underlying/core; 0.86 kB gzip on top of it (full surface, core marked external).

npm install @underlying/core @underlying/flip

Layout: flip(targets, mutate)

Wrap any DOM change. flip measures First, runs your mutation, measures Last, applies the inverse transform so nothing jumps, then springs to identity.

import { flip } from '@underlying/flip'

// reorder, filter, resize - the tiles spring to their new places
flip(tiles, () => grid.append(...reordered), { stiffness: 320, damping: 26 })

// position AND size: a grid <-> list toggle resizes every tile smoothly
flip(tiles, () => grid.classList.toggle('list'))

By default it inverts position and size (translate + scale, pinned to the top-left). Pass { scale: false } for position-only FLIP - the element translates from old place to new but is never scaled, so its box keeps its natural dimensions throughout. Use it when the size does not change, or when a child should not be squashed by a parent's scale:

// reorder a list: the rows move, but each row keeps its own height
flip(rows, () => list.prepend(rows[2]), { scale: false, stiffness: 320, damping: 26 })

Shared element: snapshot() + play()

When the old and new elements are different DOM nodes (a thumbnail expanding into a detail view, a route change), capture the old set, change the DOM, then play the new set from the captured boxes - matched by data-flip-id.

import { snapshot, play } from '@underlying/flip'

const state = snapshot(thumbnails)   // data-flip-id on each
// ... navigate / re-render: the detail view mounts ...
play(state, { targets: detailEls, stiffness: 260, damping: 24 })

A target with no matching key in the snapshot is left alone.

Drag-to-reorder: reorder()

Drag an item and the displaced siblings FLIP into their new slots; on drop the dragged item springs into place. The new order is reported on every change.

import { reorder } from '@underlying/flip'

const list = reorder(container, {
  axis: 'y',              // 'y' (default) | 'x' | 'both' (a wrapping grid)
  handle: '.grip',        // optional: only start a drag from this element
  onReorder: ({ item, from, to, order }) => save(order),
})

list.order()   // the items in their current order
list.dispose() // remove the listeners

Built on flip(): each reorder measures the siblings, mutates the DOM order, and springs them, while the dragged item is kept under the pointer across the mutation. Physics-first and interruptible - grab another item mid-settle. reorder() tree-shakes out for callers that only use flip()/play()/snapshot(). (The container's animated children should be the reorder items; other siblings are not accounted for.)

Interruption

Every flip() / play() retargets from the live spring, velocity conserved - press the button again while the tiles are still moving and the motion redirects, never restarts. That is the whole point.

Options and types

FlipTargets is what every entry point accepts: a single HTMLElement or any Iterable<HTMLElement> (an array, a NodeList, a Set).

type FlipTargets = HTMLElement | Iterable<HTMLElement>

FlipOptions extends the core SpringOptions (stiffness, damping, mass, ...) - those tune the spring that carries each element home - plus two flip-specific fields:

interface FlipOptions extends SpringOptions {
  scale?: boolean      // invert size too (scale), not only position. Default true.
  scheduler?: Scheduler
}

play() takes a FlipPlayOptions instead: the same FlipOptions, with the new elements to animate passed in as targets (matched to the snapshot by data-flip-id).

interface FlipPlayOptions extends FlipOptions {
  targets: FlipTargets   // the new elements to play from the captured boxes
}

snapshot() returns a FlipSnapshot: an opaque, read-only capture of each target's box, keyed by its data-flip-id (or the element itself when no id is set). Hold it across the DOM change, then hand it to play().

interface FlipSnapshot {
  readonly boxes: ReadonlyMap<string | HTMLElement, Box>
}

License

MIT (c) underlyi.ng