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-svelte

v0.1.1

Published

Svelte 5 bindings for turn-ts. A page-flip book component built on runes, shipped as Svelte source.

Readme

turn-ts-svelte

Svelte 5 bindings for turn-ts, the framework-agnostic page-flip engine. Two components, a bindable api, and runes throughout — no store shims, no createEventDispatcher.

[!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-svelte

svelte ^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 lang="ts">
	import { TurnBook, TurnPage } from 'turn-ts-svelte';
	import 'turn-ts/turn-ts.css'; // required — the fold has no styles without it
</script>

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

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.

Bound page

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

	let page = $state(1);
</script>

<TurnBook bind:page width={840} height={560}>
	{#each chapters as chapter (chapter.id)}
		<TurnPage>{@html chapter.body}</TurnPage>
	{/each}
</TurnBook>

<p>Page {page}</p>

With bind:page the value is two-way: set it and the book turns there, drag the book and the value follows once the turn lands. Without the binding, page 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 page = 4 and page = 5 hold the same two leaves open and neither turns to the other. The book only writes back when the value it holds has gone off screen, so a bound 5 is not silently rewritten to 4 for a spread that never moved.

The imperative api

<script lang="ts">
	import { TurnBook, TurnPage, type TurnBookApi } from 'turn-ts-svelte';

	let api = $state<TurnBookApi | null>(null);
</script>

<TurnBook bind:api width={840} height={560}>…</TurnBook>

<button type="button" onclick={() => api?.previous()}>Previous</button>
<button type="button" onclick={() => api?.next()}>Next</button>

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

TurnBook also exports next(), previous(), goTo(page) and getApi() for bind:this, which is the shape this package had before it moved onto turn-ts:

<script lang="ts">
	let book: ReturnType<typeof TurnBook>;
</script>

<TurnBook bind:this={book}>…</TurnBook>
<button type="button" onclick={() => book.next()}>Next</button>

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 | Bindable. 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 api still work. | | api | TurnBookApi \| null | Bindable. null before mount and after destroy. |

Callback props: onready, onstart, onturning, onturn, onturned, onfirst, onlast, plus onpagechange(page, view) alongside onturned and oniniterror(error). All but onstart get a TurnBookPageEvent of { type, api, page, view }; onstart additionally gets the corner that was grabbed and a preventDefault() that refuses the fold. Every other attribute you pass (id, role, aria-*, tabindex, onkeydown, class, style, data-*) lands on the container untouched.

If createTurnBook throws and you have not passed oniniterror, the error is rethrown from onMount. A book that silently did not build is worse.

TurnPage props

Renders the host-owned shape the engine documents:

<div class="turn-page-wrapper">      <!-- Svelte'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 children, clipped to the page box. -->

The wrapper is the component's root, so it is the only node Svelte 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 bind:element gives you the .turn-page node.

Pages are read from the DOM in order, so key an {#each} 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. 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
	bind:api
	width={840}
	height={560}
	role="region"
	aria-label="Field notes"
	tabindex={0}
	onkeydown={(event) => {
		if (event.key === 'ArrowRight') api?.next();
		if (event.key === 'ArrowLeft') 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, onMount does not run on the server and neither do effects. The container renders sized from CSS custom properties and the fold arrives on mount.
  • The package ships Svelte source. svelte-package emits dist/*.svelte plus the generated .d.ts, so your bundler compiles the components against your own Svelte version. There is no CJS build, because there is no Svelte 5 runtime worth putting in one.
  • 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    # svelte-check + tests + svelte-package + publint

Tests run on Vitest with Happy DOM and a real mount(). 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 bindable api and page, option pushing, dynamic 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.