@zinejs/core 0.8.2 → 0.9.1
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 +73 -0
- package/README.md +27 -49
- package/dist/{bundled-Cp43MIt0.js → bundled-ESapkNsS.js} +34 -34
- package/dist/bundled-ESapkNsS.js.map +1 -0
- package/dist/controls/contextMenu.d.ts +16 -0
- package/dist/controls/controls.d.ts +10 -3
- package/dist/controls/icons.d.ts +6 -2
- package/dist/controls/menu.d.ts +68 -0
- package/dist/controls/search.d.ts +15 -2
- package/dist/controls/styles.d.ts +1 -1
- package/dist/controls/toolbar.d.ts +1 -1
- package/dist/controls/types.d.ts +6 -0
- package/dist/{controls-DgEXR2zi.js → controls-DzVdwPJB.js} +349 -196
- package/dist/controls-DzVdwPJB.js.map +1 -0
- package/dist/curls.js +1 -1
- package/dist/flipSound-BFXSkWBo.js +78 -0
- package/dist/flipSound-BFXSkWBo.js.map +1 -0
- package/dist/geometry/curls/cone.d.ts +4 -3
- package/dist/index.js +671 -440
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +26 -7
- package/dist/index.umd.js.map +1 -1
- package/dist/loading/loading.d.ts +1 -1
- package/dist/loading-DF88K9yJ.js +45 -0
- package/dist/loading-DF88K9yJ.js.map +1 -0
- package/dist/renderer/gl/texture.d.ts +13 -2
- package/dist/renderer/types.d.ts +6 -0
- package/dist/renderer/webglRenderer.d.ts +1 -0
- package/dist/{share-WXdscwp3.js → share-Cq3RsjPr.js} +2 -2
- package/dist/{share-WXdscwp3.js.map → share-Cq3RsjPr.js.map} +1 -1
- package/dist/sound/clip.d.ts +9 -0
- package/dist/sound/flipSound.d.ts +30 -0
- package/dist/source/types.d.ts +5 -0
- package/dist/styles-C1Qp5pby.js +42 -0
- package/dist/styles-C1Qp5pby.js.map +1 -0
- package/dist/{webglRenderer-nGRruZk2.js → webglRenderer-CmQ_mUrX.js} +113 -101
- package/dist/webglRenderer-CmQ_mUrX.js.map +1 -0
- package/dist/zine.d.ts +75 -1
- package/options.schema.json +66 -0
- package/package.json +13 -11
- package/dist/bundled-Cp43MIt0.js.map +0 -1
- package/dist/controls-DgEXR2zi.js.map +0 -1
- package/dist/loading-BBK6YbSW.js +0 -45
- package/dist/loading-BBK6YbSW.js.map +0 -1
- package/dist/styles-B8fFoYm_.js +0 -40
- package/dist/styles-B8fFoYm_.js.map +0 -1
- package/dist/webglRenderer-nGRruZk2.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
12
|
+
|
|
13
|
+
## Distribution License
|
|
14
|
+
|
|
15
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
16
|
+
|
|
17
|
+
## Notices
|
|
18
|
+
|
|
19
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
20
|
+
|
|
21
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
22
|
+
|
|
23
|
+
## Changes and New Works License
|
|
24
|
+
|
|
25
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
26
|
+
|
|
27
|
+
## Patent License
|
|
28
|
+
|
|
29
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
30
|
+
|
|
31
|
+
## Noncommercial Purposes
|
|
32
|
+
|
|
33
|
+
Any noncommercial purpose is a permitted purpose.
|
|
34
|
+
|
|
35
|
+
## Personal Uses
|
|
36
|
+
|
|
37
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
38
|
+
|
|
39
|
+
## Noncommercial Organizations
|
|
40
|
+
|
|
41
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
42
|
+
|
|
43
|
+
## Fair Use
|
|
44
|
+
|
|
45
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
46
|
+
|
|
47
|
+
## No Other Rights
|
|
48
|
+
|
|
49
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
50
|
+
|
|
51
|
+
## Patent Defense
|
|
52
|
+
|
|
53
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
54
|
+
|
|
55
|
+
## Violations
|
|
56
|
+
|
|
57
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
58
|
+
|
|
59
|
+
## No Liability
|
|
60
|
+
|
|
61
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
62
|
+
|
|
63
|
+
## Definitions
|
|
64
|
+
|
|
65
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
66
|
+
|
|
67
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
68
|
+
|
|
69
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
70
|
+
|
|
71
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
72
|
+
|
|
73
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @zinejs/core
|
|
2
2
|
|
|
3
|
-
Framework-agnostic flipbook engine
|
|
3
|
+
Framework-agnostic flipbook engine for the web. Turn a list of images (or a PDF, via [`@zinejs/pdf`](https://www.npmjs.com/package/@zinejs/pdf)) into a page-flip book. Zero dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Where WebGL2 is available it uses the GPU renderer (page-curl, lighting). Elsewhere it falls back to CSS on its own.
|
|
6
|
+
|
|
7
|
+
> **Docs, live demos, and guides:** [zinejs.com/docs](https://zinejs.com/docs/) · [examples](https://zinejs.com/examples)
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -10,79 +12,55 @@ Framework-agnostic flipbook engine: turn images or PDFs into a page-flipping boo
|
|
|
10
12
|
npm install @zinejs/core
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Also ships a **UMD** build at `dist/index.umd.js` (global `ZineJS`) for CDN / `<script>` hosts. The ESM build code-splits the CSS and WebGL2 renderers; the UMD build inlines
|
|
15
|
+
Also ships a **UMD** build at `dist/index.umd.js` (global `ZineJS`) for [CDN / `<script>` hosts](https://zinejs.com/examples/html-cdn). The ESM build code-splits the CSS and WebGL2 renderers (and the toolbar); the UMD build inlines the renderers so a single script is enough.
|
|
14
16
|
|
|
15
17
|
## Image book
|
|
16
18
|
|
|
17
19
|
```js
|
|
18
|
-
import { Zine, ImageSource } from '@zinejs/core'
|
|
20
|
+
import { Zine, ImageSource } from '@zinejs/core'
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
new Zine(document.getElementById('book'), {
|
|
21
23
|
source: new ImageSource([
|
|
22
24
|
'page-01.png',
|
|
23
25
|
'page-02.png',
|
|
24
26
|
'page-03.png',
|
|
25
27
|
'page-04.png',
|
|
26
28
|
]),
|
|
27
|
-
})
|
|
29
|
+
})
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
Drag a corner or click near an edge to turn. Double-click, pinch, or Ctrl/Cmd+wheel to zoom.
|
|
31
33
|
|
|
32
34
|
## PDF book
|
|
33
35
|
|
|
34
|
-
Install
|
|
36
|
+
Install [`@zinejs/pdf`](https://www.npmjs.com/package/@zinejs/pdf) as well:
|
|
35
37
|
|
|
36
38
|
```js
|
|
37
|
-
import { Zine } from '@zinejs/core'
|
|
38
|
-
import { PdfSource } from '@zinejs/pdf'
|
|
39
|
+
import { Zine } from '@zinejs/core'
|
|
40
|
+
import { PdfSource } from '@zinejs/pdf'
|
|
39
41
|
|
|
40
42
|
new Zine(document.getElementById('book'), {
|
|
41
43
|
source: new PdfSource('document.pdf'),
|
|
42
|
-
})
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Options
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
new Zine(container, {
|
|
49
|
-
source, // ImageSource | PdfSource (required)
|
|
50
|
-
direction: 'ltr', // 'ltr' | 'rtl'
|
|
51
|
-
spreadMode: 'cover', // 'double' | 'single' | 'cover' (lone first page) | 'book' (lone first & last)
|
|
52
|
-
curl: 'cone', // WebGL2 only: 'cone' | 'roll' | 'leaf' | 'flick' | 'silk' | 'simple'
|
|
53
|
-
frontCover: 'front.jpg', // optional image prepended as a lone front cover
|
|
54
|
-
backCover: 'back.jpg', // optional image appended as a lone back cover
|
|
55
|
-
pages: { 4: 'ad.jpg' }, // optional: replace source pages with images (0-based; -1 = last)
|
|
56
|
-
startPage: 0,
|
|
57
|
-
flipDuration: 800, // ms (spread timing; a lone page stretches it and eases out)
|
|
58
|
-
singlePageThreshold: 640, // px: below this, show one page at a time
|
|
59
|
-
clickToFlip: 'edge', // 'edge' | 'half' | 'off'
|
|
60
|
-
zoom: { enabled: true, max: 4, wheel: true, doubleClick: [1, 2, 4] },
|
|
61
|
-
renderer: 'auto', // 'auto' | 'css' | 'webgl2' | a custom Renderer
|
|
62
|
-
controls: true, // built-in toolbar; false for none, or { position, items, … }
|
|
63
|
-
});
|
|
44
|
+
})
|
|
64
45
|
```
|
|
65
46
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
A toolbar (paging, page number, zoom, search, share, a `⋮` menu with download, and fullscreen)
|
|
69
|
-
is rendered below the book by default. Turn it off with `controls: false`, move it with
|
|
70
|
-
`controls: { position: 'top' }`, float it over the book with `controls: { docked: false }`, or
|
|
71
|
-
choose the buttons with `controls: { items: [...] }`. Register your own with `defineControl`.
|
|
47
|
+
See [PdfSource](https://zinejs.com/docs) for worker setup, `legacy`, and CDN.
|
|
72
48
|
|
|
73
|
-
|
|
74
|
-
downloads them. See the [full docs](https://github.com/awecode/zinejs#controls).
|
|
49
|
+
## Options and API
|
|
75
50
|
|
|
76
|
-
|
|
77
|
-
## Methods & events
|
|
51
|
+
Only `source` is required. Common options: `spreadMode`, `curl` (`'cone'` or `'simple'` by name; other curls from `@zinejs/core/curls`), `zoom`, `controls`, `renderer` (`'auto'` prefers GPU). A JSON Schema is at `@zinejs/core/options.schema.json`.
|
|
78
52
|
|
|
79
53
|
```js
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
54
|
+
const zine = new Zine(el, {
|
|
55
|
+
source,
|
|
56
|
+
spreadMode: 'cover',
|
|
57
|
+
zoom: { max: 4 },
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await zine.ready
|
|
61
|
+
zine.on('pageChanged', ({ page }) => console.log(page))
|
|
62
|
+
zine.flipNext()
|
|
63
|
+
zine.flipTo(12)
|
|
86
64
|
```
|
|
87
65
|
|
|
88
|
-
|
|
66
|
+
The toolbar loads as a separate chunk, so `controls: false` never downloads it. Full options, methods, events, and curl models: [zinejs.com/docs](https://zinejs.com/docs/) and the [GitHub README](https://github.com/awecode/zinejs#readme).
|
|
@@ -25,45 +25,45 @@ function n(e) {
|
|
|
25
25
|
}
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/geometry/curls/cone.ts
|
|
28
|
-
var r = 30 * Math.PI / 180, i =
|
|
29
|
-
function
|
|
30
|
-
let
|
|
31
|
-
if (
|
|
32
|
-
for (let n = 0; n <
|
|
33
|
-
let r = n / e.rows *
|
|
34
|
-
for (let i = 0; i <
|
|
35
|
-
let a = i / e.cols * t, o = (n *
|
|
36
|
-
|
|
28
|
+
var r = 30 * Math.PI / 180, i = 1.35, a = .7, o = .42, s = .84, c = .88;
|
|
29
|
+
function l(e, t, l, u, d) {
|
|
30
|
+
let f = e.cols + 1, p = e.rows + 1, m = e.positions, h = u >= s ? 1 : u / s, g = Math.sin(Math.PI * h), _ = Math.PI / 2 - g * (Math.PI / 2 - r), v = Math.sin(_), y = Math.cos(_), b = 1 / Math.max(v, 1e-6), x = l * (i + (a - i) * g), S = (d.y - .5) * 2, C = S >= 0, w = Math.min(1, Math.abs(S) / o), T = 1 - w, E = C ? l + x : -x, D = Math.hypot(t, .5 * l - E), O = Math.max(D * v, t / Math.PI), k = Math.min(1, u / c), A = -Math.PI * k, j = Math.cos(A), M = Math.sin(A);
|
|
31
|
+
if (g < 1e-4) {
|
|
32
|
+
for (let n = 0; n < p; n++) {
|
|
33
|
+
let r = n / e.rows * l;
|
|
34
|
+
for (let i = 0; i < f; i++) {
|
|
35
|
+
let a = i / e.cols * t, o = (n * f + i) * 3;
|
|
36
|
+
m[o] = a * j, m[o + 1] = r, m[o + 2] = -a * M;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
n(e);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
for (let n = 0; n <
|
|
43
|
-
let r = n / e.rows *
|
|
44
|
-
for (let i = 0; i <
|
|
42
|
+
for (let n = 0; n < p; n++) {
|
|
43
|
+
let r = n / e.rows * l;
|
|
44
|
+
for (let i = 0; i < f; i++) {
|
|
45
45
|
let a = i / e.cols * t, o = a, s = r, c = 0;
|
|
46
|
-
if (
|
|
47
|
-
let e =
|
|
46
|
+
if (w > 1e-5) {
|
|
47
|
+
let e = C ? l - r : r, t = -x, n = Math.hypot(a, e - t);
|
|
48
48
|
if (n > 1e-8) {
|
|
49
|
-
let e = n *
|
|
50
|
-
o = e * Math.sin(r), s =
|
|
49
|
+
let e = n * v, r = Math.asin(Math.min(1, Math.max(0, a / n))) * b, i = 1 - Math.cos(r), u = n + t - e * i * v;
|
|
50
|
+
o = e * Math.sin(r), s = C ? l - u : u, c = e * i * y;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
let
|
|
54
|
-
if (
|
|
55
|
-
let e = a /
|
|
56
|
-
|
|
53
|
+
let u = o, d = s, p = c;
|
|
54
|
+
if (T > 1e-5) {
|
|
55
|
+
let e = a / O, t = O * Math.sin(e), n = O * (1 - Math.cos(e));
|
|
56
|
+
u = w * o + T * t, d = w * s + T * r, p = w * c + T * n;
|
|
57
57
|
}
|
|
58
|
-
let
|
|
59
|
-
|
|
58
|
+
let h = (n * f + i) * 3;
|
|
59
|
+
m[h] = u * j + p * M, m[h + 1] = d, m[h + 2] = -u * M + p * j;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
n(e);
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
65
65
|
//#region src/geometry/curls/simple.ts
|
|
66
|
-
function
|
|
66
|
+
function u(e, t, r, i) {
|
|
67
67
|
let a = e.cols + 1, o = e.rows + 1, s = e.positions, c = i * Math.PI, l = Math.cos(c), u = Math.sin(c);
|
|
68
68
|
for (let n = 0; n < a; n++) {
|
|
69
69
|
let i = n / e.cols * t, c = i * l, d = i * u;
|
|
@@ -76,25 +76,25 @@ function f(e, t, r, i) {
|
|
|
76
76
|
}
|
|
77
77
|
//#endregion
|
|
78
78
|
//#region src/geometry/curls/bundled.ts
|
|
79
|
-
var
|
|
80
|
-
deform:
|
|
79
|
+
var d = {
|
|
80
|
+
deform: l,
|
|
81
81
|
anchored: !0
|
|
82
|
-
},
|
|
83
|
-
deform:
|
|
82
|
+
}, f = {
|
|
83
|
+
deform: u,
|
|
84
84
|
anchored: !1,
|
|
85
85
|
flat: !0,
|
|
86
86
|
gloss: !1
|
|
87
|
-
},
|
|
88
|
-
cone:
|
|
89
|
-
simple:
|
|
87
|
+
}, p = {
|
|
88
|
+
cone: d,
|
|
89
|
+
simple: f
|
|
90
90
|
};
|
|
91
|
-
function
|
|
91
|
+
function m(t) {
|
|
92
92
|
if (typeof t != "string") return t;
|
|
93
|
-
let n =
|
|
93
|
+
let n = p[t];
|
|
94
94
|
if (n) return n;
|
|
95
95
|
throw e.includes(t) ? Error(`Zine: the '${t}' curl is not bundled. Import it and pass the model: import { ${t} } from '@zinejs/core/curls' → curl: ${t}`) : Error(`Zine: unknown curl ${JSON.stringify(t)}.`);
|
|
96
96
|
}
|
|
97
97
|
//#endregion
|
|
98
|
-
export { n as a,
|
|
98
|
+
export { n as a, f as i, d as n, t as o, m as r, p as t };
|
|
99
99
|
|
|
100
|
-
//# sourceMappingURL=bundled-
|
|
100
|
+
//# sourceMappingURL=bundled-ESapkNsS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-ESapkNsS.js","names":[],"sources":["../src/geometry/curls/mesh.ts","../src/geometry/curls/cone.ts","../src/geometry/curls/simple.ts","../src/geometry/curls/bundled.ts"],"sourcesContent":["/**\n * Shared grid mesh + normal utilities for the page-curl models.\n *\n * A curl model fills `positions`/`normals` in place each frame; UVs are static grid\n * coordinates (no texture swimming). All models bend the same (cols+1)×(rows+1) grid.\n */\n\nexport interface PageMesh {\n readonly cols: number;\n readonly rows: number;\n /** ((cols+1)*(rows+1))*2 — static grid UVs (u across width, v down height). */\n readonly uvs: Float32Array;\n /** *3 — deformed positions, updated by the curl model. */\n readonly positions: Float32Array;\n /** *3 — per-vertex normals, updated by the curl model. */\n readonly normals: Float32Array;\n}\n\n/** Allocate a (cols+1)×(rows+1) grid mesh with static UVs (positions/normals filled by a curl). */\nexport function createPageMesh(cols: number, rows: number): PageMesh {\n const nx = cols + 1;\n const ny = rows + 1;\n const count = nx * ny;\n const uvs = new Float32Array(count * 2);\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const k = j * nx + i;\n uvs[k * 2] = i / cols;\n uvs[k * 2 + 1] = j / rows;\n }\n }\n return {\n cols,\n rows,\n uvs,\n positions: new Float32Array(count * 3),\n normals: new Float32Array(count * 3),\n };\n}\n\n/** Per-vertex normals from central differences of the deformed grid (front = +z when flat). */\nexport function computeNormals(mesh: PageMesh): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n const nrm = mesh.normals;\n\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const iPrev = i > 0 ? i - 1 : i;\n const iNext = i < nx - 1 ? i + 1 : i;\n const jPrev = j > 0 ? j - 1 : j;\n const jNext = j < ny - 1 ? j + 1 : j;\n\n const a = (j * nx + iNext) * 3;\n const b = (j * nx + iPrev) * 3;\n const c = (jNext * nx + i) * 3;\n const e = (jPrev * nx + i) * 3;\n\n const tuX = pos[a]! - pos[b]!;\n const tuY = pos[a + 1]! - pos[b + 1]!;\n const tuZ = pos[a + 2]! - pos[b + 2]!;\n const tvX = pos[c]! - pos[e]!;\n const tvY = pos[c + 1]! - pos[e + 1]!;\n const tvZ = pos[c + 2]! - pos[e + 2]!;\n\n let nx0 = tuY * tvZ - tuZ * tvY;\n let ny0 = tuZ * tvX - tuX * tvZ;\n let nz0 = tuX * tvY - tuY * tvX;\n const l = Math.hypot(nx0, ny0, nz0) || 1;\n nx0 /= l;\n ny0 /= l;\n nz0 /= l;\n\n const k = (j * nx + i) * 3;\n nrm[k] = nx0;\n nrm[k + 1] = ny0;\n nrm[k + 2] = nz0;\n }\n }\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Cone angle at peak curl. π/2 is flat; smaller values wind the sheet tighter.\n * ~30° gives a soft, paper-like ridge without self-intersection on typical page\n * aspect ratios (Hong / Nuon / Project Austin all land in the 18–35° band).\n */\nconst THETA_MIN = (30 * Math.PI) / 180;\n\n/**\n * How far past the page the cone apex sits, as a fraction of height, at rest.\n * Closer mid-turn sharpens the diagonal; farther reads more cylindrical.\n * Kept moderately distant so the sheet stays page-like without cloth-y sliding.\n */\nconst APEX_REST = 1.35;\nconst APEX_PEAK = 0.7;\n\n/**\n * Above this |anchor.y − 0.5| the deform is a pure cone; at the mid-edge it\n * blends fully to a cylinder (Project Austin’s conicContribution).\n */\nconst CORNER_PURE = 0.42;\n\n/**\n * Curl envelope ends before t=1 so the last stretch is a flat spine settle.\n * Holding sin(πt) curl into the 0.9s left a tall residual ridge that deflated\n * while the free edge was already nearly home — that read as a rebound bounce.\n */\nconst CURL_END = 0.84;\n\n/**\n * Spine rotation reaches ±π before t=1 and holds. Flip easing decelerates hard into\n * the end (a near-still tail after PAGE_LEAD); finishing the flop early keeps that\n * tail from slowly dropping the leaf into place (the settle/rebound feel).\n * Same schedule for lone pages: the dissolve then covers a still, landed sheet\n * rather than a late whip off the hinge.\n */\nconst RHO_END = 0.88;\n\n/**\n * Natural conical page curl — the Xerox PARC / iBooks model.\n *\n * The leaf wraps an imaginary right circular cone whose apex rides the spine and\n * whose opening angle θ breathes over the turn (Hong, Card & Chen, 2004). A corner\n * grab yields a diagonal cone; a mid-edge grab blends toward a cylinder so the\n * free edge lifts as one. The curled sheet then rotates about the spine and settles\n * flat on the far side. Arc length from the apex is preserved, so the paper never\n * stretches — the motion reads as stiff, premium stock rather than cloth.\n *\n * Lone pages (`anchor.fill`) use the same cone and flop as a spread half; the\n * renderer hinges at the container edge and dissolves the leaf as it finishes.\n * Softening or delaying the fill path made single-page turns read as a peel-fade\n * instead of a page turn.\n */\nexport function deformCone(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n // Curl envelope: 0 at the ends (flat), 1 near mid-turn. Span ends at CURL_END\n // so the ridge is gone before the spine holds the landed pose through the ease tail.\n const curlT = t >= CURL_END ? 1 : t / CURL_END;\n const curl = Math.sin(Math.PI * curlT);\n\n // θ: π/2 (identity) → THETA_MIN (pronounced curl) → π/2.\n const theta = Math.PI / 2 - curl * (Math.PI / 2 - THETA_MIN);\n const sinTheta = Math.sin(theta);\n const cosTheta = Math.cos(theta);\n const invSinTheta = 1 / Math.max(sinTheta, 1e-6);\n\n // Apex distance: pulled in as the curl develops for a tighter diagonal.\n const apexDist = H * (APEX_REST + (APEX_PEAK - APEX_REST) * curl);\n\n // Corner bias: −1 = top edge, +1 = bottom. Near 0 → cylinder blend.\n const bias = (anchor.y - 0.5) * 2;\n const fromBottom = bias >= 0;\n const absBias = Math.abs(bias);\n let conic = Math.min(1, absBias / CORNER_PURE);\n const cylWeight = 1 - conic;\n\n // Cylinder radius matched to the cone’s free-edge cross-section (Austin), so\n // the blend is continuous as the grab slides from corner to mid-edge.\n // Apex sits past the *leading* edge (below for a bottom grab, above for top).\n const AyRef = fromBottom ? H + apexDist : -apexDist;\n const cylR = Math.hypot(W, 0.5 * H - AyRef);\n const cylRadius = Math.max(cylR * sinTheta, W / Math.PI);\n\n // Spine rotation: concurrent with the cone so the page flops while it curls.\n // Same x–z convention as `roll` (ρ: 0 → −π). Finish by RHO_END and hold so the\n // ease-out / dissolve tail stays still (spread land or lone fade alike).\n // `anchor.fill` is a renderer concern; geometry matches a spread half.\n const rhoT = Math.min(1, t / RHO_END);\n const rho = -Math.PI * rhoT;\n const cosR = Math.cos(rho);\n const sinR = Math.sin(rho);\n\n // Near the rests the cone is the identity plane. A finite-radius cylinder blend\n // still carries a tiny residual arc — skip it and rotate the flat sheet.\n if (curl < 1e-4) {\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n const k = (j * nx + i) * 3;\n pos[k] = x0 * cosR;\n pos[k + 1] = y;\n pos[k + 2] = -x0 * sinR;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n for (let j = 0; j < ny; j++) {\n const y0 = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n\n let cx = x0;\n let cy = y0;\n let cz = 0;\n\n if (conic > 1e-5) {\n // Canonical cone: apex above the page (A < 0) makes the *near* edge lead.\n // In our y-down leaf, that means the top. For a bottom grab we flip into\n // that space, deform, then flip back — one code path for both corners.\n const yIn = fromBottom ? H - y0 : y0;\n const A = -apexDist;\n const R = Math.hypot(x0, yIn - A);\n if (R > 1e-8) {\n const r = R * sinTheta;\n const beta = Math.asin(Math.min(1, Math.max(0, x0 / R))) * invSinTheta;\n const oneCos = 1 - Math.cos(beta);\n const yCone = R + A - r * oneCos * sinTheta;\n cx = r * Math.sin(beta);\n cy = fromBottom ? H - yCone : yCone;\n cz = r * oneCos * cosTheta;\n }\n }\n\n let bx = cx;\n let by = cy;\n let bz = cz;\n\n if (cylWeight > 1e-5) {\n const beta = x0 / cylRadius;\n const sx = cylRadius * Math.sin(beta);\n const sz = cylRadius * (1 - Math.cos(beta));\n bx = conic * cx + cylWeight * sx;\n by = conic * cy + cylWeight * y0;\n bz = conic * cz + cylWeight * sz;\n }\n\n const k = (j * nx + i) * 3;\n pos[k] = bx * cosR + bz * sinR;\n pos[k + 1] = by;\n pos[k + 2] = -bx * sinR + bz * cosR;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\n\n/**\n * Flat page turn — the plainest flip, matching the CSS renderer. The leaf stays a rigid\n * flat sheet and rotates around the spine (angle 0→π), going edge-on at the midpoint. No\n * cylinder bend; GPU-rendered here with the shared perspective so it reads like the CSS fold.\n */\nexport function deformSimple(mesh: PageMesh, W: number, H: number, t: number): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n const angle = t * Math.PI;\n const cosA = Math.cos(angle);\n const sinA = Math.sin(angle);\n\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W; // distance from the spine\n const X = x0 * cosA;\n const Z = x0 * sinA;\n for (let j = 0; j < ny; j++) {\n const k = (j * nx + i) * 3;\n pos[k] = X;\n pos[k + 1] = (j / mesh.rows) * H;\n pos[k + 2] = Z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { deformCone } from './cone';\nimport { deformSimple } from './simple';\nimport { IMPORTABLE_CURLS, type CurlModel, type CurlSpec, type CurlType } from './types';\n\n/**\n * The curl models carried in the bundle, and the only ones nameable by string.\n *\n * Kept apart from `./index` so importing them does not drag in the other four: the barrel names\n * every model, which is right for a caller picking one but wrong for the renderer, which must\n * only ever pull these two.\n */\nexport const cone: CurlModel = { deform: deformCone, anchored: true };\nexport const simple: CurlModel = {\n deform: deformSimple,\n anchored: false,\n flat: true,\n gloss: false,\n};\n\nexport const BUNDLED_CURLS: Record<CurlType, CurlModel> = { cone, simple };\n\n/**\n * Turn a `curl` option into a model. Strings name a bundled curl; anything else is already a\n * model, imported from `@zinejs/core/curls` or written by the caller.\n */\nexport function resolveCurl(spec: CurlSpec): CurlModel {\n if (typeof spec !== 'string') return spec;\n const model = BUNDLED_CURLS[spec];\n if (model) return model;\n // Reached only when validation was bypassed (a renderer used directly, say), so name the fix\n // rather than the mistake: these curls are real, they just are not bundled.\n if (IMPORTABLE_CURLS.includes(spec)) {\n throw new Error(\n `Zine: the '${spec}' curl is not bundled. Import it and pass the model: ` +\n `import { ${spec} } from '@zinejs/core/curls' → curl: ${spec}`,\n );\n }\n throw new Error(`Zine: unknown curl ${JSON.stringify(spec)}.`);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ContextMenuOptions } from './types';
|
|
2
|
+
import type { Zine } from '../zine';
|
|
3
|
+
/**
|
|
4
|
+
* A right-click menu of reading controls, opened at the cursor over the book.
|
|
5
|
+
*
|
|
6
|
+
* It reuses the toolbar's control definitions and its {@link ControlMenu} body, so the same icons
|
|
7
|
+
* and actions appear whether a reader reaches them from the toolbar or a right-click. It is opt-in
|
|
8
|
+
* (`contextMenu` off by default), because overriding the browser's own menu is a real cost to pay
|
|
9
|
+
* only when asked. Works with `controls: false` too, which is the point: a bare book with no
|
|
10
|
+
* toolbar can still offer a right-click menu.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ContextMenu {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(zine: Zine, container: HTMLElement, options?: ContextMenuOptions, hidden?: ReadonlySet<string>);
|
|
15
|
+
destroy(): void;
|
|
16
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { ControlsOptions } from './types';
|
|
1
|
+
import type { ContextMenuOptions, ControlsOptions } from './types';
|
|
2
2
|
import type { Zine } from '../zine';
|
|
3
3
|
export { defineControl, getControl, DEFAULT_ITEMS } from './registry';
|
|
4
4
|
export { ICONS, createIcon } from './icons';
|
|
5
|
-
export type { ControlContext, ControlDef, ControlItem, ControlsColorScheme, ControlsOptions, ControlsPosition, } from './types';
|
|
5
|
+
export type { ContextMenuOptions, ControlContext, ControlDef, ControlItem, ControlsColorScheme, ControlsOptions, ControlsPosition, } from './types';
|
|
6
6
|
/**
|
|
7
7
|
* Build the toolbar inside `container` and return its teardown.
|
|
8
8
|
*
|
|
@@ -11,4 +11,11 @@ export type { ControlContext, ControlDef, ControlItem, ControlsColorScheme, Cont
|
|
|
11
11
|
* named `controls.ts` rather than `index.ts` so the emitted chunk is `controls-<hash>.js`, which
|
|
12
12
|
* is what the size-limit budget watches.
|
|
13
13
|
*/
|
|
14
|
-
export declare function mountControls(zine: Zine, container: HTMLElement, options?: ControlsOptions): () => void;
|
|
14
|
+
export declare function mountControls(zine: Zine, container: HTMLElement, options?: ControlsOptions, hidden?: ReadonlySet<string>): () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Attach the right-click menu to `container` and return its teardown.
|
|
17
|
+
*
|
|
18
|
+
* Shares this lazy chunk with {@link mountControls}, so a book that wants only the context menu
|
|
19
|
+
* (`controls: false`, `contextMenu: true`) still reuses the same icons and control definitions.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mountContextMenu(zine: Zine, container: HTMLElement, options?: ContextMenuOptions, hidden?: ReadonlySet<string>): () => void;
|
package/dist/controls/icons.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ export declare const ICONS: {
|
|
|
18
18
|
first: string;
|
|
19
19
|
/** chevrons-right */
|
|
20
20
|
last: string;
|
|
21
|
-
/** zoom
|
|
21
|
+
/** circle-plus: zoom in. A plus, not a magnifier, so it never reads as a third "search". */
|
|
22
22
|
zoomIn: string;
|
|
23
|
-
/** zoom
|
|
23
|
+
/** circle-minus: zoom out, paired with zoomIn. */
|
|
24
24
|
zoomOut: string;
|
|
25
25
|
/** search */
|
|
26
26
|
search: string;
|
|
@@ -46,6 +46,10 @@ export declare const ICONS: {
|
|
|
46
46
|
download: string;
|
|
47
47
|
/** x */
|
|
48
48
|
close: string;
|
|
49
|
+
/** volume-2: sound on */
|
|
50
|
+
soundOn: string;
|
|
51
|
+
/** volume-x: muted */
|
|
52
|
+
soundOff: string;
|
|
49
53
|
};
|
|
50
54
|
/** Build an `<svg>` for one of the {@link ICONS}, or null if the name is unknown. */
|
|
51
55
|
export declare function createIcon(doc: Document, markup: string): SVGSVGElement;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ControlContext, ControlDef, ControlItem } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Drop every control whose id is in `hidden`, the one filter behind the top-level `hideControls`
|
|
4
|
+
* option. Applied at each build point so a named control disappears from the toolbar, its overflow
|
|
5
|
+
* menu and the right-click menu at once. Separators are left in place; menus drop them anyway and
|
|
6
|
+
* the toolbar tidies its own runs after filtering.
|
|
7
|
+
*/
|
|
8
|
+
export declare function filterHidden(items: readonly ControlItem[], hidden: ReadonlySet<string>): readonly ControlItem[];
|
|
9
|
+
/**
|
|
10
|
+
* The shared menu machinery.
|
|
11
|
+
*
|
|
12
|
+
* A `.zine-controls-menu` is built the same way in two places: the toolbar's overflow (the ⋮
|
|
13
|
+
* button) and the right-click context menu. Rather than let those drift, both build their items,
|
|
14
|
+
* read their state, and run their actions through the helpers here, so a menu looks and behaves
|
|
15
|
+
* identically wherever it is anchored. Placement and dismissal stay with each owner, because a
|
|
16
|
+
* popover hanging off a toolbar button and one opening at the cursor differ only there.
|
|
17
|
+
*/
|
|
18
|
+
/** A control's label, which may depend on the current state. */
|
|
19
|
+
export declare function titleOf(def: ControlDef, ctx: ControlContext): string;
|
|
20
|
+
/** A control's glyph, which may likewise depend on state (an RTL mirror, say). */
|
|
21
|
+
export declare function iconOf(def: ControlDef, ctx: ControlContext): string | undefined;
|
|
22
|
+
/** The element itself if it can be disabled, else any inputs/buttons it wraps. */
|
|
23
|
+
export declare function formFields(el: HTMLElement): (HTMLButtonElement | HTMLInputElement)[];
|
|
24
|
+
/** Toggle the pending-action state on a button: a trailing spinner, disabled while it spins. */
|
|
25
|
+
export declare function setBusy(btn: HTMLButtonElement, busy: boolean): void;
|
|
26
|
+
/**
|
|
27
|
+
* Run a control's action, holding the button busy while a slow one (a cross-origin file fetched to
|
|
28
|
+
* save or print) settles. `onAfter` re-reads state once the action has run, and again when a
|
|
29
|
+
* pending one finishes. A disabled or already-busy button does nothing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function activateControl(def: ControlDef, trigger: HTMLButtonElement, ctx: ControlContext, onAfter: () => void): void;
|
|
32
|
+
/**
|
|
33
|
+
* A control button. Bar buttons are icon-only with the title as tooltip; menu items and any
|
|
34
|
+
* control without an icon also carry a visible label. `onActivate` is left to the caller so the
|
|
35
|
+
* toolbar can route a press through its submenu logic while a menu item just acts.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildControlButton(doc: Document, def: ControlDef, className: string, ctx: ControlContext, onActivate: (def: ControlDef, btn: HTMLButtonElement) => void, withLabel?: boolean): HTMLButtonElement;
|
|
38
|
+
/**
|
|
39
|
+
* Re-read one control's state after the book changes and reflect it on its element.
|
|
40
|
+
*
|
|
41
|
+
* Applies to custom `render` widgets as well as plain buttons: the predicates live on `ControlDef`,
|
|
42
|
+
* so a widget may be any element and each piece of state is applied however that element supports
|
|
43
|
+
* it. `visible` overrides the `isVisible` predicate for a control whose visibility is decided
|
|
44
|
+
* elsewhere (a submenu shown only when it has visible children).
|
|
45
|
+
*/
|
|
46
|
+
export declare function applyControlItemState(el: HTMLElement, def: ControlDef, ctx: ControlContext, doc: Document, visible?: boolean): void;
|
|
47
|
+
/**
|
|
48
|
+
* A `.zine-controls-menu`: a column of control items with `role=menu`. Owns the items it builds so
|
|
49
|
+
* it can re-read their state and clean up after itself. The owner supplies the context (whose
|
|
50
|
+
* `close()` dismisses this menu) and drives when to build, refresh, and destroy; where the menu is
|
|
51
|
+
* placed and how it is dismissed are the owner's business.
|
|
52
|
+
*/
|
|
53
|
+
export declare class ControlMenu {
|
|
54
|
+
#private;
|
|
55
|
+
readonly el: HTMLElement;
|
|
56
|
+
constructor(doc: Document, context: () => ControlContext, onAfterAction: () => void, hidden?: ReadonlySet<string>);
|
|
57
|
+
/** Fill the menu from a list of controls, dropping separators and anything not applicable. */
|
|
58
|
+
build(children: readonly ControlItem[]): void;
|
|
59
|
+
/** Re-read every item's state. */
|
|
60
|
+
refresh(): void;
|
|
61
|
+
/** Whether the menu (or something inside it) contains `node`. */
|
|
62
|
+
contains(node: Node | null): boolean;
|
|
63
|
+
focusFirst(): void;
|
|
64
|
+
destroy(): void;
|
|
65
|
+
}
|
|
66
|
+
/** A submenu is only worth showing if something inside it is. Keeps a menu trigger from opening
|
|
67
|
+
* onto nothing when every entry has hidden itself. */
|
|
68
|
+
export declare function hasVisibleChildren(def: ControlDef, ctx: ControlContext, hidden?: ReadonlySet<string>): boolean;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { type PanelOptions } from './sidebar';
|
|
2
|
-
import type { Zine } from '../zine';
|
|
2
|
+
import type { SearchHit, Zine } from '../zine';
|
|
3
|
+
/** The query and its results, so a closed panel can be reopened where the reader left it rather
|
|
4
|
+
* than blank. Held by the toolbar (which outlives the panel) and handed back on reopen. */
|
|
5
|
+
export interface SearchState {
|
|
6
|
+
query: string;
|
|
7
|
+
results: SearchHit[];
|
|
8
|
+
}
|
|
9
|
+
/** Panel options plus the optional state to reopen with. */
|
|
10
|
+
export interface SearchOptions extends PanelOptions {
|
|
11
|
+
/** Query and results from a previous open, put back so the reader continues where they left off. */
|
|
12
|
+
state?: SearchState;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
15
|
* Full-text search beside the book: a query field over its results.
|
|
5
16
|
*
|
|
@@ -11,6 +22,8 @@ export declare class Search {
|
|
|
11
22
|
#private;
|
|
12
23
|
/** The panel's themed root, so the toolbar can stamp its colour scheme. */
|
|
13
24
|
readonly root: HTMLElement;
|
|
14
|
-
constructor(zine: Zine, container: HTMLElement, options?:
|
|
25
|
+
constructor(zine: Zine, container: HTMLElement, options?: SearchOptions);
|
|
26
|
+
/** The query and results as they stand, for the toolbar to stash across a close. */
|
|
27
|
+
getState(): SearchState;
|
|
15
28
|
destroy(): void;
|
|
16
29
|
}
|