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

turn-ts-vue

v0.1.1

Published

Vue 3.5 bindings for turn-ts. A page-flip book component with v-model paging and a typed imperative api.

Readme

turn-ts-vue

Vue 3.5 bindings for turn-ts, the framework-agnostic page-flip engine. Two components, v-model:page, typed emits, and the live engine on a template ref.

[!IMPORTANT] Not open source. Non-commercial use only. The runtime dependency turn-ts is a derivative of turn.js (3rd release), whose licence permits use "solely for personal benefit and not for any commercial purpose or for monetary gain." That restriction reaches this package through the dependency and cannot be removed here. Read LICENSE.md before installing.

Install

npm i turn-ts-vue

vue ^3.5 is a peer dependency. turn-ts is a direct dependency — you do not install it yourself, but you do import its stylesheet.

Use

<script setup lang="ts">
import { TurnBook, TurnPage } from 'turn-ts-vue';
import 'turn-ts/turn-ts.css'; // required — the fold has no styles without it
</script>

<template>
  <TurnBook :width="840" :height="560" display="double">
    <TurnPage>Cover</TurnPage>
    <TurnPage>Page two</TurnPage>
    <TurnPage>Page three</TurnPage>
    <TurnPage>Back</TurnPage>
  </TurnBook>
</template>

turn-ts/turn-ts.css is not optional and is not bundled into this package — it carries the rules the fold needs, and just as importantly it leaves out the ones that break it (no overflow, no contain, no perspective on the container). Import it once, anywhere, or copy its rules into your own sheet.

Paging with v-model

<script setup lang="ts">
import { ref } from 'vue';
import { TurnBook, TurnPage } from 'turn-ts-vue';

const page = ref(1);
</script>

<template>
  <TurnBook v-model:page="page" :width="840" :height="560">
    <TurnPage v-for="chapter in chapters" :key="chapter.id">{{ chapter.body }}</TurnPage>
  </TurnBook>

  <p>Page {{ page }}</p>
</template>

Set the model and the book turns there; drag the book and the model follows once the turn lands. Bind :page without the model and it still works as the starting page and as a programmatic target — the book just has nowhere to report back to. defaultPage is the same starting-page role for code that never wants to touch page at all.

"Already on page" means in view, not equal. A display="double" book shows a spread, so 4 and 5 hold the same two leaves open and neither turns to the other. The book only emits update:page when the value it holds has gone off screen, so a model sitting on 5 is not silently rewritten to 4 for a spread that never moved.

The imperative api

<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { TurnBook, TurnPage, type TurnBookExposed } from 'turn-ts-vue';

const book = useTemplateRef<TurnBookExposed>('book');
</script>

<template>
  <TurnBook ref="book" :width="840" :height="560">…</TurnBook>

  <button type="button" @click="book?.api?.previous()">Previous</button>
  <button type="button" @click="book?.api?.next()">Next</button>
</template>

api is the live TurnBookApinext, previous, page, pages, view, range, size, display, configure, resize, update, disable, stop, animating, hasPage, addPage, removePage, destroy. It is null before onMounted has run and null again after unmount. destroy is the component's to call; calling it yourself leaves the component holding a dead book.

TurnBook props

| Prop | Type | Notes | | --- | --- | --- | | width, height | number | Whole book in px — both leaves in display="double". Omit to size from the container. | | page | number | The v-model:page value. Current page, 1-based. | | defaultPage | number | Starting page when page has no value. | | display | 'single' \| 'double' | | | gradients | boolean | | | duration | number | Turn duration in ms. | | cornerSize | number | Size of the grabbable corner hit area in px. | | corners | 'backward' \| 'forward' \| 'all' \| ('tl'\|'tr'\|'bl'\|'br')[] | Which corners fold. | | disabled | boolean | Refuses pointer folds. Turns through the api still work. |

Emits: ready, start, turning, turn, turned, first, last, plus update:page and page-change(page, view) alongside turned, and init-error(error). All but start carry a TurnBookPageEvent of { type, api, page, view }; start additionally carries the corner that was grabbed and a preventDefault() that refuses the fold.

inheritAttrs is off and attributes are applied by hand, so declared emits never leak onto the container. Everything else you pass (id, role, aria-*, tabindex, @keydown, class, style, data-*) lands on it untouched.

If createTurnBook throws and nothing is listening for init-error, the error is rethrown from onMounted and reaches your nearest onErrorCaptured. A book that silently did not build is worse.

TurnPage props

Renders the host-owned shape the engine documents:

<div class="turn-page-wrapper">      <!-- Vue's node. The engine styles it, never moves it. -->
  <div class="turn-page">            <!-- The engine's node to fold. -->
    <div class="turn-page-content">  <!-- Your slot content, clipped to the page box. -->

The wrapper is the component's root, so it is the only node Vue ever inserts into or removes from the book — which is what keeps a re-render mid-fold from throwing NotFoundError at a page element the engine has lifted out. class, style and every other attribute land on the .turn-page; wrapperClass and wrapperStyle reach the wrapper, and a template ref exposes element, the .turn-page node.

Pages are read from the DOM in order, so key a v-for the way you would key any list — insert one in the middle and it becomes the middle page.

Styling and accessibility

Headless: the only class this package renders on the container is .turn-book, and the only inline styles it writes there are --turn-book-width and --turn-book-height. Colour, borders, shadows and type are yours. Do not set width/height in style — those belong to the engine, and Vue would fight it for them on every patch. Use the width/height props.

The book has no keyboard affordance of its own, because a page-turn is a visual convenience and the navigation belongs to your app. Give it one — the attributes pass straight through:

<TurnBook
  ref="book"
  :width="840"
  :height="560"
  role="region"
  aria-label="Field notes"
  tabindex="0"
  @keydown.right="book?.api?.next()"
  @keydown.left="book?.api?.previous()"
>
  …
</TurnBook>

Pages outside the current view are hidden with display: none, so assistive technology sees only what is open. Give each TurnPage real headings and put anything that must always be reachable — a table of contents, a "read as one page" fallback — outside the book.

Notes

  • SSR is safe. turn-ts touches no DOM at module scope, and onMounted does not run on the server. The container renders sized from CSS custom properties and the fold arrives on mount, so Nuxt and vue/server-renderer need no <ClientOnly> wrapper around it.
  • Teardown runs in onBeforeUnmount, ahead of Vue removing the container, so every page node the engine had lifted into its fold layer is back where Vue expects it before Vue detaches it.
  • The components are render functions, not SFCs. The published package is plain ESM + CJS with .d.ts; nothing in your build or ours needs a .vue compiler to consume it.
  • The pages option is not exposed. It truncates by calling removePage, which throws for the host-owned wrappers TurnPage renders. Render fewer children instead.
  • The engine re-measures itself on pointerdown and through a ResizeObserver. Call api.resize() by hand only if you defeat both.

Develop

npm install
npm run lint    # typecheck + tests + build + publint + attw

Tests run on node --test through tsx, with Happy DOM registered as the global DOM and a real createApp. Happy DOM has no layout engine, so fold geometry is not covered here — that is turn-ts's problem. What is covered is this package's: mount timing, teardown, the exposed api, v-model paging, option and disabled pushing, dynamic slot children, initialization failure reporting, attribute pass-through, and unmounting mid-fold without a NotFoundError.

License

Non-commercial. See LICENSE.md — turn.js 3rd release terms, inherited through turn-ts.