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

prettier-modals-angular

v0.2.1

Published

Angular directives and service for Prettier Modals — beautiful <dialog> animations powered by GSAP Flip.

Downloads

526

Readme

prettier-modals-angular

Angular directives and an injectable service for Prettier Modals — beautiful open/close animations for native <dialog> elements, powered by GSAP Flip.

Standalone (no NgModule), SSR-safe, and runs animations outside the Angular zone.

Installation

npm install prettier-modals-angular prettier-modals gsap

prettier-modals, gsap, and @angular/core/@angular/common are peer dependencies (Angular >=17).

Register the GSAP plugins once, e.g. in main.ts:

import gsap from 'gsap'
import { Flip } from 'gsap/Flip'
import { CustomEase } from 'gsap/CustomEase'

gsap.registerPlugin(Flip, CustomEase)

Declarative usage (directives)

Import the directives you need — or PRETTY_MODAL_DIRECTIVES for all of them — into a standalone component:

import { Component } from '@angular/core'
import { PRETTY_MODAL_DIRECTIVES } from 'prettier-modals-angular'

@Component({
  selector: 'app-settings',
  standalone: true,
  imports: [PRETTY_MODAL_DIRECTIVES],
  template: `
    <button [prettyModalTrigger]="'settings'" anchor="origin">Open</button>

    <dialog id="settings" prettyModal anchor="origin"
            (opened)="onOpen()" (closed)="onClose()">
      <h1>Settings</h1>
      <button prettyModalClose>Close</button>
    </dialog>
  `,
})
export class SettingsComponent {
  onOpen() {}
  onClose() {}
}

No ViewChild, no NgZone boilerplate — the directives wire everything to the core.

Directives

| Directive | Selector | Description | |---|---|---| | PrettyModalDirective | dialog[prettyModal] | Marks the dialog (needs an id). Inputs: anchor, animateCancel. Outputs: (opened), (closed). | | PrettyModalTriggerDirective | [prettyModalTrigger] | Opens the dialog whose id/element it's bound to, morphing from the host. Inputs: anchor, duration, openDuration, scale, originGap, respectReducedMotion. | | PrettyModalCloseDirective | [prettyModalClose] | Closes the nearest ancestor <dialog> (or a given id/element). Inputs: duration, closeDuration. |

animateCancel (default true) intercepts the native Escape key so it closes with the animation instead of instantly.

Any input left unset falls back to the core instance defaults, so you only override what you need:

<button [prettyModalTrigger]="'menu'" anchor="origin" [originGap]="8" [openDuration]="0.6">Menu</button>

Global defaults (providePrettyModal)

Some core options — ease and originEase (the CustomEase paths) — are compiled once when the core is built, so they're configured at the app level rather than per call. Use providePrettyModal to set them (and any other defaults) for the whole app:

import { bootstrapApplication } from '@angular/platform-browser'
import { providePrettyModal } from 'prettier-modals-angular'

bootstrapApplication(AppComponent, {
  providers: [
    providePrettyModal({
      anchor: 'origin',
      originGap: 8,
      ease: 'M0,0 C0.25,0.1 0.25,1 1,1',
      originEase: 'M0,0 C0.34,1.56 0.64,1 1,1',
    }),
  ],
})

The config accepts anchor, duration, openDuration, closeDuration, ease, originEase, scale, originGap and respectReducedMotion. Per-call options (on the directives or service) still override these defaults — except ease/originEase, which are only configurable here.

Trigger and dialog in separate components

The trigger and the <dialog> don't have to live in the same component. They're linked only by the dialog's id: the PrettyModalService is a root singleton shared by the whole app, and the core resolves the dialog through document.getElementById. So you can wrap each part in its own reusable component:

// button.component.ts — knows only the target dialog id
import { Component, Input } from '@angular/core'
import { PrettyModalTriggerDirective, type PrettyModalAnchor } from 'prettier-modals-angular'

@Component({
  selector: 'app-modal-button',
  standalone: true,
  imports: [PrettyModalTriggerDirective],
  template: `<button [prettyModalTrigger]="target" [anchor]="anchor"><ng-content /></button>`,
})
export class ModalButtonComponent {
  @Input({ required: true }) target!: string
  @Input() anchor?: PrettyModalAnchor
}
// modal.component.ts — owns the dialog, declared independently
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { PrettyModalDirective, PrettyModalCloseDirective, type PrettyModalAnchor } from 'prettier-modals-angular'

@Component({
  selector: 'app-modal',
  standalone: true,
  imports: [PrettyModalDirective, PrettyModalCloseDirective],
  template: `
    <dialog [id]="modalId" prettyModal [anchor]="anchor"
            (opened)="opened.emit($event)" (closed)="closed.emit($event)">
      <button prettyModalClose>Close</button>
      <ng-content />
    </dialog>
  `,
})
export class ModalComponent {
  // Don't name this input `id`: a native `id` would also be reflected onto the
  // host <app-modal>, duplicating the id and breaking document.getElementById.
  @Input({ required: true }) modalId!: string
  @Input() anchor?: PrettyModalAnchor
  @Output() opened = new EventEmitter<HTMLDialogElement>()
  @Output() closed = new EventEmitter<HTMLDialogElement>()
}

A parent just drops both in and matches the id:

<app-modal-button target="settings" anchor="origin">Open</app-modal-button>

<app-modal modalId="settings" anchor="origin">
  <h1>Settings</h1>
</app-modal>

Two things to keep in mind:

  • The dialog id must be unique in the document — Angular's view encapsulation scopes CSS, not id attributes. Avoid exposing a wrapper input literally named id: Angular reflects it onto the host element too, so it ends up duplicated (use modalId or similar, as above).
  • The <dialog> must be rendered in the DOM when the trigger fires. Don't strip it with @if/*ngIf; it stays hidden until opened anyway.

Imperative usage (service)

import { Component, inject } from '@angular/core'
import { PrettyModalService } from 'prettier-modals-angular'

@Component({ /* … */ })
export class MyComponent {
  private readonly modal = inject(PrettyModalService)

  open(trigger: HTMLElement) {
    this.modal.open('settings', { trigger, anchor: 'origin' })
  }

  close() {
    this.modal.close('settings')
  }
}

PrettyModalService lazily creates the core in the browser only (SSR-safe) and runs every animation outside the Angular zone.

Updating

This wrapper and the vanilla core (prettier-modals) are two separate packages with independent versions. Update both so the wrapper runs against the matching core:

npm outdated prettier-modals-angular prettier-modals      # any newer versions?
npm update prettier-modals-angular prettier-modals gsap   # update within your "^" ranges

For a major jump, install the latest explicitly:

npm install prettier-modals-angular@latest prettier-modals@latest

Get notified of new versions: every release is published as a GitHub Release. Click Watch → Custom → Releases on the repo to be notified whenever a new version ships, and check each release's notes for what changed.

License

MIT © Antonio Monreal Diaz