@marianmeres/stuic 3.32.2 → 3.33.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.
|
@@ -310,10 +310,31 @@
|
|
|
310
310
|
});
|
|
311
311
|
});
|
|
312
312
|
|
|
313
|
-
// ----
|
|
313
|
+
// ---- Debounced "settled" spread — prevents mass image downloads during rapid slider drags ----
|
|
314
|
+
|
|
315
|
+
let settledSpread = $state(0);
|
|
316
|
+
let _settleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
314
317
|
|
|
315
318
|
$effect(() => {
|
|
316
319
|
const current = activeSpread;
|
|
320
|
+
clearTimeout(_settleTimer);
|
|
321
|
+
// Small jump (±1): settle immediately (normal next/prev navigation)
|
|
322
|
+
if (Math.abs(current - settledSpread) <= 1) {
|
|
323
|
+
settledSpread = current;
|
|
324
|
+
} else {
|
|
325
|
+
// Large jump (slider drag): debounce to avoid intermediate downloads
|
|
326
|
+
_settleTimer = setTimeout(() => {
|
|
327
|
+
settledSpread = current;
|
|
328
|
+
}, 120);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
$effect(() => () => clearTimeout(_settleTimer));
|
|
333
|
+
|
|
334
|
+
// ---- Image preloading ----
|
|
335
|
+
|
|
336
|
+
$effect(() => {
|
|
337
|
+
const current = settledSpread;
|
|
317
338
|
const toPreload: PreloadImgOptions[] = [];
|
|
318
339
|
for (
|
|
319
340
|
let i = Math.max(0, current - 1);
|
|
@@ -704,7 +725,7 @@
|
|
|
704
725
|
|
|
705
726
|
{#if spreads.length}
|
|
706
727
|
<!-- Measurement wrapper: always 100% width for responsive detection -->
|
|
707
|
-
<div bind:clientWidth={containerWidth} style:width="100%" style:
|
|
728
|
+
<div bind:clientWidth={containerWidth} style:width="100%" style:display="flex" style:justify-content="center">
|
|
708
729
|
<!-- Visual book wrapper: owns background/shadow/radius -->
|
|
709
730
|
<div
|
|
710
731
|
bind:this={el}
|
|
@@ -741,7 +762,7 @@
|
|
|
741
762
|
<!-- Sheets (3D flippable elements) — no static pages needed -->
|
|
742
763
|
{#each sheets as sheet (sheet.id)}
|
|
743
764
|
{@const flipped = sheet.id < activeSpread}
|
|
744
|
-
{@const isNearby = Math.abs(sheet.id -
|
|
765
|
+
{@const isNearby = Math.abs(sheet.id - settledSpread) <= 2}
|
|
745
766
|
<div
|
|
746
767
|
class={twMerge(!unstyled && "stuic-book-sheet")}
|
|
747
768
|
data-flipped={flipped ? "true" : undefined}
|
|
@@ -51,16 +51,37 @@
|
|
|
51
51
|
// We own the single/dual page decision (responsive={false}) to avoid feedback loops —
|
|
52
52
|
// the Book's internal responsive detection uses bind:clientWidth which conflicts with
|
|
53
53
|
// our external sizing and can oscillate during flip animations.
|
|
54
|
-
// Uses window resize event instead of ResizeObserver for the same reason.
|
|
55
54
|
$effect(() => {
|
|
56
55
|
if (!containerEl) return;
|
|
57
56
|
let timer: ReturnType<typeof setTimeout>;
|
|
57
|
+
let lastCW = 0;
|
|
58
|
+
let lastCH = 0;
|
|
58
59
|
|
|
59
60
|
const apply = () => {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// use getBoundingClientRect() for subpixel precision — clientWidth/clientHeight
|
|
62
|
+
// return integers which can round UP, causing the page to exceed available
|
|
63
|
+
// space by a fraction of a pixel (enough to trigger scrollbars in min-h-screen
|
|
64
|
+
// layouts). The later Math.floor() on page dimensions rounds DOWN from the
|
|
65
|
+
// precise float, guaranteeing the page never exceeds the container.
|
|
66
|
+
const rect = containerEl!.getBoundingClientRect();
|
|
67
|
+
const style = getComputedStyle(containerEl!);
|
|
68
|
+
const cw = rect.width
|
|
69
|
+
- parseFloat(style.paddingLeft)
|
|
70
|
+
- parseFloat(style.paddingRight)
|
|
71
|
+
- parseFloat(style.borderLeftWidth)
|
|
72
|
+
- parseFloat(style.borderRightWidth);
|
|
73
|
+
const ch = rect.height
|
|
74
|
+
- parseFloat(style.paddingTop)
|
|
75
|
+
- parseFloat(style.paddingBottom)
|
|
76
|
+
- parseFloat(style.borderTopWidth)
|
|
77
|
+
- parseFloat(style.borderBottomWidth);
|
|
62
78
|
if (!cw || !ch) return;
|
|
63
79
|
|
|
80
|
+
// skip if dimensions unchanged (prevents ResizeObserver oscillation)
|
|
81
|
+
if (cw === lastCW && ch === lastCH) return;
|
|
82
|
+
lastCW = cw;
|
|
83
|
+
lastCH = ch;
|
|
84
|
+
|
|
64
85
|
// suppress width/translate transitions during dimension changes,
|
|
65
86
|
// then restore so flip animations (stage translate) work normally
|
|
66
87
|
resizing = true;
|
|
@@ -94,7 +115,16 @@
|
|
|
94
115
|
// initial measurement
|
|
95
116
|
apply();
|
|
96
117
|
|
|
97
|
-
//
|
|
118
|
+
// observe container resizes (layout-driven changes like sidebar toggle)
|
|
119
|
+
const ro = new ResizeObserver(() => {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
timer = setTimeout(apply, debounceMs);
|
|
122
|
+
});
|
|
123
|
+
ro.observe(containerEl!);
|
|
124
|
+
|
|
125
|
+
// also listen to window resize — ResizeObserver may not fire in
|
|
126
|
+
// min-h-screen layouts where viewport changes don't immediately
|
|
127
|
+
// propagate to the container's border box
|
|
98
128
|
const onResize = () => {
|
|
99
129
|
clearTimeout(timer);
|
|
100
130
|
timer = setTimeout(apply, debounceMs);
|
|
@@ -103,6 +133,7 @@
|
|
|
103
133
|
|
|
104
134
|
return () => {
|
|
105
135
|
clearTimeout(timer);
|
|
136
|
+
ro.disconnect();
|
|
106
137
|
window.removeEventListener("resize", onResize);
|
|
107
138
|
};
|
|
108
139
|
});
|
|
@@ -130,6 +161,7 @@
|
|
|
130
161
|
<style>
|
|
131
162
|
.stuic-book-responsive {
|
|
132
163
|
flex: 1;
|
|
164
|
+
min-height: 0;
|
|
133
165
|
display: flex;
|
|
134
166
|
align-items: center;
|
|
135
167
|
justify-content: center;
|