Skip to content

v2 multi-axis#521

Open
clementroche wants to merge 18 commits into
v2from
v2-multi-axis
Open

v2 multi-axis#521
clementroche wants to merge 18 commits into
v2from
v2-multi-axis

Conversation

@clementroche

@clementroche clementroche commented Jun 3, 2026

Copy link
Copy Markdown
Member

Merges the multi-axis work into v2.

Commits

  • phase1 / phase2 — multi-axis foundation
  • maximum inertia, snap, tweaks, two-axis example
  • refactor(react): use useSyncExternalStore for the lenis store (React peer bumped to >=18)
  • refactor(react): drop deprecated autoRaf prop + its type

scrollTo as a single logical operation

scrollTo({ x, y }) used to fan out into two scrollAxisTo calls, each handed the same options — so onStart/onComplete/userData/lock applied once per axis. A new dispatchScrollTo orchestrator makes one call = one operation:

  • callbacksonStart fires once (first axis to start), onComplete once (last axis to settle). 2D snap no longer fires onSnapStart/onSnapComplete twice.
  • userData — single operation-scoped tag (get/set), readable until the whole operation completes; getter no longer aggregates per-axis (removes the snap inFlight workaround rationale).
  • lock — single instance-wide isLocked flag (get/set), all-or-nothing; drops the per-axis lock and the OR/AND orientation composition that contradicted its own doc.
  • offset — applies to every target form including 2D, and accepts number | { x?, y? } for per-axis offsets.
  • immediate — fires both onStart and onComplete.
  • scrollAxisTo is now private (gesture handler + orchestrator only).

Review fixes

  • emit('gesture', …) no longer forwards false to subscribers (guard moved above the emit).
  • QUANTIZE in animate.ts documented.
  • Removed committed *.stackdump crash dumps and gitignored them.

Docs

  • LENIS-API.md — behavioral source of truth for every Lenis property/method and every scrollTo option.

🤖 Generated with Claude Code

clementroche and others added 11 commits May 13, 2026 15:06
Replace the useState/useEffect subscription in useStore with
useSyncExternalStore, the canonical primitive for external stores.
Bind subscribe/get as arrow class fields so they keep a stable
identity when passed to the hook, and pass get as getServerSnapshot
for correct SSR/hydration. Bump the react peer dep to >=18 since
useSyncExternalStore is React 18+.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the deprecated top-level autoRaf prop from ReactLenis and rely
solely on options.autoRaf, simplifying the lenis setup effect and its
dependency array.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@clementroche clementroche requested a review from a team as a code owner June 3, 2026 19:50
@clementroche clementroche requested review from FerminBD and elliott-romano and removed request for a team June 3, 2026 19:50
@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lenis-playground Ready Ready Preview, Comment Jun 4, 2026 6:36pm

Request Review

@clementroche

Copy link
Copy Markdown
Member Author

Review notes (v2-multi-axis → v2)

Automated + manual review of the full diff (23 files, +1496/−552). Logged here for the record.

🚩 Weird / shouldn't be committed

  • bash.exe.stackdump + playground/bash.exe.stackdump — Windows msys crash dumps, tracked in git and not gitignored. Should be git rm'd and added to .gitignore.
  • animate.ts: QUANTIZE = 10 — new magic constant that changes animation "done" detection granularity for all animations (not multi-axis specific), uncommented.

🐞 Bugs to fix before merge

  1. emit('gesture', …) can emit false (packages/core/src/lenis.ts:237). The emit moved above the if (data === false) return guard and now passes raw data. When onGesture returns false to cancel, every on('gesture') subscriber receives false, violating the GestureCallback type contract. Verified against the diff.
  2. 2D snap fires onSnapStart/onSnapComplete twice. Core invokes them per-axis (scrollAxisTo), so a {x,y} snap calls the user callback once for x and once for y. Not de-duplicated.
  3. isLocked doc says "both axes", code says OR (lenis.ts:~1190, this.x.isLocked || this.y.isLocked). For orientation:'both', a partial scrollTo({x}, {lock:true}) reports the whole instance locked and adds lenis-locked while the y axis is still interactive. Doc or code is wrong.
  4. userData getter doesn't aggregate as documented. It returns the active axis's userData, which clears when that axis finishes — so a 2D scrollTo loses the tag while the other axis is still animating. packages/snap/src/snap.ts:44 already carries a workaround comment for exactly this.

