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

@page-view/svelte

v0.1.5

Published

Headless, Flutter-PageView-inspired paginated scroll/carousel primitive for Svelte 5 (runes) — gestures, wheel, keyboard, autoplay, RTL and lazy-loading built in.

Readme

@page-view/svelte

A headless, Flutter PageView-inspired paginated scroll/carousel primitive for Svelte 5 (runes). Gestures, wheel, keyboard, autoplay, RTL and lazy-loading are all built in — styling is entirely up to you.

  • Zero runtime dependencies — nothing beyond Svelte itself.
  • Headless — no imposed CSS beyond scroll-snap mechanics; bring your own classes.
  • SSR-safe — no top-level window/document access, no hydration-mismatch-prone IDs.
  • Accessible by default — keyboard navigation works out of the box; indicators render as real <button>s.

Install

npm install @page-view/svelte

Requires svelte@^5 as a peer dependency.

Quick start

<script lang="ts">
	import { PageView, PageItem } from '@page-view/svelte';

	const slides = ['One', 'Two', 'Three'];
</script>

<PageView>
	{#snippet children(ctrl)}
		{#each slides as slide, i (slide)}
			<PageItem controller={ctrl} index={i}>
				<div class="flex h-64 items-center justify-center bg-base-200">{slide}</div>
			</PageItem>
		{/each}
	{/snippet}
</PageView>

That's it — swipe, arrow keys, and (on wide/pointer-first viewports) wheel navigation all work with no further setup.

Autoplay

<AutoPlayView> wraps <PageView> and adds a pause-on-hover timer:

<script lang="ts">
	import { AutoPlayView, PageItem } from '@page-view/svelte';
</script>

<AutoPlayView interval={4000} autoplayEnabled={true}>
	{#snippet children(ctrl, autoCtrl)}
		<!-- same as PageView; autoCtrl exposes start()/stop()/setHovered() if needed -->
	{/snippet}
</AutoPlayView>

Hover-pause is automatic — <AutoPlayView> pauses the timer while the pointer is over it and resumes on leave, no prop needed.

Viewport & RTL — supplied by your app, live-reactive

'auto' input modes (see below) resolve against a breakpoint (matching a Tailwind-style xs | sm | md | lg | xl | 2xl | 3xl scale) and RTL state. This package does not read window/document to guess either of these — that's real environment detection, which belongs in your app, not a headless primitive. Omit them and you get a neutral static fallback ('lg', false); supply them for correct 'auto'-mode behavior.

Every PageController option — not just these two — is read live, not snapshotted at construction: the constructor stores the options object itself, so a property defined with JS getter syntax stays reactive with no $effect needed:

<PageView
	viewport={myBreakpoint}
	rtl={myPrefs.isRtl}
/>

<PageView>/<AutoPlayView> already forward every prop this way internally — that's how they stay in sync with your reactive $props() without a remount. If you're constructing a PageController directly outside a component, use a getter to keep it live:

const ctrl = new PageController({
	get rtl() {
		return myPrefs.isRtl;
	}
});

A plain value ({ rtl: true }) works identically for static/one-off use — the controller can't tell a getter from a plain property.

For RTL layout (button placement, logical CSS properties), wrap in dir="rtl" — independent of the rtl option above, which only drives scroll math and gesture/wheel direction:

<div dir="rtl">
	<PageView rtl={true}>...</PageView>
</div>

Input control modes

gesturesState, wheelState, and keyboardState each accept 'enabled' | 'disabled' | 'auto':

| Input | 'auto' resolution | Default | | --------- | ----------------------------------------------------------------------- | ---------- | | gestures | touch-first breakpoints (xs/sm/md) → on, others → off | 'auto' | | wheel | pointer-first breakpoints (lg/xl/2xl/3xl) → on, others → off | 'disabled' on horizontal, 'auto' on vertical | | keyboard | always on | 'auto' |

Listeners are only ever attached when the final resolved value is true — 'disabled' costs nothing at runtime (no listener registered at all).

Unbounded/bidirectional content (opt-in)

For content that grows at the edges over time (an endless timeline, infinite history, etc.) — not the same thing as loop, which wraps a fixed, already-known page count. Two small, independent, opt-in primitives, both undefined/no-op by default:

new PageController({
	onReachStart: () => {
		/* prepend more pages, then: */
		controller.notifyPagesPrepended(count);
	},
	onReachEnd: () => {
		/* just append more pages — nothing else needed */
	},
	edgeThreshold: 1 // how many pages from an edge counts as "near" it
});
  • onReachStart / onReachEnd fire once on entering the edge zone (not on every settle while already inside it), so you don't need your own re-fire guard.
  • notifyPagesPrepended(count) — call after inserting count pages before the existing ones. Prepending shifts every existing page's position; this corrects currentPage and the scroll offset instantly (no measurement needed — every page is the same known size) so the view doesn't visibly jump. Appending needs no equivalent call — nothing before the current page moved.

This package has no opinion on what a page contains or how more of them get created — see docs/infinite-scroll-research.md in the repo for the design rationale (compared against react-virtuoso, TanStack Virtual, Flutter's sliver anchoring, and IntersectionObserver-based infinite scroll before settling on this shape).

Public API

import {
	// Controllers (framework-agnostic state, usable outside a component too)
	PageController,
	AutoplayController,

	// Components
	PageView,
	AutoPlayView,
	PageTrack,
	PageItem,
	PageIndicator,
	PageNavButton,

	// Enums / unions
	ScrollDirection, // 'horizontal' | 'vertical'
	ScrollPhysics, // 'bouncing' | 'clamped' | 'never'
	IndicatorPosition,
	InputControlMode, // 'enabled' | 'disabled' | 'auto'
	Breakpoint, // 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'

	// Lower-level building blocks (reuse if you're composing something custom)
	getPageSize,
	getPageOffset,
	computeCurrentPage,
	scrollToPage,
	canGoToNextPage,
	canGoToPrevPage,
	physicsToOverscrollBehavior,
	getSnapType,
	clamp,

	// Constants
	DEFAULT_BREAKPOINT,
	DEFAULT_BREAKPOINT_MIN_WIDTH_PX,
	DEFAULT_EDGE_THRESHOLD,
	TOUCH_FIRST_BREAKPOINTS

	// ...plus every corresponding *Options / *Props type.
} from '@page-view/svelte';

PageController is the framework-agnostic navigation core (works even outside a .svelte file); AutoplayController composes a PageController instance and adds a pause-on-hover timer. PageNavButton's default chevron icon is a plain inline SVG — pass the icon snippet prop to override it with anything (an icon library, an emoji, a custom asset).

Accessibility, dark mode, RTL

  • Keyboard navigation (arrow keys) is on by default and works without any configuration.
  • <PageIndicator> renders real <button> elements (role="tab", aria-selected, aria-label).
  • No imposed color/theme — style via your own classes (works fine with DaisyUI/Tailwind dark-mode variants, since none of this package's CSS hardcodes a palette).
  • RTL: see Viewport & RTL above.