leafthrough 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dmytro Barylo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,339 @@
1
- # Temporary Holding Version
1
+ # LeafThrough
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A 3D magazine you can flip through. Pages are printed paper: they bend, roll, catch the
4
+ light and cast shadows. Built on three.js, written in TypeScript, with bindings for React,
5
+ Vue, Svelte, and a Web Component for everything else (Angular, Solid, Lit, plain HTML).
6
+
7
+ **[Live demo](https://dmtrbrl.github.io/leafthrough/)**, with one page per binding and a dev panel
8
+ for every option, method and event.
9
+
10
+ - **Two layouts.** `spread` is an open magazine with sheets turning over the spine.
11
+ `single` shows one page at a time, and read pages roll up behind the magazine. `auto`
12
+ picks between them with a media query.
13
+ - **Real paper feel.** Pages bulge off the spine and nest on the stacks. They bend while
14
+ turning and fold towards wherever you hold them. Paper grain, glossy ink and a little
15
+ show-through complete the look.
16
+ - **Drag, click, press and hold, or keys.** Drags follow your hand, and a released page
17
+ falls with the speed you gave it.
18
+ - **Customizable** paper, lighting, motion, interactions and page shape.
19
+ - **Accessible.** Keyboard control, screen reader announcements, page descriptions and
20
+ text for screen readers, reduced motion support, and a plain view without 3D.
21
+ - **Big magazines.** Pages load nearest first and unload when the reader is far away, so
22
+ memory stays flat however long the issue.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install leafthrough three
28
+ ```
29
+
30
+ `three` (r160 or newer) is a peer dependency. React, Vue and Svelte are optional peers;
31
+ install only the one you use.
32
+
33
+ ## Quick start
34
+
35
+ Every binding renders into an element you size with CSS. The magazine fits itself inside
36
+ (see [Sizing](#sizing)).
37
+
38
+ ### Vanilla
39
+
40
+ ```ts
41
+ import { LeafThrough } from 'leafthrough'
42
+
43
+ const magazine = new LeafThrough(document.querySelector('#magazine')!, {
44
+ pages: ['/pages/01.jpg', '/pages/02.jpg', '/pages/03.jpg', '/pages/04.jpg'],
45
+ })
46
+
47
+ magazine.addEventListener('pagechange', (e) => console.log(e.detail.visiblePages))
48
+ document.querySelector('#next')!.addEventListener('click', () => magazine.next())
49
+ ```
50
+
51
+ ### React (18+)
52
+
53
+ ```tsx
54
+ import { useRef } from 'react'
55
+ import { LeafThrough, type LeafThroughHandle } from 'leafthrough/react'
56
+
57
+ export function Issue({ pages }: { pages: string[] }) {
58
+ const magazine = useRef<LeafThroughHandle>(null)
59
+ return (
60
+ <>
61
+ <LeafThrough ref={magazine} pages={pages} style={{ height: '80vh' }} onPageChange={(d) => console.log(d.page)} />
62
+ <button onClick={() => magazine.current?.next()}>Next</button>
63
+ </>
64
+ )
65
+ }
66
+ ```
67
+
68
+ The component is marked `'use client'` and creates nothing on the server, so it works in
69
+ Next.js and other SSR setups. Props are compared by value, so inline objects such as
70
+ `paper={{ grain: false }}` don't cause rebuilds. Callbacks can change freely.
71
+
72
+ ### Vue (3.3+)
73
+
74
+ ```vue
75
+ <script setup lang="ts">
76
+ import { ref } from 'vue'
77
+ import { LeafThrough } from 'leafthrough/vue'
78
+
79
+ const magazine = ref()
80
+ const pages = ['/pages/01.jpg', '/pages/02.jpg', '/pages/03.jpg']
81
+ </script>
82
+
83
+ <template>
84
+ <LeafThrough ref="magazine" :pages="pages" style="height: 80vh" @pagechange="(d) => console.log(d.page)" />
85
+ <button @click="magazine.next()">Next</button>
86
+ </template>
87
+ ```
88
+
89
+ The template ref exposes `next()`, `prev()`, `goTo()` and `instance`. Events are
90
+ `pagechange`, `ready` and `error`.
91
+
92
+ ### Svelte (4 and 5)
93
+
94
+ ```svelte
95
+ <script lang="ts">
96
+ import { leafThrough, type LeafThrough } from 'leafthrough/svelte'
97
+
98
+ const pages = ['/pages/01.jpg', '/pages/02.jpg', '/pages/03.jpg']
99
+ let magazine: LeafThrough
100
+ </script>
101
+
102
+ <div style="height: 80vh" use:leafThrough={{ pages, onInit: (m) => (magazine = m) }}></div>
103
+ <button onclick={() => magazine.next()}>Next</button>
104
+ ```
105
+
106
+ ### Web Component (Angular, Solid, Lit, plain HTML)
107
+
108
+ ```html
109
+ <script type="module">import 'leafthrough/element'</script>
110
+
111
+ <leaf-through style="height: 80vh" layout="auto" start-page="0" label="Spring issue">
112
+ <img src="/pages/01.jpg" alt="Cover: a lighthouse at dusk" />
113
+ <figure>
114
+ <img src="/pages/02.jpg" alt="" />
115
+ <figcaption>Contents. 3: The keeper's year. 7: Charts of the coast.</figcaption>
116
+ </figure>
117
+ <img src="/pages/03.jpg" alt="" />
118
+ </leaf-through>
119
+ ```
120
+
121
+ You can give pages as `<img>` children, as a `pages` attribute (a JSON array or
122
+ comma-separated URLs), or through the `pages` property. An image's `alt` describes its
123
+ page, and a `<figcaption>` beside it in a `<figure>` gives the page's text (see
124
+ [Accessibility](#accessibility)). Simple options are attributes: `start-page`, `layout`,
125
+ `spread-query`, `page-aspect`, `loop`, `bleed`, `label`, `keyboard`, `preload` and `plain`.
126
+ Everything else goes through the `options` property. The element has `next()`, `prev()`,
127
+ `goTo()`, `page`, `pageCount` and `instance`. It fires `pagechange`, `ready`, `pageload`
128
+ (detail `{ page }`) and `pageerror` (detail `{ error, page }`) events, which bubble. The last one isn't
129
+ called `error`, because on the DOM that means a script error and reaches `window`, where
130
+ error trackers listen. To register the element under another tag, call
131
+ `defineLeafThrough('my-tag')`.
132
+
133
+ **Angular**: import `leafthrough/element` once (for example in `main.ts`). Add
134
+ `CUSTOM_ELEMENTS_SCHEMA` to the component, then bind with
135
+ `[pages]="pages" (pagechange)="onPage($event.detail)"`.
136
+
137
+ **Solid**: `<leaf-through prop:pages={pages()} on:pagechange={(e) => setPage(e.detail.page)} />`.
138
+
139
+ ## Sizing
140
+
141
+ The magazine fills the element you give it and keeps its proportions, centred.
142
+
143
+ - **Fixed size.** Give the element a width and height, for example `height: 80vh`.
144
+ - **Auto height.** If the element has no height of its own, the magazine takes its height
145
+ from the width.
146
+ - **Bleed.** By default the magazine fits the element, and turning pages and shadows spill
147
+ past it, as they would off a real table. In the single layout, a turning page swings
148
+ out to the left of the spine. The spill stops at the edges of the document, so it never
149
+ adds a horizontal scrollbar; on a phone the page sweeps off the screen edge. Don't clip
150
+ the element with `overflow: hidden`. To keep everything inside the element, set
151
+ `bleed: false`; the pages then come out smaller.
152
+
153
+ ## Options
154
+
155
+ | Option | Type | Default | |
156
+ | ----------------- | -------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
157
+ | `pages` | `PageSource[]` | **required** | Reading order, front cover first. URL strings, `<img>`, `<canvas>`, `ImageBitmap`, a three.js `Texture`, or `{ src, alt, text }` (see [Pages](#pages)). |
158
+ | `startPage` | `number` | `0` | 0-based page to open at. |
159
+ | `layout` | `'auto' \| 'spread' \| 'single'` | `'auto'` | |
160
+ | `spreadQuery` | `string` | `(min-width: 768px) and (orientation: landscape)` | When `auto` uses the spread layout. |
161
+ | `pageAspect` | `number` | `1.377` | Page height ÷ width. |
162
+ | `loop` | `boolean` | `true` | Single layout: after the last page, close and restart from the cover. |
163
+ | `bleed` | `boolean` | `true` | See [Sizing](#sizing). |
164
+ | `paper` | `PaperOptions` | | See below. |
165
+ | `lighting` | `LightingOptions` | | See below. |
166
+ | `motion` | `MotionOptions` | | See below. |
167
+ | `interaction` | `InteractionOptions` | | See below. |
168
+ | `maxPixelRatio` | `number` | `2` | The canvas is also capped at about 15 megapixels. |
169
+ | `preload` | `number` | `6` | Pages either side of the visible ones to load ahead. Pages twice as far away unload. `Infinity` keeps all. |
170
+ | `plain` | `boolean` | `false` | Flat pages without 3D. Used by itself when the browser can't run WebGL. |
171
+ | `label` | `string` | `'Magazine'` | Accessible name. |
172
+ | `formatPageLabel` | `(detail) => string` | `"Pages 14–15 of 28"` | What screen readers hear when pages change. |
173
+ | `onPageChange` | `(detail) => void` | | Same as the `pagechange` event. |
174
+ | `onReady` | `() => void` | | The first pages are loaded and on screen. |
175
+ | `onPageLoad` | `(page) => void` | | A page's image loaded. Same as the `pageload` event. |
176
+ | `onError` | `(error, page?) => void` | | A page failed to load. It stays blank paper until `retry()`. |
177
+
178
+ **`paper`**
179
+
180
+ | | Default | |
181
+ | ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
182
+ | `grain` | `true` | `false` turns it off. You can also pass a tiling greyscale image or its URL; only its brightest specks show. |
183
+ | `grainScale` | `1.46` | Tiles across a page. |
184
+ | `grainStrength` | `0.17` | How much the grain darkens. |
185
+ | `showThrough` | spread `0.04`, single `0.3` | How much of the other side shows through the paper. |
186
+ | `inkGloss` | `0.33` | How much glossier ink is than bare paper. |
187
+ | `roughness` / `metalness` | `0.5` / `0.17` | |
188
+ | `backColor` | `#fcfcf9` | Unprinted paper: the blank back of single-layout pages, and transparent parts of page images. |
189
+ | `wrinkles` | `1` | Waviness of resting sheets; `0` lays them flat. |
190
+ | `bulge` | `1` | How much pages rise off the spine. |
191
+
192
+ **`lighting`**: `ambient: { sky, ground, intensity }`, `sun: { color, intensity, position }`,
193
+ `shadows` (default `true`), `shadowMapSize` (default `2048`), and `floorShadow`. That last
194
+ one is the opacity of the shadow under the open spread, default `0.15`.
195
+
196
+ **`motion`**
197
+
198
+ | | Default | |
199
+ | ----------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- |
200
+ | `speed` | `1` | Multiplies the speed of every turn. |
201
+ | `dragDistance` | spread `0.8`, single `1.2` | Share of the canvas width that drags a page all the way over. |
202
+ | `commitThreshold` | spread `0.2`, single `0.1` | How far a released drag must have turned to finish turning. |
203
+ | `curl` | `1` | How much a turning page bends. |
204
+ | `maxTilt` | spread `45`, single `35` | Degrees the fold tilts from where you hold it. |
205
+ | `jitter` | `true` | Small random differences, so no two turns look alike. |
206
+ | `hoverLift` | `0.03` | How far a hovered sheet lifts (spread). |
207
+ | `reducedMotion` | `'auto'` | Follows `prefers-reduced-motion`. Reduced motion means quicker turns, no hover lift and no hold-to-riffle. |
208
+
209
+ **`interaction`**: `drag`, `click`, `hold` and `hover` all default to `true`. `keyboard`
210
+ defaults to `true` (works while the magazine has focus); set it to `'global'` to listen on
211
+ the whole window (form fields are ignored).
212
+
213
+ ## Methods, properties, events
214
+
215
+ ```ts
216
+ magazine.next() // false at the end
217
+ magazine.prev() // false at the start
218
+ magazine.goTo(12) // jump
219
+ magazine.goTo(0, { animate: true }) // riffle back to the cover
220
+ magazine.update({ layout: 'single' }) // change some options, keep the rest and the page
221
+ magazine.setOptions({ pages }) // replace all options: anything left out goes back to its default
222
+ magazine.retry() // load failed pages again (also happens when the browser comes back online)
223
+ magazine.dispose() // remove everything, free the GPU
224
+
225
+ magazine.page // first visible page, 0-based
226
+ magazine.visiblePages // e.g. [13, 14]
227
+ magazine.pageCount
228
+ magazine.layout // 'spread' | 'single'
229
+ magazine.plain // true while showing flat pages (by choice, or without WebGL)
230
+ magazine.turning // true while a page is dragged or still finishing its turn
231
+ magazine.element // the focusable root, [data-leafthrough][data-layout][data-state][data-plain]
232
+
233
+ magazine.addEventListener('pagechange', (e) => e.detail) // { page, visiblePages, pageCount, layout }
234
+ magazine.addEventListener('ready', () => {})
235
+ magazine.addEventListener('pageload', (e) => e.detail) // { page }
236
+ magazine.addEventListener('error', (e) => e.detail) // { error, page }
237
+ ```
238
+
239
+ Both compare options by value. Changing callbacks or the label is free. Changing `pages`,
240
+ `layout`, paper, lighting, motion or interaction rebuilds the scene (a few milliseconds)
241
+ and keeps the reader on the same page. `startPage` only applies at creation; use `goTo()`
242
+ after that. The React, Vue and Svelte bindings and the element use `setOptions()`, so
243
+ removing a prop or attribute restores its default. Options are copied on every call, so an
244
+ object or array changed in place and passed again (as Vue's reactive props are) counts as
245
+ a change.
246
+
247
+ When the browser can't run WebGL, the magazine shows plain pages instead (see
248
+ [Accessibility](#accessibility)); `magazine.plain` tells you so.
249
+
250
+ ## Pages
251
+
252
+ ```ts
253
+ pages: [
254
+ { src: '/pages/01.jpg', alt: 'Cover: a lighthouse at dusk' },
255
+ { src: '/pages/02.jpg', alt: 'Contents', text: "Inside this issue.\n\n3: The keeper's year." },
256
+ '/pages/03.jpg',
257
+ ]
258
+ ```
259
+
260
+ - **Sources.** Use images in the page aspect (default 1 : 1.377). 1200–1600 px wide is
261
+ plenty.
262
+ - **Descriptions.** A page can be `{ src, alt, text }`: `alt` describes it like an image's
263
+ alt text, and `text` is what's printed on it (blank lines separate paragraphs). Screen
264
+ readers get both, and changing them doesn't reload anything.
265
+ - **Transparent artwork.** PNG and WebP alpha is composited onto the paper: transparent
266
+ areas are unprinted paper in `paper.backColor`, including when the reverse side shows
267
+ through.
268
+ - **Cross-origin URLs.** URLs on another origin need CORS headers, because the images are
269
+ uploaded to WebGL.
270
+ - **Textures.** A three.js `Texture` you pass in is never disposed, but the magazine sets its
271
+ `colorSpace`, `premultiplyAlpha` and `anisotropy`. Give it a texture of its own if you
272
+ use the same one elsewhere.
273
+ - **Load order.** Pages load nearest the reader first, four at a time, and the order follows
274
+ the reader as they turn or jump. Pages that haven't arrived show blank paper.
275
+ - **Memory.** Pages within `preload` of the visible ones load; pages more than twice that
276
+ far away unload, freeing their GPU memory, and load again when the reader comes back.
277
+ A texture you pass in yourself stays loaded, since it's yours.
278
+ - **Failures.** A page that fails stays blank paper and is reported through `onError`.
279
+ `retry()` loads failed pages again, and so does the browser coming back online.
280
+ - **Spread order.** In `spread`, the front cover sits alone on the right, then pages pair up
281
+ as 1–2, 3–4, and so on. With an odd page count, the last sheet has a blank back.
282
+
283
+ ## Accessibility
284
+
285
+ The magazine root is focusable (`tabindex="0"`) with `role="region"` and
286
+ `aria-roledescription="magazine"`. A polite live region announces the visible pages. Keys:
287
+ `←` / `→`, `PageUp` / `PageDown`, `Home` and `End`. Rendering pauses while the magazine is
288
+ off screen or the tab is hidden.
289
+
290
+ The 3D canvas is hidden from screen readers, so give pages an `alt` and their `text` (see
291
+ [Pages](#pages)). The magazine keeps the visible pages' descriptions and text in the page,
292
+ visually hidden, where screen readers can read them; each page is a `figure` named by
293
+ `formatPageLabel`.
294
+
295
+ **Plain view.** `plain: true` shows the pages as flat images, with the same layouts,
296
+ navigation, keyboard and events, and no 3D. Clicking the right half turns forward, the left
297
+ half back, and a swipe turns its way. Offer it as a setting for readers who prefer it. The
298
+ magazine also switches to it by itself when the browser can't run WebGL.
299
+
300
+ ## Development
301
+
302
+ ```bash
303
+ npm install
304
+ npm run dev # demo playground: a page per binding, each with the dev panel
305
+ npm run typecheck
306
+ npm test # browser tests in headless Chromium (first run: npx playwright install chromium)
307
+ npm run build # dist/: ESM bundles + type declarations
308
+ npm run build:demo # static demo site in dist-demo/ (published to GitHub Pages on every push to main)
309
+ LEAFTHROUGH_DIST=1 npx vite # run the demo against the built dist/ instead of src/
310
+ ```
311
+
312
+ How it works:
313
+
314
+ - `demo/pages.ts` exports the twelve-page LeafThrough magazine: image URLs with alt text and each page's printed copy.
315
+ Its final PNGs and consolidated `PROMPTS.md` live in `demo/assets/magazine/`.
316
+ Pastel photographic collages alternate with editorial copy about pages and motion,
317
+ with a contents page, running headings, and numbered interior footers. Pages 4–5
318
+ and 8–9 use softer charcoal paper and ivory pencil marks; light pages and covers
319
+ retain transparency. Each PNG includes its artwork and text, preserving the original
320
+ varied collage layouts and proportional sans typography. Running headers and footers
321
+ use exact shared pixel coordinates, documented in `PROMPTS.md` and `alignment.json`.
322
+
323
+ - `src/spread` and `src/single` hold each layout's engine (simulation and input) and its
324
+ sheet shader. `src/plain` holds the plain view's engine.
325
+ - `content.ts` keeps the visible pages' content as DOM: hidden for screen readers in 3D,
326
+ the pages themselves in the plain view.
327
+ - `textures.ts` loads, prioritises and unloads page textures.
328
+ - `src/paper` holds the shared paper material and the procedural grain.
329
+ - `LeafThrough.ts` handles the DOM, sizing, loading, layout switching, keyboard and
330
+ events.
331
+
332
+ ## Credits
333
+
334
+ The page deformation and interaction model are reverse-engineered from the magazine on
335
+ [paper.design/mono](https://paper.design/mono).
336
+
337
+ ## License
338
+
339
+ [MIT](LICENSE)
@@ -0,0 +1,119 @@
1
+ import { type LeafThroughOptions, type Layout, type PageChangeDetail } from './options.js';
2
+ export interface LeafThroughEventMap {
3
+ pagechange: CustomEvent<PageChangeDetail>;
4
+ ready: CustomEvent<void>;
5
+ pageload: CustomEvent<{
6
+ page: number;
7
+ }>;
8
+ error: CustomEvent<{
9
+ error: unknown;
10
+ page?: number;
11
+ }>;
12
+ }
13
+ export interface GoToOptions {
14
+ /** Turn the pages one by one instead of jumping. Default `false`. */
15
+ animate?: boolean;
16
+ }
17
+ /**
18
+ * A 3D magazine rendered into `container`. Size the container with CSS; the magazine
19
+ * fits inside it (a container without a height takes its height from its width).
20
+ *
21
+ * ```ts
22
+ * const magazine = new LeafThrough(document.querySelector('#magazine'), {
23
+ * pages: ['/pages/01.jpg', '/pages/02.jpg', '/pages/03.jpg'],
24
+ * })
25
+ * magazine.addEventListener('pagechange', (e) => console.log(e.detail.page))
26
+ * ```
27
+ */
28
+ export declare class LeafThrough extends EventTarget {
29
+ readonly container: HTMLElement;
30
+ /** The element the magazine renders into; focusable, labelled, and styleable via `[data-leafthrough]`. */
31
+ readonly element: HTMLDivElement;
32
+ private options;
33
+ private readonly canvas;
34
+ private readonly hitArea;
35
+ private readonly live;
36
+ private readonly content;
37
+ private readonly renderer;
38
+ private frameRequest;
39
+ private readonly blank;
40
+ private readonly backBlank;
41
+ private pattern;
42
+ private loader;
43
+ private view;
44
+ private spreadMedia;
45
+ private readonly motionMedia;
46
+ private readonly abort;
47
+ private readonly resizeObserver;
48
+ private readonly metrics;
49
+ private hitBox;
50
+ private hitSide;
51
+ private readonly intersection;
52
+ private visible;
53
+ private needsRender;
54
+ private running;
55
+ private riffle;
56
+ private lastPagesKey;
57
+ private ready;
58
+ private disposed;
59
+ constructor(container: HTMLElement, options: LeafThroughOptions);
60
+ /** First visible page, 0-based. */
61
+ get page(): number;
62
+ get pageCount(): number;
63
+ get layout(): Layout;
64
+ get visiblePages(): number[];
65
+ /** True while showing flat pages: the `plain` option is on, or the browser can't run WebGL. */
66
+ get plain(): boolean;
67
+ /** True while a page is being dragged or is completing its turn. */
68
+ get turning(): boolean;
69
+ /** Turns to the next page. Returns false at the end. */
70
+ next(): boolean;
71
+ /** Turns to the previous page. Returns false at the start. */
72
+ prev(): boolean;
73
+ /** Shows `page` (0-based), jumping there or turning the pages one by one. */
74
+ goTo(page: number, { animate }?: GoToOptions): void;
75
+ /** Loads the pages that failed again. Also happens by itself when the browser comes back online. */
76
+ retry(): void;
77
+ /** Changes some options and keeps the rest. Structural changes rebuild the scene and keep the current page. */
78
+ update(options: Partial<LeafThroughOptions>): void;
79
+ /**
80
+ * Replaces every option at once: anything left out goes back to its default. Bindings
81
+ * use this, since props describe the whole state. Structural changes rebuild the scene
82
+ * and keep the current page.
83
+ */
84
+ setOptions(options: LeafThroughOptions): void;
85
+ private applyOptions;
86
+ addEventListener<K extends keyof LeafThroughEventMap>(type: K, listener: (event: LeafThroughEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
87
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
88
+ removeEventListener<K extends keyof LeafThroughEventMap>(type: K, listener: (event: LeafThroughEventMap[K]) => void, options?: boolean | EventListenerOptions): void;
89
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
90
+ dispose(): void;
91
+ private pickLayout;
92
+ private watchSpreadQuery;
93
+ private remount;
94
+ private createPlain;
95
+ private createScene;
96
+ private patternFor;
97
+ private layoutBox;
98
+ private pagesKey;
99
+ private preloadRadius;
100
+ private loadPages;
101
+ private resetReady;
102
+ private pageLoaded;
103
+ private fail;
104
+ private pageToPosition;
105
+ private positionToPage;
106
+ private pagesAt;
107
+ private detail;
108
+ private lastDetailKey;
109
+ private emitPageChange;
110
+ private syncContent;
111
+ private applyLabel;
112
+ private bindKeyboard;
113
+ private stopRiffle;
114
+ private stepRiffle;
115
+ private syncLoop;
116
+ private setLoop;
117
+ private placeHitArea;
118
+ private frame;
119
+ }