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

my-library-topiq

v1.5.3

Published

Vue 3 component library: 3D book, SCORM and Flowpaper players, and a framework-agnostic video modal

Readme

my-library-topiq

Vue 3 component library: 3D book, SCORM and Flowpaper players, and a framework-agnostic video modal.

All components are written with <script setup lang="ts"> and the Composition API. Vue is kept external in every build — the library never bundles its own copy.

Installation

npm install my-library-topiq

Vue 3.5 or newer is a peer dependency — the library uses the copy already installed in your project instead of bundling its own.

Usage

Import from the package root:

import {
  Button,
  Book3D,
  ScormPlayer,
  createVideoModal,
  createFetchVideoSourceResolver,
} from 'my-library-topiq';

Or import a single module by its subpath, so nothing else ends up in your bundle:

import Button from 'my-library-topiq/button';
import ScormPlayer from 'my-library-topiq/scormPlayer';
import createVideoModal from 'my-library-topiq/videoModal';

| Subpath | Export | | -------------------- | -------------------------- | | ./button | Button | | ./book3D | Book3D | | ./pageBook3D | PageBook3D | | ./skewButton | SkewButton | | ./scormPlayer | ScormPlayer | | ./flowpaperPlayer | FlowpaperPlayer | | ./videoModal | createVideoModal (default export only) |

Styles are bundled into the JavaScript — importing a component is enough, there is no separate CSS file to include.

Components

Button

| Prop | Type | Default | Description | | ---------- | --------------------------------------------------------------- | ------- | ----------------- | | type | 'primary' \| 'secondary' \| 'success' \| 'danger' \| 'warning' | — | Visual variant | | disabled | boolean | false | Disables the button |

Emits click with the MouseEvent. Content goes into the default slot.

<Button type="primary" @click="onClick">Save</Button>

SkewButton

| Prop | Type | Default | Description | | -------------- | -------------------- | --------- | ------------------------------ | | label | string | '' | Button text | | reversIcon | boolean | false | Puts the arrows before the label | | disabled | boolean | false | Disables the button | | theme | 'light' \| 'dark' | 'light' | Color theme | | customStyles | CustomStyles | {} | Per-instance color overrides |

CustomStyles accepts backgroundColor, textColor, hoverBackgroundColor, shadowColor, hoverShadowColor and arrowColor; each one maps to a CSS custom property on the root element.

Emits click with the MouseEvent.

<SkewButton label="Read" theme="dark" @click="onClick" />

Book3D

Renders an animated 3D book. Pages are provided through the default slot.

| Prop | Type | Default | Description | | -------------- | ---------- | ------- | ------------------------------- | | item | BookType | — | Book data (required) | | outsideOpen | boolean | — | Opened state, supports v-model:outsideOpen | | outsideFlip | boolean | — | Flipped state, supports v-model:outsideFlip |

BookType is { name, description?, img?, color_bg?, color_fg?, color_sh? }.

Slots: default (pages), bookCover, insideContent, imgContent.

Emits update:outsideOpen, update:outsideFlip and readAll (fired when the last page is reached).

<Book3D :item="book" v-model:outsideOpen="isOpen" @readAll="onReadAll">
  <PageBook3D v-for="(page, i) in pages" :key="i" :index="i" />
</Book3D>

PageBook3D

A single page of Book3D.

| Prop | Type | Default | Description | | ------------- | ------------------- | ------------ | -------------------------- | | index | number | 0 | Page order, drives z-index and rotation | | isOpened | boolean | — | Whether the page is turned | | visibleMark | boolean | true | Shows the bookmark | | mark | 'top' \| 'right' | 'right' | Bookmark position | | icon | string | 'pi-check' | Bookmark icon class | | iconColor | string | 'white' | Icon color | | markBkColor | string | 'red' | Bookmark background | | pageBkColor | string | 'white' | Page background |

Emits openPage with the page index, and imLastPage with a boolean.

ScormPlayer

Embeds SCORM content in an iframe, scaling it to the container and driving the internal player API.

| Prop | Type | Default | Description | | ----------- | -------- | ------- | ------------------------------ | | src | string | — | SCORM entry URL (required) | | zoom | number | 50 | Zoom level, in percent | | pageLimit | string | — | Page limit passed to the player |

Emits loaded once the content is measured and scaled, and error with an Error.

<ScormPlayer :src="scormUrl" :zoom="80" @loaded="onLoaded" @error="onError" />

FlowpaperPlayer

Embeds a Flowpaper PDF viewer and relays postMessage events coming from the iframe.

| Prop | Type | Default | Description | | ----- | -------- | ------- | ------------------------- | | src | string | — | Viewer URL (required) |

Supports v-model for the loading state (boolean, true until the iframe fires load).

Emits message with { type: 'CLICKED' | 'ALL', value: string } — messages prefixed with CLICKED: are reported as CLICKED with the prefix stripped, everything else as ALL.

<FlowpaperPlayer v-model="loading" :src="pdfUrl" @message="onMessage" />

createVideoModal

A framework-agnostic video modal, usable outside Vue as well. It appends its own markup to document.body, resolves the video source and renders the matching player.

import createVideoModal from 'my-library-topiq/videoModal';