⚠️ Possibly missing / incomplete

  • Axis.checkOverflow lost its side effects. Old code reset the axis + emitted + updated className when scrollability flipped at runtime; the new getter is pure, so an in-flight animation won't halt when an axis becomes non-scrollable. The JSDoc still describes the old behavior.
  • isSmooth public getter removed from Lenis (breaking — was public API).
  • No back-compat shim for the removed snap type option — existing {type:'mandatory'} configs silently no-op.
  • Snap README gaps: lock: 'true' (string, should be boolean true) in the example; the slideshow example omits the required mode:'directional'; lock and the align tuple form aren't documented; stale references to the removed type:'mandatory'.

✅ Looks good

  • MULTI-AXIS-PLAN.md has zero unchecked items.
  • No stray console.log / debugger / TODO in core or snap source (the old debug log in axis.ts scrollTo was removed).
  • Per-axis math is handled symmetrically for both axes.

Public API breaking changes (for the changelog)

  • Lenis: isSmooth getter removed; userData is now a read-only getter (was a writable field); velocity/lastVelocity/direction/targetScroll/animatedScroll are now getter/setters aliasing the active axis (alias y only in 'both' mode).
  • Snap: SnapItem {value}{x?, y?} (flows into onSnapStart/onSnapComplete payloads); add(value)add(x, y?); type option replaced by mode + lock.

🤖 Generated with Claude Code

Document why lerp completion is quantized: lerping asymptotically
approaches the target without reaching it, so QUANTIZE snaps both
values to a coarse grid to consider the animation done once within
~0.1 and avoid a never-ending animation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clementroche and others added 3 commits June 4, 2026 17:45
Windows msys crash dumps (bash.exe.stackdump, playground/bash.exe.stackdump)
were tracked in git. Delete them and add *.stackdump to .gitignore so they
don't come back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scrollTo({ x, y }) fanned out into two scrollAxisTo calls, each handed the
same options — so onStart/onComplete/userData/lock applied once per axis
(callbacks fired twice; snap needed an inFlight workaround). Add a
dispatchScrollTo orchestrator: one call fires onStart once and onComplete
once across all driven axes.

- userData: single operation-scoped tag (get/set), readable until the whole
  operation completes; getter no longer aggregates per-axis.
- lock: single instance-wide isLocked flag (get/set), all-or-nothing — drops
  the per-axis lock and the OR/AND orientation composition that disagreed
  with its own doc.
- offset: applies to every target form including 2D, and accepts
  number | { x?, y? } for per-axis offsets.
- immediate: fires both onStart and onComplete.
- scrollAxisTo is now private (gesture handler + orchestrator only).

Document the full instance contract in LENIS-API.md as the source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire up the Snap demo in the two-axis playground with onSnapStart/onSnapComplete
logging, map lenis/* to the built d.ts files in tsconfig, and record session
permission grants.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ow doc

When an axis becomes non-scrollable at runtime, Lenis.checkOverflow now calls
updateClassName so `lenis-stopped` reflects the change. Also drop the stale
"returns true" line from Axis.checkOverflow's JSDoc (it returns void).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Restore the public `Lenis.isSmooth` getter (isScrolling === 'smooth')
  removed during the multi-axis work, and document it in LENIS-API.md.
- Snap README: fix the slideshow example (boolean `lock: true` + the
  required `mode: 'directional'`), document the `lock` option and the
  `align` tuple form, correct the `add(x, y?)` signature, and drop stale
  `type: 'mandatory'` references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant