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

pinterest-gallery

v22.0.0

Published

A masonry Pinterest-style gallery of image cards, built with Angular.

Readme

pinterest-gallery

A masonry Pinterest-style gallery of image cards, built with Angular.

Needs @angular/cdk and @angular/material (^22.1.0) next to @angular/core and @angular/common: the cdk drag and drop for moving cards, and Material for the ripple and the previous and next buttons of the single row.

Models

Every shape in the library follows one pattern: a …Interface and a class of the same name with constructor defaults and a static adapt(item?: any) that builds an instance from raw input (PinterestCard, PinterestCardOptions, SelectionLimit, PinterestSelectionResult, PinterestPoint, PinterestBox); the enum-like values are named types (SelectionMode, RowAlign, SelectionLimitReason, GalleryLayout) in the same folder.

Components

  • app-pinterest-gallery — lays cards out in columns, wrapping lines, equal-height rows or one scrolling row, with gap spacing; can select and reorder cards.
  • app-pinterest-card — a single image card with auto background, text clamping, image bleed, rounded corners, border, elevation and a selected or pressed state.

Card inputs

| Input | Type | Default | Description | |---|---|---|---| | data | PinterestCard | — | Card data (required). | | autoHeight | boolean | false | The card takes the height its parent gives it: the picture is pinned to the bottom of its box, shown whole, and rises out of the top when it is taller than the box. | | maxWidth | number | 0 | Card width cap in pixels; 0 means no cap. Ignored with autoHeight. | | bleedWidth | number | 0 | Image extends beyond each side. | | autoBackground | boolean | true | Derive the background from the image. | | backgroundColor | string | '' | Manual background when autoBackground is off. | | titleLines | number \| 'auto' | 1 | Clamp the title to N lines; 0 or 'auto' means no limit. | | descriptionLines | number | 1 | Clamp the description to N lines; 0 means no limit. | | elevation | number | 0 | Material elevation shadow, 0–5. | | borderRadius | number | 0 | Corner radius in pixels. A card whose picture rises out of its top rounds only its bottom corners. The text padding grows to 0.6 times the radius so text stays clear of the curve. | | borderWidth | number | 0 | Border thickness in pixels, drawn over the card's edge without changing its size. | | borderColor | string | '' | Border colour; '' means the card's own background colour. | | selectedBorderColor | string | '' | Colour of the outline of a selected card; '' means the theme's primary colour. | | selectable | boolean | false | A click gives the card a pressed state ('single') or toggles selected ('multiple', on the card itself; inside a gallery the Shift-click rules apply instead). | | selectionMode | 'single' \| 'multiple' | 'single' | 'single': the card darkens while the pointer is down and nothing stays selected on the card itself (inside a gallery, the gallery picks the clicked card). 'multiple': a click toggles selected, which stays (on the card itself; a gallery applies its own click and Shift-click rules). | | ripple | boolean | false | A Material ripple spreads from the press point while selectable is on, in both modes. It covers a rising picture but not its see-through pixels. In 'multiple' it is the only press effect. | | selected | boolean (two-way) | false | Shows the selected outline. |

Outputs: cardClick (the card's PinterestCard, on every click) and selectedChange (from the two-way selected).

A card with autoHeight needs a height from its parent (the gallery sets one with equalHeight). The card's top bleed is no longer an input: the card height decides how far the picture rises.

Gallery inputs

| Input | Type | Default | Description | |---|---|---|---| | items | PinterestCard[] (two-way) | [] | Cards to display, in order. A drop (with movable) reorders it. | | gap | number | 8 | Space between cards in pixels, horizontally and vertically. | | cols | number | 4 | Number of columns. 0 wraps cards onto lines: as wide as their picture, or cardOptions.cardWidth each. | | colorMap | Partial<Record<string, string>> | {} | Per-card background colour, keyed by card id. | | cardOptions | PinterestCardOptions | see below | Settings applied to every card. | | singleRow | boolean | false | One line of cards that scrolls sideways, with previous and next buttons. | | rowAlign | 'top' \| 'middle' \| 'bottom' | 'bottom' | Where cards of different heights line up in the single row. No effect when singleRow is off or equalHeight is on. | | selectable | boolean | false | A click picks the card ('single'), or picks it and lets Shift add or remove cards ('multiple'), and shows the pressed state. | | selectionMode | 'single' \| 'multiple' | 'single' | 'single': a click picks that card and lets go of the previous one, with no outline; a click on the picked card unselects it. minSelection and maxSelection do not apply. 'multiple': a plain click picks just that card (on the only selected card it unselects it, and a click on one of several selected cards resets to it); Shift+click adds or removes a card; a touch tap toggles, since a finger has no Shift key. Selected cards have an outline. Changed: 'single' used to only press, and 'multiple' used to toggle on a plain click. | | ripple | boolean | false | A Material ripple from the press point, in both modes (needs selectable). | | selectedIds | string[] (two-way) | [] | Ids of the selected cards. A click changes it, and a parent can set it in any mode. | | maxSelection | number | 0 | At most this many cards can be selected ('multiple'); 0 is no limit. Stops a Shift-click or tap that would add past it; a plain click picks one card and is never stopped by it. | | minSelection | number | 0 | A selection of more than one card cannot shrink below this many ('multiple'): a Shift-click or tap that removes a card, and a plain click that would reset several cards to one, are refused; 0 is no limit. The last selected card can always be unselected. With a maxSelection, the lesser of the two is the minimum. | | movable | boolean | false | Cards can be dragged to a new place. With several cards selected, grabbing a selected one moves them all. | | value | PinterestCardRef[] (two-way) | [] | The form control value: the selected cards as ids or objects with an id, kept equal to selectedIds by id. See Form control. | | disabled | boolean | false | Set by a bound form control's disable(). Clicks no longer select, press or emit touch, and the whole gallery looks disabled (38% opacity). Dragging and scrolling still work. |

cardOptions (PinterestCardOptions; PinterestCardOptions.adapt() gives the defaults and PinterestCardOptions.adapt({ borderRadius: 12 }) a partial one): cardWidth 0, bleedWidth 0, titleLines 1, descriptionLines 1, elevation 0, borderRadius 0, borderWidth 0, borderColor '', selectedBorderColor '', equalHeight false, cardHeight 0. A card's background colour still comes from colorMap.

Outputs: cardClick (PinterestCard, on every click), itemsChange (the reordered cards, from the two-way items), selectedIdsChange (the whole list, from the two-way selectedIds) selectionLimit (SelectionLimit: reason 'max' or 'min', limit, card, selectedIds; emitted when a click is ignored by maxSelection or minSelection), touch (every click on a card while selectable is on, even one a limit refuses; not while disabled), selectionChange (PinterestCard[]: the selected cards, whole, each time a user's click, Shift-click or tap changes the selection; not for a patch or a parent setting selectedIds) and valueChange (from the two-way value).

Layouts

| Condition | Layout | |---|---| | singleRow | One scrolling line. cols cards fill the visible width; with cols 0, cardOptions.cardWidth each, or one card per page. | | equalHeight, cols above 0 | Rows of equal height: cardHeight pixels, or the tallest card (a cardHeight below the text block is treated as 0, unless a line count is 0). The space between rows is gap, plus the room a picture rising out of the lower row needs. | | cols above 0 | CSS columns, filled top to bottom. cardWidth is ignored. | | cols 0, cardWidth 0 | Cards as wide as their own picture, wrapping onto the next line. | | cols 0, cardWidth above 0 | Columns of exactly cardWidth, as many as fit, centred. Cards fill the first line left to right, wrap, and sit under the shortest column, so the order is row-first and a card can land earlier than its place in the list. |

The previous and next buttons of the single row use Material Icons: load the Material Icons font in the host page.

Moving cards

With movable, a card can be picked up anywhere and dropped in a new place, in every layout. A faint copy shows where it will land; it only moves while the pointer is over a card (not in a gap), and the cards it pushes aside glide aside. A plain click still emits cardClick. The cdk sets touch-action: none on movable cards, so a touch drag over them does not scroll.

With selectable on in 'multiple' mode and two or more cards selected, picking up a selected card lifts the whole selection: the other selected cards leave their places, the lifted preview shows them fanned out behind the card you hold with a count of how many are moving, and the landing place shows all of them together. On release they land as one block, in the order they had, and itemsChange emits the new order. The selection does not change. Picking up a card that is not selected moves only that card.

Form control

The gallery is a form control for Angular's forms (it implements FormValueControl, with no ControlValueAccessor), so formControlName, [formControl] and ngModel work with no adapter:

import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { PinterestCard, PinterestCardRef, PinterestGalleryComponent } from 'pinterest-gallery';

form = new FormGroup({ cards: new FormControl<PinterestCardRef[]>([], { nonNullable: true }) });
cards: PinterestCard[] = [new PinterestCard('c1', 'One'), new PinterestCard('c2', 'Two'), new PinterestCard('c3', 'Three')];
picked: PinterestCard[] = [];
<form [formGroup]="form">
  <app-pinterest-gallery formControlName="cards" [items]="cards" [selectable]="true" selectionMode="multiple" (selectionChange)="picked = $event" />
</form>
  • Patch: form.patchValue({ cards: ['c1', 'c3'] }). Each entry is a card id or any object with an id (a whole card, or { id: 'c1' }), looked up in items by id. The control keeps exactly what you patched, emits once and is not marked dirty. An id with no card stays in the value and shows nothing. A patch is silent for selectionChange, which reports what the user picks; selectedIdsChange still fires, since it is the two-way selectedIds. The form's own valueChanges fires once for any patchValue: pass { emitEvent: false } to silence it.
  • A patch needs selection on. It only takes effect while selectable is on, and several entries only in selectionMode="multiple"; otherwise it is ignored. An empty patch always clears.
  • A click (a plain click picks one card, Shift+click adds or removes) writes the selected cards, as objects, in selection order, to the control (valueChanges); the control is then marked dirty. selectionChange gives the same cards.
  • Disable: form.controls.cards.disable() sets disabled: no clicks select, the gallery looks disabled; enable() brings it back.
  • Touched: touch marks the control touched on every click on a card while selectable is on. Cards cannot take keyboard focus, so there is no blur.
  • Validation belongs to the form, not the gallery: a rule such as a minimum number of cards is a validator on the control (minSelection and maxSelection only refuse clicks).

Migrating

| Before | Now | |---|---| | Card autoWidth | Removed. Cards fill their column; use maxWidth to cap. | | Card bleedTop | Removed. Give the card a height (autoHeight, or the gallery's equalHeight and cardHeight) and the picture rises as far as it needs to. | | Card maxWidth unset | 0 means no cap (default). | | Card titleLines 'auto' | Default 1; use 0 or 'auto' for no limit. | | Card descriptionLines 0 (no limit) | Default 1; 0 still means no limit. | | A card reserved room for titleLines lines of title | It no longer does: a shorter title is followed by 0.5rem. | | Gallery gap 16 | Default 8. | | Gallery items one-way | Two-way; a one-way binding still works. | | Gallery selectionMode 'multiple', a click toggled a card | A plain click now picks just that card; hold Shift to add or remove cards. A touch tap still toggles. Breaking. Cards cannot take keyboard focus, so multi-select needs Shift with a mouse or a tap. | | Gallery selectionMode 'single' only pressed | It now picks one card: selectedIds holds it and selectedIdsChange fires. A picked card has no outline. | | PinterestCardData interface | Renamed PinterestCardInterface; PinterestCard now also has PinterestCard.adapt(raw). | | DEFAULT_CARD_OPTIONS constant | Gone: PinterestCardOptions.adapt() gives the defaults, PinterestCardOptions.adapt({ ... }) a partial. |

Usage

import { PinterestCard, PinterestCardOptions, PinterestGalleryComponent } from 'pinterest-gallery';

options = PinterestCardOptions.adapt({ borderRadius: 12, borderWidth: 2, equalHeight: true, cardHeight: 320 });
<app-pinterest-gallery
  [(items)]="cards"
  [cols]="4"
  [gap]="8"
  [cardOptions]="options"
  [selectable]="true"
  selectionMode="multiple"
  [(selectedIds)]="selected"
  [movable]="true"
/>