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

pustak

v1.0.4

Published

A premium, dependency-free page-flip library written in pure TypeScript.

Downloads

743

Readme

📖 Pustak Pageflip Engine

A premium, high-performance, and dependency-free page-flip library written in pure TypeScript.

Pustak mimics the physical tactile experience of flipping a book page. By utilizing GPU-accelerated CSS 3D transforms, real-time geometry calculations, and dynamic CSS clip-path polygons, it runs smoothly (60+ FPS) even on mobile web browsers, keeping all inner content fully readable by SEO crawlers, screen readers, and selectable by users.


🛠️ Prerequisites & System Requirements

To run, build, or contribute to Pustak, verify your environment meets the following specifications:

| Requirement | Supported Version | Notes | | :--- | :--- | :--- | | Node.js | v18.0.0 or higher | Recommended: v20.x LTS or higher. | | NPM | v9.0.0 or higher | Standard package manager (Yarn / PNPM work perfectly as well). | | Browsers | Modern Browsers | Requires modern CSS standard support for 3D perspective (transform-style: preserve-3d) and clip-path polygons. |

🔌 Integration Examples

Pustak can be integrated into any front-end environment. Below are complete examples for Normal HTML / Vanilla JavaScript, React, and Angular.

1. Normal HTML / JavaScript

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pustak - HTML Integration</title>
  <!-- Import Pustak Styles -->
  <link rel="stylesheet" href="node_modules/pustak/dist/style.css">
  <style>
    #book-container {
      width: 760px; /* 380px * 2 for double-spread */
      height: 500px;
      margin: 50px auto;
      box-shadow: 0 10px 30px rgba(0,0,0,0.15);
    }
    .page {
      background: #fbf8eb;
      padding: 30px;
      box-sizing: border-box;
    }
  </style>
</head>
<body>

  <div id="book-container">
    <div class="page">Page 1 (Cover)</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>
    <div class="page">Page 4 (Back Cover)</div>
  </div>

  <div style="text-align: center;">
    <button id="prev-btn">Previous</button>
    <button id="next-btn">Next</button>
  </div>

  <!-- Initialize with ES Modules -->
  <script type="module">
    import { Book } from './node_modules/pustak/dist/pustak.js';

    const container = document.getElementById('book-container');
    const book = new Book(container, {
      width: 380,
      height: 500,
      mode: 'double',
      autoCenter: true,
      hardCovers: true
    });

    document.getElementById('prev-btn').addEventListener('click', () => book.prev());
    document.getElementById('next-btn').addEventListener('click', () => book.next());
  </script>
</body>
</html>

2. React

import React, { useEffect, useRef } from 'react';
import { Book } from 'pustak';
import 'pustak/style.css'; // Ensure you import CSS in your entrypoint or component

export const BookViewer: React.FC = () => {
  const containerRef = useRef<HTMLDivElement>(null);
  const bookRef = useRef<Book | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    // Initialize Pustak
    const book = new Book(containerRef.current, {
      width: 380,
      height: 500,
      mode: 'double',
      autoCenter: true,
      hardCovers: true
    });

    bookRef.current = book;

    // Clean up on unmount
    return () => {
      book.destroy();
      bookRef.current = null;
    };
  }, []);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px' }}>
      {/* Book Container */}
      <div ref={containerRef} style={{ boxShadow: '0 10px 30px rgba(0,0,0,0.15)' }}>
        <div className="page" style={{ background: '#fbf8eb', padding: '30px' }}>Cover Page</div>
        <div className="page" style={{ background: '#fbf8eb', padding: '30px' }}>Page 2</div>
        <div className="page" style={{ background: '#fbf8eb', padding: '30px' }}>Page 3</div>
        <div className="page" style={{ background: '#fbf8eb', padding: '30px' }}>Back Cover</div>
      </div>

      {/* Controls */}
      <div>
        <button onClick={() => bookRef.current?.prev()}>Previous</button>
        <button onClick={() => bookRef.current?.next()} style={{ marginLeft: '10px' }}>Next</button>
      </div>
    </div>
  );
};

3. Angular

import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { Book } from 'pustak';

@Component({
  selector: 'app-book-viewer',
  standalone: true,
  template: `
    <div class="viewer-wrapper">
      <div #bookContainer class="book-container">
        <div class="page">Front Cover</div>
        <div class="page">Page 2</div>
        <div class="page">Page 3</div>
        <div class="page">Back Cover</div>
      </div>
      
      <div class="controls">
        <button (click)="onPrev()">Previous</button>
        <button (click)="onNext()">Next</button>
      </div>
    </div>
  `,
  styles: [`
    .viewer-wrapper { display: flex; flex-direction: column; align-items: center; gap: 20px; }
    .book-container { box-shadow: 0 10px 30px rgba(0,0,0,0.15); }
    .page { background: #fbf8eb; padding: 30px; box-sizing: border-box; }
    .controls button { margin: 0 5px; padding: 8px 16px; cursor: pointer; }
  `]
})
export class BookViewerComponent implements AfterViewInit, OnDestroy {
  @ViewChild('bookContainer') bookContainer!: ElementRef<HTMLElement>;
  private book: Book | null = null;

