fix(carousel): first-click no-op — viewportSize was stale until ResizeObserver fired

Real bug behind the persistent "los clicks fallan" report. The
`viewportSize` derivation was reading exclusively from the
`viewportClient{Width,Height}` $state mirrors, which are populated
asynchronously by the ResizeObserver. On first paint (and on the
very first click after mount / orientation switch) those mirrors were
still `0`. So:

  translate = -value × slideSize
            = -1   × (viewportSize / 1)
            = -1   × 0
            = 0

The index DID advance (soma's `value` went 0→1) but the rail stayed
at translate=0, so the user saw the active slide unchanged — looked
exactly like a dead click. Subsequent clicks accumulated until the
observer finally fired and the carousel suddenly jumped to whatever
index had become.

Fix: two-tier read in the `viewportSize` derivation. Prefer the
observer-tracked mirror when non-zero (steady state, picks up reshape);
fall back to a synchronous `vp.clientWidth` / `clientHeight` read while
the mirror is still 0. Same derivation reactivity, no extra effects.

Verified: first click after page load now lands `translate3d(-380px, 0,
0)` on a 380px-wide viewport (idx 0→1) — was 0 / no movement before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 09bbf05ec0
commit a81b44b40c

@ -201,17 +201,26 @@ export class CarouselProvider {
// ── Geometry ─────────────────────────────────────────────────────────────
/**
* Size (px) of the viewport along the primary axis. Sourced from the
* `viewportClient{Width,Height}` reactive mirrors (populated by the
* ResizeObserver above) so that layout-only changes — like flipping
* orientation, which rewrites the viewport's block-size via the eidos
* recipe — propagate to translate calc on the next reactive tick.
* Size (px) of the viewport along the primary axis.
*
* Two-tier read: the ResizeObserver mirrors (`viewportClient{Width,
* Height}`) are the steady-state source — they pick up layout reshape
* (orientation flip, container resize, font scaling) on the next
* frame. BUT the observer is async, so on first paint — or on the very
* first user interaction after mount — those mirrors may still be `0`.
* Falling back to the live `clientWidth/Height` reads synchronously
* from the DOM so the first click translates to a real pixel offset
* instead of `value * 0 = 0` (the bug that made early clicks look like
* dead-no-ops while the index actually advanced).
*/
readonly viewportSize = $derived.by(() =>
this.opts.orientation.current === 'horizontal'
? this.viewportClientWidth
: this.viewportClientHeight
);
readonly viewportSize = $derived.by(() => {
const horizontal = this.opts.orientation.current === 'horizontal';
const tracked = horizontal ? this.viewportClientWidth : this.viewportClientHeight;
if (tracked > 0) return tracked;
const vp = this.viewportRef;
if (!vp) return 0;
return horizontal ? vp.clientWidth : vp.clientHeight;
});
/** Slide size (px) along the primary axis = viewport / slidesPerView. */
readonly slideSize = $derived.by(() => {

Loading…
Cancel
Save

Powered by TurnKey Linux.