@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
- // ---- Image preloading ----
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:text-align="center">
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 - activeSpread) <= 2}
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
- const cw = containerEl!.clientWidth;
61
- const ch = containerEl!.clientHeight;
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
- // subsequent resizes debounced to avoid flickering during drag
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.32.2",
3
+ "version": "3.33.1",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",