  ngAfterViewInit(): void {
    this.book = new Book(this.bookContainer.nativeElement, {
      width: 380,
      height: 500,
      mode: 'double',
      autoCenter: true,
      hardCovers: true
    });
  }

  onPrev(): void {
    this.book?.prev();
  }

  onNext(): void {
    this.book?.next();
  }

  ngOnDestroy(): void {
    if (this.book) {
      this.book.destroy();
    }
  }
}

Note: Make sure to import pustak/style.css in your global stylesheet (e.g., styles.scss or angular.json).


⚙️ API Documentation

Constructor Options (BookOptions)

When calling new Book(container, options), configure the system using these options:

| Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | width | number | Required | Width of a single page in pixels (px). | | height | number | Required | Height of a page in pixels (px). | | mode | 'single' \| 'double' | 'double' | Display spread mode. double shows left + right spreads; single is ideal for mobile screens. | | autoCenter | boolean | true | When true in double mode, centers single cover/back cover pages on screen. | | hardCovers | boolean | true | If true, treats the first page and last page as rigid card covers. | | paginationRequired | boolean | false | If true, enables DOM virtualization/pruning to keep only active pages inside the DOM to improve performance. | | pagesLimit | number | 2 | Number of buffer pages (enforced as an even number). Keeps currentPage - pagesLimit/2 to currentPage + pagesLimit/2 in the DOM and prunes the rest. E.g., if set to 6, it maintains 3 previous and 3 next pages in the DOM. |


Public Methods

| Method | Parameters | Return Type | Description | | :--- | :--- | :--- | :--- | | next() | None | void | Triggers a smooth page-turn animation forward to the next page. | | prev() | None | void | Triggers a smooth page-turn animation backward to the previous page. | | jumpTo(pageIndex) | pageIndex: number | void | Jumps instantly to the selected page index (without turn animations). | | setMode(mode) | 'single' \| 'double' | void | Switches orientation mode on the fly between single page and double-spread. | | getCurrentPage() | None | number | Returns the current visible page index. | | getPageCount() | None | number | Returns the total count of pages detected inside the container. | | on(event, handler) | event: EventKey, handler: EventHandler | void | Adds a listener for custom engine events. | | off(event, handler) | event: EventKey, handler: EventHandler | void | Removes an active event listener. | | destroy() | None | void | Cleans up engine events and resources. |


Custom Event Hooks

Listen to key library lifecycles using .on('eventName', data => { ... }):

  • 'flip': Dispatched when a page-turn starts.
    book.on('flip', (data) => {
      console.log(`Flipping started. Current page: ${data.page}, Direction: ${data.direction}`);
    });
  • 'update': Dispatched on every pixel of page-dragging or auto-flipping progress (0 to 1).
    book.on('update', (data) => {
      console.log(`Flip progress: ${data.progress} on corner ${data.corner}`);
    });
  • 'flipped': Dispatched when a page turn finishes completely.
    book.on('flipped', (data) => {
      console.log(`Flip completed. Active Page: ${data.page}`);
    });
  • 'jump': Dispatched after calling .jumpTo().
  • 'orientation': Dispatched when .setMode() toggles display layout.

🎨 Interactive DOM & Sandbox Elements

Unlike canvas libraries, Pustak leaves all inner controls responsive. If you place interactive widgets on a page (like text boxes, canvas sketches, slider bars, or buttons):

  1. Bind your event listeners as usual.
  2. In your button click handlers or interactive inputs, call event.stopPropagation() on mousedown, touchstart, or click events to prevent the Pustak interaction engine from capturing mouse coordinates as a book drag.

For example, a widget inside the book page can be safely set up like this:

const widgetButton = document.getElementById('widget-btn');

widgetButton.addEventListener('click', (e) => {
  e.stopPropagation(); // Prevents page from turning when clicking this button
  // Run custom action
});

📬 Contact & Support

For commercial licensing, custom integration assistance, inquiries, or support, please reach out to:


🛡️ License

Pustak is licensed under a dual-license model:

  • Personal / Non-Commercial Use: Free for personal, educational, and non-commercial projects.
  • Commercial Use: Requires a paid commercial license. Please contact the author at [email protected] to purchase a commercial license or discuss commercial usage rights.