@marianmeres/stuic 3.16.0 → 3.17.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.
|
@@ -3,12 +3,31 @@
|
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
4
|
import type { Snippet } from "svelte";
|
|
5
5
|
|
|
6
|
+
export interface BookPageArea {
|
|
7
|
+
id: string | number;
|
|
8
|
+
/** X position in natural image pixels */
|
|
9
|
+
x: number;
|
|
10
|
+
/** Y position in natural image pixels */
|
|
11
|
+
y: number;
|
|
12
|
+
/** Width in natural image pixels */
|
|
13
|
+
w: number;
|
|
14
|
+
/** Height in natural image pixels */
|
|
15
|
+
h: number;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
export interface BookPage {
|
|
7
20
|
id: string | number;
|
|
8
21
|
src: string;
|
|
9
22
|
srcset?: string;
|
|
10
23
|
sizes?: string;
|
|
11
24
|
title?: string;
|
|
25
|
+
/** Natural image width in px (required when areas are used) */
|
|
26
|
+
width?: number;
|
|
27
|
+
/** Natural image height in px (required when areas are used) */
|
|
28
|
+
height?: number;
|
|
29
|
+
/** Clickable areas on this page */
|
|
30
|
+
areas?: BookPageArea[];
|
|
12
31
|
[key: string]: any;
|
|
13
32
|
}
|
|
14
33
|
|
|
@@ -37,7 +56,7 @@
|
|
|
37
56
|
keyboard?: boolean;
|
|
38
57
|
/** Enable swipe gesture navigation (default: true) */
|
|
39
58
|
swipe?: boolean;
|
|
40
|
-
/** Flip animation duration in ms (default:
|
|
59
|
+
/** Flip animation duration in ms (default: 500) */
|
|
41
60
|
duration?: number;
|
|
42
61
|
/** Enable zoom capability (default: true) */
|
|
43
62
|
zoom?: boolean;
|
|
@@ -54,6 +73,8 @@
|
|
|
54
73
|
/** Callback when a page is clicked, with relative x/y coordinates (0–1)
|
|
55
74
|
* (0, 0) = top-left corner, (1, 1) = bottom-right corner */
|
|
56
75
|
onPageClick?: (data: { page: BookPage; x: number; y: number }) => void;
|
|
76
|
+
/** Callback when a clickable area on a page is clicked */
|
|
77
|
+
onAreaClick?: (data: { area: BookPageArea; page: BookPage }) => void;
|
|
57
78
|
/** Custom render snippet for pages */
|
|
58
79
|
renderPage?: Snippet<[{ page: BookPage; position: "left" | "right" | "cover" }]>;
|
|
59
80
|
/** Custom class for container */
|
|
@@ -158,7 +179,7 @@
|
|
|
158
179
|
activeSpread = $bindable(0),
|
|
159
180
|
keyboard = true,
|
|
160
181
|
swipe = true,
|
|
161
|
-
duration =
|
|
182
|
+
duration = 500,
|
|
162
183
|
zoom: zoomEnabled = true,
|
|
163
184
|
zoomLevels: ZOOM_LEVELS = [1, 1.5, 2, 3],
|
|
164
185
|
clampPan = false,
|
|
@@ -166,6 +187,7 @@
|
|
|
166
187
|
responsive = true,
|
|
167
188
|
onSpreadChange,
|
|
168
189
|
onPageClick,
|
|
190
|
+
onAreaClick,
|
|
169
191
|
renderPage,
|
|
170
192
|
class: classProp,
|
|
171
193
|
classStage,
|
|
@@ -416,8 +438,6 @@
|
|
|
416
438
|
|
|
417
439
|
export function next() {
|
|
418
440
|
if (activeSpread < totalSpreads - 1) {
|
|
419
|
-
// The sheet being flipped is the one at index === activeSpread
|
|
420
|
-
setTransitioningSheet(activeSpread);
|
|
421
441
|
coll.setActiveNext();
|
|
422
442
|
resetZoom();
|
|
423
443
|
}
|
|
@@ -425,8 +445,6 @@
|
|
|
425
445
|
|
|
426
446
|
export function previous() {
|
|
427
447
|
if (activeSpread > 0) {
|
|
428
|
-
// The sheet being flipped back is the one at index === activeSpread - 1
|
|
429
|
-
setTransitioningSheet(activeSpread - 1);
|
|
430
448
|
coll.setActivePrevious();
|
|
431
449
|
resetZoom();
|
|
432
450
|
}
|
|
@@ -644,9 +662,9 @@
|
|
|
644
662
|
role="region"
|
|
645
663
|
aria-label="Book"
|
|
646
664
|
aria-roledescription="book"
|
|
647
|
-
style:
|
|
665
|
+
style:translate={isSinglePageMode
|
|
648
666
|
? "calc(var(--stuic-book-page-width) * -1)"
|
|
649
|
-
: "
|
|
667
|
+
: "0"}
|
|
650
668
|
style:touch-action="none"
|
|
651
669
|
style:user-select="none"
|
|
652
670
|
style:transform={zoomLevel !== 1
|
|
@@ -693,6 +711,28 @@
|
|
|
693
711
|
draggable="false"
|
|
694
712
|
/>
|
|
695
713
|
{/if}
|
|
714
|
+
{#if onAreaClick && sheet.frontPage.areas?.length && sheet.frontPage.width && sheet.frontPage.height}
|
|
715
|
+
<svg
|
|
716
|
+
viewBox="0 0 {sheet.frontPage.width} {sheet.frontPage.height}"
|
|
717
|
+
preserveAspectRatio="xMidYMid meet"
|
|
718
|
+
class={!unstyled ? "stuic-book-areas" : undefined}
|
|
719
|
+
>
|
|
720
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
721
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
722
|
+
{#each sheet.frontPage.areas as area (area.id)}
|
|
723
|
+
<rect
|
|
724
|
+
x={area.x} y={area.y}
|
|
725
|
+
width={area.w} height={area.h}
|
|
726
|
+
class={!unstyled ? "stuic-book-area" : undefined}
|
|
727
|
+
onclick={(e: MouseEvent) => {
|
|
728
|
+
if (_wasDragged) return;
|
|
729
|
+
e.stopPropagation();
|
|
730
|
+
onAreaClick({ area, page: sheet.frontPage! });
|
|
731
|
+
}}
|
|
732
|
+
/>
|
|
733
|
+
{/each}
|
|
734
|
+
</svg>
|
|
735
|
+
{/if}
|
|
696
736
|
{/if}
|
|
697
737
|
</div>
|
|
698
738
|
|
|
@@ -719,6 +759,28 @@
|
|
|
719
759
|
draggable="false"
|
|
720
760
|
/>
|
|
721
761
|
{/if}
|
|
762
|
+
{#if onAreaClick && sheet.backPage.areas?.length && sheet.backPage.width && sheet.backPage.height}
|
|
763
|
+
<svg
|
|
764
|
+
viewBox="0 0 {sheet.backPage.width} {sheet.backPage.height}"
|
|
765
|
+
preserveAspectRatio="xMidYMid meet"
|
|
766
|
+
class={!unstyled ? "stuic-book-areas" : undefined}
|
|
767
|
+
>
|
|
768
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
769
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
770
|
+
{#each sheet.backPage.areas as area (area.id)}
|
|
771
|
+
<rect
|
|
772
|
+
x={area.x} y={area.y}
|
|
773
|
+
width={area.w} height={area.h}
|
|
774
|
+
class={!unstyled ? "stuic-book-area" : undefined}
|
|
775
|
+
onclick={(e: MouseEvent) => {
|
|
776
|
+
if (_wasDragged) return;
|
|
777
|
+
e.stopPropagation();
|
|
778
|
+
onAreaClick({ area, page: sheet.backPage! });
|
|
779
|
+
}}
|
|
780
|
+
/>
|
|
781
|
+
{/each}
|
|
782
|
+
</svg>
|
|
783
|
+
{/if}
|
|
722
784
|
{/if}
|
|
723
785
|
</div>
|
|
724
786
|
</div>
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import type { ItemCollection as ItemCollectionBase } from "@marianmeres/item-collection";
|
|
2
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
+
export interface BookPageArea {
|
|
5
|
+
id: string | number;
|
|
6
|
+
/** X position in natural image pixels */
|
|
7
|
+
x: number;
|
|
8
|
+
/** Y position in natural image pixels */
|
|
9
|
+
y: number;
|
|
10
|
+
/** Width in natural image pixels */
|
|
11
|
+
w: number;
|
|
12
|
+
/** Height in natural image pixels */
|
|
13
|
+
h: number;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
4
16
|
export interface BookPage {
|
|
5
17
|
id: string | number;
|
|
6
18
|
src: string;
|
|
7
19
|
srcset?: string;
|
|
8
20
|
sizes?: string;
|
|
9
21
|
title?: string;
|
|
22
|
+
/** Natural image width in px (required when areas are used) */
|
|
23
|
+
width?: number;
|
|
24
|
+
/** Natural image height in px (required when areas are used) */
|
|
25
|
+
height?: number;
|
|
26
|
+
/** Clickable areas on this page */
|
|
27
|
+
areas?: BookPageArea[];
|
|
10
28
|
[key: string]: any;
|
|
11
29
|
}
|
|
12
30
|
export interface BookSpread {
|
|
@@ -33,7 +51,7 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
|
33
51
|
keyboard?: boolean;
|
|
34
52
|
/** Enable swipe gesture navigation (default: true) */
|
|
35
53
|
swipe?: boolean;
|
|
36
|
-
/** Flip animation duration in ms (default:
|
|
54
|
+
/** Flip animation duration in ms (default: 500) */
|
|
37
55
|
duration?: number;
|
|
38
56
|
/** Enable zoom capability (default: true) */
|
|
39
57
|
zoom?: boolean;
|
|
@@ -54,6 +72,11 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
|
54
72
|
x: number;
|
|
55
73
|
y: number;
|
|
56
74
|
}) => void;
|
|
75
|
+
/** Callback when a clickable area on a page is clicked */
|
|
76
|
+
onAreaClick?: (data: {
|
|
77
|
+
area: BookPageArea;
|
|
78
|
+
page: BookPage;
|
|
79
|
+
}) => void;
|
|
57
80
|
/** Custom render snippet for pages */
|
|
58
81
|
renderPage?: Snippet<[{
|
|
59
82
|
page: BookPage;
|
|
@@ -52,6 +52,7 @@ Pages are grouped into **spreads**:
|
|
|
52
52
|
| `singlePage` | `boolean` | `false` | Force single-page layout (one page per flip) |
|
|
53
53
|
| `responsive` | `boolean` | `true` | Auto-switch to single-page when container is too narrow |
|
|
54
54
|
| `onSpreadChange` | `(spread, index) => void` | - | Callback when active spread changes |
|
|
55
|
+
| `onAreaClick` | `({area, page}) => void` | - | Callback when a clickable area is clicked |
|
|
55
56
|
| `renderPage` | `Snippet` | - | Custom render snippet for pages |
|
|
56
57
|
| `class` | `string` | - | Custom class for container |
|
|
57
58
|
| `classStage` | `string` | - | Custom class for the 3D stage |
|
|
@@ -62,10 +63,22 @@ Pages are grouped into **spreads**:
|
|
|
62
63
|
## Interfaces
|
|
63
64
|
|
|
64
65
|
```typescript
|
|
66
|
+
interface BookPageArea {
|
|
67
|
+
id: string | number;
|
|
68
|
+
x: number; // X position in natural image pixels
|
|
69
|
+
y: number; // Y position in natural image pixels
|
|
70
|
+
w: number; // Width in natural image pixels
|
|
71
|
+
h: number; // Height in natural image pixels
|
|
72
|
+
[key: string]: any;
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
interface BookPage {
|
|
66
76
|
id: string | number;
|
|
67
77
|
src: string;
|
|
68
78
|
title?: string;
|
|
79
|
+
width?: number; // Natural image width in px (required for areas)
|
|
80
|
+
height?: number; // Natural image height in px (required for areas)
|
|
81
|
+
areas?: BookPageArea[];
|
|
69
82
|
[key: string]: any;
|
|
70
83
|
}
|
|
71
84
|
|
|
@@ -89,6 +102,32 @@ interface BookSpread {
|
|
|
89
102
|
| `resetZoom()` | Reset zoom to 1x |
|
|
90
103
|
| `getCollection()` | Get the underlying ItemCollection |
|
|
91
104
|
|
|
105
|
+
## Clickable Areas
|
|
106
|
+
|
|
107
|
+
Pages can define clickable areas (e.g. product hotspots in a catalog). Areas are rendered as an SVG overlay that scales correctly with the page image. Requires `width`/`height` on the page (natural image dimensions) and the `onAreaClick` callback.
|
|
108
|
+
|
|
109
|
+
```svelte
|
|
110
|
+
<script lang="ts">
|
|
111
|
+
import { Book, type BookPage } from "@marianmeres/stuic";
|
|
112
|
+
|
|
113
|
+
const pages: BookPage[] = [
|
|
114
|
+
{
|
|
115
|
+
id: 0,
|
|
116
|
+
src: "/catalog-page-1.jpg",
|
|
117
|
+
width: 2480,
|
|
118
|
+
height: 3508,
|
|
119
|
+
areas: [
|
|
120
|
+
{ id: "SKU-001", x: 100, y: 200, w: 400, h: 300 },
|
|
121
|
+
{ id: "SKU-002", x: 600, y: 200, w: 400, h: 300 },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
// ...
|
|
125
|
+
];
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<Book {pages} onAreaClick={({ area, page }) => addToCart(area.id)} />
|
|
129
|
+
```
|
|
130
|
+
|
|
92
131
|
## Keyboard Navigation
|
|
93
132
|
|
|
94
133
|
| Key | Action |
|
|
@@ -110,3 +149,4 @@ interface BookSpread {
|
|
|
110
149
|
| `--stuic-book-page-bg` | `var(--stuic-color-surface)` | Page background color |
|
|
111
150
|
| `--stuic-book-page-shadow` | `0 2px 16px rgba(0,0,0,0.15)` | Book shadow |
|
|
112
151
|
| `--stuic-book-radius` | `var(--radius-sm)` | Page border radius |
|
|
152
|
+
| `--stuic-book-area-fill-hover` | `rgba(0, 0, 0, 0.06)` | Area hover highlight fill |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* Book Component Tokens */
|
|
2
2
|
@theme inline {
|
|
3
|
-
--stuic-book-perspective:
|
|
4
|
-
--stuic-book-duration:
|
|
5
|
-
--stuic-book-timing: ease-
|
|
3
|
+
--stuic-book-perspective: 2000px;
|
|
4
|
+
--stuic-book-duration: 500ms;
|
|
5
|
+
--stuic-book-timing: ease-out;
|
|
6
6
|
--stuic-book-page-bg: transparent; /*var(--stuic-color-surface, #fff);*/
|
|
7
7
|
--stuic-book-page-shadow: none; /*0 2px 16px rgba(0, 0, 0, 0.15);*/
|
|
8
8
|
--stuic-book-radius: var(--radius-sm, 2px);
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/*
|
|
35
|
-
* Stage — always full double-page width, positioned via
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Stage — always full double-page width, positioned via translate.
|
|
36
|
+
* In single-page mode, translate shifts the stage left so only the
|
|
37
|
+
* right half (the visible sheet) is shown in the wrapper.
|
|
38
38
|
* Transparent — the wrapper provides the page background.
|
|
39
39
|
*/
|
|
40
40
|
.stuic-book-stage {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
perspective: var(--stuic-book-perspective);
|
|
43
43
|
width: calc(var(--stuic-book-page-width) * 2);
|
|
44
44
|
height: var(--stuic-book-page-height);
|
|
45
|
-
transition:
|
|
45
|
+
transition: translate var(--stuic-book-duration) var(--stuic-book-timing);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/* ---- Sheet (the flippable paper) ---- */
|
|
@@ -54,8 +54,9 @@
|
|
|
54
54
|
height: 100%;
|
|
55
55
|
transform-style: preserve-3d;
|
|
56
56
|
transform-origin: left center;
|
|
57
|
+
transform: rotateY(0deg);
|
|
57
58
|
transition: transform var(--stuic-book-duration) var(--stuic-book-timing);
|
|
58
|
-
|
|
59
|
+
/* outline: 3px solid black; */
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/* Front face (visible when not flipped — right side of the book) */
|
|
@@ -67,7 +68,6 @@
|
|
|
67
68
|
background: var(--stuic-book-page-bg);
|
|
68
69
|
border-radius: 0 var(--stuic-book-radius) var(--stuic-book-radius) 0;
|
|
69
70
|
overflow: hidden;
|
|
70
|
-
clip-path: inset(0);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
.stuic-book-sheet-front img {
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
background: var(--stuic-book-page-bg);
|
|
87
87
|
border-radius: var(--stuic-book-radius) 0 0 var(--stuic-book-radius);
|
|
88
88
|
overflow: hidden;
|
|
89
|
-
clip-path: inset(0);
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
.stuic-book-sheet-back img {
|
|
@@ -95,6 +94,26 @@
|
|
|
95
94
|
object-fit: contain;
|
|
96
95
|
}
|
|
97
96
|
|
|
97
|
+
/* ---- Clickable areas SVG overlay ---- */
|
|
98
|
+
.stuic-book-areas {
|
|
99
|
+
position: absolute;
|
|
100
|
+
inset: 0;
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.stuic-book-area {
|
|
107
|
+
pointer-events: all;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
fill: transparent;
|
|
110
|
+
transition: fill 150ms;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.stuic-book-area:hover {
|
|
114
|
+
fill: var(--stuic-book-area-fill-hover, rgba(0, 0, 0, 0.06));
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
/* Placeholder: empty page face with content on the other side */
|
|
99
118
|
.stuic-book-sheet-front[data-placeholder],
|
|
100
119
|
.stuic-book-sheet-back[data-placeholder] {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as Book, type Props as BookProps, type BookPage, type BookSpread, type BookSheet, type BookCollection, buildSpreads, buildSinglePageSpreads, buildSheets, } from "./Book.svelte";
|
|
1
|
+
export { default as Book, type Props as BookProps, type BookPage, type BookPageArea, type BookSpread, type BookSheet, type BookCollection, buildSpreads, buildSinglePageSpreads, buildSheets, } from "./Book.svelte";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/stuic",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"!dist/**/*.test.*",
|
|
@@ -39,26 +39,26 @@
|
|
|
39
39
|
"@marianmeres/icons-fns": "^5.0.0",
|
|
40
40
|
"@marianmeres/random-human-readable": "^1.6.1",
|
|
41
41
|
"@sveltejs/adapter-auto": "^4.0.0",
|
|
42
|
-
"@sveltejs/kit": "^2.
|
|
42
|
+
"@sveltejs/kit": "^2.51.0",
|
|
43
43
|
"@sveltejs/package": "^2.5.7",
|
|
44
44
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
45
45
|
"@tailwindcss/cli": "^4.1.18",
|
|
46
46
|
"@tailwindcss/forms": "^0.5.11",
|
|
47
47
|
"@tailwindcss/typography": "^0.5.19",
|
|
48
48
|
"@tailwindcss/vite": "^4.1.18",
|
|
49
|
-
"@types/node": "^25.2.
|
|
49
|
+
"@types/node": "^25.2.3",
|
|
50
50
|
"dotenv": "^16.6.1",
|
|
51
51
|
"eslint": "^9.39.2",
|
|
52
52
|
"globals": "^16.5.0",
|
|
53
53
|
"prettier": "^3.8.1",
|
|
54
54
|
"prettier-plugin-svelte": "^3.4.1",
|
|
55
55
|
"publint": "^0.3.17",
|
|
56
|
-
"svelte": "^5.50.
|
|
56
|
+
"svelte": "^5.50.3",
|
|
57
57
|
"svelte-check": "^4.3.6",
|
|
58
58
|
"tailwindcss": "^4.1.18",
|
|
59
59
|
"tsx": "^4.21.0",
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
|
-
"typescript-eslint": "^8.
|
|
61
|
+
"typescript-eslint": "^8.55.0",
|
|
62
62
|
"vite": "^7.3.1",
|
|
63
63
|
"vitest": "^3.2.4"
|
|
64
64
|
},
|