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

@masonrykit/core

v0.2.0

Published

Pure, framework-agnostic layout computation for masonry grids. Multi-span items, stamps, and deterministic positioning.

Readme

@masonrykit/core

npm bundle size MIT License

Pure, framework-agnostic math for masonry-style grid layouts. Zero runtime dependencies, no DOM, no React — just input cells in, positions out.

If you're rendering to the DOM, the higher-level packages — @masonrykit/browser and @masonrykit/react — wrap this core in ergonomic helpers that hand you the layout data plus the refs/observers you need to wire the DOM. Every visual decision (element choice, CSS var naming, positioning strategy, animation) lives in your code. Core itself has no opinion on output.

Install

npm install @masonrykit/core

Quick start

import { computeLayout, heightCell, aspectCell } from '@masonrykit/core'

const cells = [heightCell('a', 100), aspectCell('b', 16 / 9), heightCell('c', 50)]

const layout = computeLayout(cells, {
  gridWidth: 800,
  columnWidth: 200,
  gap: 16,
})

for (const cell of layout.cells) {
  console.log(cell.id, cell.x, cell.y, cell.width, cell.height)
}
// layout.width / layout.height / layout.columns are also set

API

Cell factories

The simplest way to build cells — each factory produces the right discriminated-union shape and threads a typed meta field when supplied.

heightCell(id: string, height: number, options?: { columnSpan?; meta? }): HeightCell
aspectCell(id: string, aspectRatio: number, options?: { columnSpan?; meta? }): AspectCell
measuredCell(id: string, options?: { columnSpan?; estimatedHeight?; meta? }): MeasuredCell

When you pass a concrete M type, meta is required, so cell.meta.src type-checks without a non-null assertion:

interface Photo {
  src: string
  alt: string
}

const cell = heightCell<Photo>('p1', 200, { meta: { src: '/a.jpg', alt: 'A' } })
cell.meta.src // '/a.jpg' — no `!` needed

Cell types

type HeightCell<M = undefined> = {
  id: string
  type: 'height'
  height: number
  columnSpan?: number
} & Meta<M>

type AspectCell<M = undefined> = {
  id: string
  type: 'aspect'
  aspectRatio: number // width / height; must be > 0
  columnSpan?: number
} & Meta<M>

/**
 * A cell whose height is discovered from the DOM (e.g. via ResizeObserver).
 * Framework bindings attach an observer to the rendered element and either
 * replace the cell with a `HeightCell` once measured, or keep passing it
 * through — `computeLayout` uses `estimatedHeight` until a real measurement
 * lands.
 */
type MeasuredCell<M = undefined> = {
  id: string
  type: 'measured'
  columnSpan?: number
  estimatedHeight?: number
} & Meta<M>

type Cell<M = undefined> = HeightCell<M> | AspectCell<M> | MeasuredCell<M>

// Meta<M> — when M is supplied, `meta` is required; otherwise it's optional.
type Meta<M> = [M] extends [undefined] ? { meta?: M } : { meta: M }

computeLayout(cells, options)

type LayoutOptions = {
  gridWidth: number // required, >= 0
  columnWidth?: number // desired column width; defaults to gridWidth (single column)
  gap?: number // default 0
  horizontalOrder?: boolean // default false — shortest-column placement
  stamps?: readonly Stamp[] // pixel-aligned reserved rectangles
  columnStamps?: readonly ColumnStamp[] // column-aligned reserved rectangles
}

type Layout<M = undefined> = {
  cells: LayoutCell<M>[]
  width: number
  height: number
  columns: Columns
}

type LayoutCell<M = undefined> = {
  index: number
  id: string
  column: number
  span: number
  x: number
  y: number
  width: number
  height: number
} & Meta<M>

Throws Error if any AspectCell has a non-positive aspectRatio.

computeColumns(options)

type Columns = { count: number; width: number; gap: number }

function computeColumns(options: { gridWidth: number; columnWidth?: number; gap?: number }): Columns

Resolves how many columns fit. Returns pixel-rounded width and gap.

columnStampsToPixels(stamps, columns)

type ColumnStamp = { column: number; span: number; y: number; height: number }
type Stamp = { x: number; y: number; width: number; height: number }

function columnStampsToPixels(
  stamps: readonly ColumnStamp[],
  columns: Pick<Columns, 'width' | 'gap'>,
): Stamp[]

Useful for pre-computing pixel stamps. computeLayout also accepts columnStamps directly.

resolveBreakpoint(breakpoints, gridWidth)

type Breakpoint = { minWidth: number; columnWidth?: number; gap?: number }

function resolveBreakpoint(
  breakpoints: readonly Breakpoint[],
  gridWidth: number,
): Breakpoint | undefined

Returns the entry with the largest minWidth <= gridWidth, or undefined if none match. Pure — callers merge matched fields with their defaults:

const tiers: Breakpoint[] = [
  { minWidth: 0, columnWidth: 160, gap: 8 },
  { minWidth: 768, columnWidth: 220, gap: 12 },
  { minWidth: 1280, columnWidth: 280, gap: 16 },
]

const match = resolveBreakpoint(tiers, gridWidth)
const columnWidth = match?.columnWidth ?? defaultColumnWidth
const gap = match?.gap ?? defaultGap

filterVisibleCells(cells, gridTop, viewport, overscan?)

type Viewport = { top: number; bottom: number }

function filterVisibleCells<M>(
  cells: readonly LayoutCell<M>[],
  gridTop: number,
  viewport: Viewport,
  overscan?: number, // default 0
): readonly LayoutCell<M>[]

Pure geometry. Given laid-out cells, the grid's top edge, and viewport bounds (all in the same coord space — usually viewport-relative pixels), returns the subset that intersects the viewport ± overscan. Browsers read the actual numbers from getBoundingClientRect() / window.innerHeight and pass them in. Used by the React hook's virtualize option.

Features at a glance

  • Three cell shapes via discriminated union (height / aspect / measured)
  • Multi-span items — cells can span multiple columns
  • Stamps — reserve space with pixel or column-aligned rectangles
  • Horizontal ordering — optional row-wise placement instead of shortest-column
  • Responsive breakpoints (resolveBreakpoint) and viewport filtering (filterVisibleCells) primitives
  • Pure & deterministic — no side effects, no DOM, no hidden state
  • TypeScript-first — generic meta flows from input to output with no casts

License

MIT