const videoModal = createVideoModal({ bookName, sourceStrategy: 'local' });

videoModal.pickElement(iframeSpot);                     // with a play overlay
videoModal.pickElement(iframeSpot, { overlay: false }); // without one

Options:

| Option | Type | Default | Description | | -------------------- | ----------------------------------------------- | --------- | ------------------------------ | | bookName | string | — | Used in the default local video path; only required by the default resolver | | sourceStrategy | 'youtube' \| 'local' \| (() => 'youtube' \| 'local') | 'local' | How the source is resolved; a function is re-evaluated on every open | | resolveVideoSource | VideoSourceResolver | createFetchVideoSourceResolver() | Locates the local video file | | youtubeProxyUrl | string | — | Base URL of a proxy to embed YouTube through; without it the embed points at youtube-nocookie.com | | debug | boolean | false | Logs source resolution and the final embed URL to the console |

Returned API:

| Method | Description | | ------------------------------- | -------------------------------------------------------- | | pickElement(iframe, options?) | Binds click handlers to video elements inside the iframe | | openVideoModal(url) | Opens the modal for a URL | | closeVideoModal() | Closes the modal and disposes the active player | | destroy() | Removes listeners and the modal markup from the document |

Locating the local video

By default the local strategy fetches /video-resource/:bookName/:resourceId and falls back to YouTube when that resource is unavailable — your backend needs to serve that endpoint, returning { status: 'ok', path, type }. The base path is configurable:

import { createFetchVideoSourceResolver } from 'my-library-topiq';

createVideoModal({
  bookName,
  resolveVideoSource: createFetchVideoSourceResolver({ baseUrl: '/api/media' }),
});

Lookup is not always an HTTP request, though — in a Capacitor app there is no server to fetch from, and the file lives on the device. Pass your own resolveVideoSource to replace the transport, the URL scheme and the response format all at once:

type VideoSource = {
  src: string;
  type?: string;
  dispose?: () => void; // optional cleanup, e.g. URL.revokeObjectURL
};

type VideoSourceResolver = (
  url: string,
  bookName: string | undefined,
  signal: AbortSignal
) => Promise<VideoSource | null>;

Return null when the video is simply not there — that triggers the YouTube fallback, same as a thrown error. Aborting through signal does not: it means the modal was closed or switched to another video, so nothing is rendered.

import { Capacitor } from '@capacitor/core';
import { Directory, Filesystem } from '@capacitor/filesystem';
import createVideoModal from 'my-library-topiq/videoModal';

const videoModal = createVideoModal({
  bookName,
  resolveVideoSource: async (url, book) => {
    const { pathname, searchParams } = new URL(url, location.origin);
    const id = pathname === '/watch' ? searchParams.get('v') : pathname.split('/')[1];

    try {
      const { uri } = await Filesystem.getUri({
        directory: Directory.Data,
        path: `books/${book}/videos/${id}.mp4`,
      });

      return { src: Capacitor.convertFileSrc(uri), type: 'video/mp4' };
    } catch {
      return null; // not downloaded — fall back to YouTube
    }
  },
});

bookName is only read by the default resolver, so a custom one can ignore it and omit the option entirely — identify the book however your app does.

createFetchVideoSourceResolver is exported from the package root only; the ./videoModal subpath keeps its single default export.

The YouTube strategy renders an iframe directly, with no player library involved.

youtubeProxyUrl

YouTube validates the origin of the embedding page and answers with error 153 when it does not like it. Inside a Capacitor WebView window.location.origin is capacitor://your.host (iOS), which YouTube rejects, and the embed cannot override it. Routing the embed through your own proxy sidesteps the check — the proxy is the page YouTube sees:

const videoModal = createVideoModal({
  bookName,
  youtubeProxyUrl: 'https://proxy.pages.dev/',
});

The video id is appended as ?v=, together with autoplay=1:

https://proxy.pages.dev/?v=dQw4w9WgXcQ&autoplay=1

Query parameters already present in youtubeProxyUrl are preserved, so a proxy that needs its own options (https://proxy.pages.dev/?lang=kk) works as is.

When youtubeProxyUrl is omitted, the embed goes straight to youtube-nocookie.com/embed/:id — fine on a regular https page, subject to error 153 inside a WebView.

debug

Set debug: true to log the extracted video id and the final embed URL under a [VideoModal] prefix.

Development

npm run build    # full Rollup build into dist/
npm run dev      # build in watch mode
npm run deploy   # build + npm publish

There are no tests, linter or dev server in this project. Formatting is handled by Prettier (single quotes, 2 spaces, printWidth 100).

Adding a component

Rollup scans src/components/ and builds every directory it finds, so a new component needs its directory plus three registrations:

  1. src/components/<ComponentName>/ — the component, index.js (or index.ts), index.d.ts and styles/
  2. src/index.js — import, named export, and a field in the default object
  3. src/index.d.ts — the same, plus a field in _default
  4. package.json → exports["./componentName"] with import, require and types paths

Keep the public types inside the component's own index.d.ts rather than importing them through the @/* alias — rollup-plugin-dts does not resolve that alias and would emit a broken declaration.

License

ISC