Skip to content

Conversation

fyzanshaik
Copy link

@fyzanshaik fyzanshaik commented Aug 27, 2025

What does this PR do?

Fixes Carousel component where navigation buttons were disabled and users couldn't scroll.

Issues fixed:

  • Right arrow button was hardcoded as disabled
  • Broken scroll calculation logic
  • Missing button state initialization
  • No mouse wheel support

Test Plan

  1. Go to /docs page
  2. Click left/right arrow buttons - should work now
  3. Use mouse wheel over carousel - should scroll horizontally
  4. Verify buttons disable at start/end positions

Related PRs and Issues

Fixes #2122

Have you read the Contributing Guidelines on issues?

Yes.

Summary by CodeRabbit

  • New Features

    • Mouse wheel now scrolls the carousel horizontally, translating vertical wheel movement into horizontal motion.
    • Smoother next/prev navigation with smart, size-based scroll steps.
    • Automatic state stabilization after load for accurate start/end indicators.
  • Bug Fixes

    • More precise end-of-carousel detection to prevent over-scrolling.
    • Forward button correctly disables at the end.
    • Prevents unintended vertical page scrolling when interacting with the carousel.
    • Guards against missing elements to avoid runtime errors and ensures consistent scroll behavior.

@coolify-appwrite-org
Copy link

coolify-appwrite-org bot commented Aug 27, 2025

The preview deployment failed. 🔴

Open Build Logs

Last updated at: 2025-08-27 20:38:32 CET

Copy link

appwrite bot commented Aug 27, 2025

appwrite.io

Project ID: 684969cb000a2f6c0a02

Sites (1)
Site Status Logs Preview QR
 website
68496a17000f03d62013
Failed Failed Authorize Preview URL QR Code

Note

Appwrite has a Discord community with over 16 000 members.

Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

  • Replaced persistent scroll accumulator with a deterministic calculateScrollAmount.
  • calculateScrollAmount guards null refs, derives child size from first li offsetWidth plus gap, and returns numberOfItems * childSize * direction.
  • next/prev now use carousel.scrollBy({ left, behavior: 'smooth' }).
  • handleScroll null-guards, preserves isStart, and updates isEnd using carousel.scrollWidth - 1.
  • Added an effect to invoke handleScroll after mount when the carousel becomes available.
  • Forward button disabled state is now bound to isEnd.
  • Added onwheel on the list to convert vertical wheel delta to horizontal scrolling and prevent default when applicable.

Possibly related issues

  • appwrite/appwrite#10080 — Adjusts scroll calculations, button-driven scrollBy, end-state handling, and wheel-to-horizontal scroll, matching the navigation/button behavior concerns.

Pre-merge checks (5 passed)

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix: Carousel navigation buttons and scrolling functionality" is concise and directly describes the primary change in the PR — fixing carousel navigation and scrolling behavior (button state and scroll calculation/wheel support) as implemented in src/lib/components/Carousel.svelte.
Linked Issues Check ✅ Passed The modifications implement deterministic scroll calculation, remove the hard-coded disabled forward button, add initial handleScroll stabilization, and translate vertical wheel events into horizontal scrolling, which together restore button-driven shifting and add wheel support required by the linked bug report; these changes directly satisfy the objectives in issue [#2122].
Out of Scope Changes Check ✅ Passed All changes are localized to src/lib/components/Carousel.svelte and pertain to scroll calculation, button state, and wheel handling; there are no edits to unrelated files or to public/exported APIs, so no out-of-scope changes were detected.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/lib/components/Carousel.svelte (3)

47-51: Harden end detection to avoid off-by-a-few-pixels issues across zoom/scrollbar variances.

Use a small epsilon and clientWidth to reduce false negatives/positives at boundaries.

-        isStart = carousel.scrollLeft <= 0;
-        isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth - 1;
+        const epsilon = 2; // px tolerance
+        isStart = carousel.scrollLeft <= epsilon;
+        isEnd = carousel.scrollLeft + carousel.clientWidth >= carousel.scrollWidth - epsilon;

53-59: Avoid scheduling unbounded timeouts on every state change; initialize on mount and react to resizes.

$effect runs after any state change; each run schedules a setTimeout, which can pile up. Prefer onMount and observe size changes to keep isStart/isEnd accurate on responsive layouts.

-$effect(() => {
-    if (carousel) {
-        setTimeout(() => {
-            handleScroll();
-        }, 0);
-    }
-});
+onMount(() => {
+    handleScroll();
+    const ro = new ResizeObserver(() => handleScroll());
+    if (carousel) ro.observe(carousel);
+    const onWinResize = () => handleScroll();
+    window.addEventListener('resize', onWinResize);
+    return () => {
+        ro.disconnect();
+        window.removeEventListener('resize', onWinResize);
+    };
+});

Add (outside this hunk) at the top:

import { onMount } from 'svelte';

Want me to include a MutationObserver as well to react when items mount/unmount?


95-100: Wheel UX: let the page scroll at edges, handle non-pixel deltaModes, and use scrollBy.

Currently vertical wheel is always intercepted, which traps page scroll when the carousel is at start/end. Also, some devices emit line/page deltas. The tweak below improves ergonomics without changing intent.

-            onwheel={(e) => {
-                if (e.deltaY !== 0) {
-                    e.preventDefault();
-                    carousel.scrollLeft += e.deltaY;
-                }
-            }}
+            onwheel={(e) => {
+                if (!carousel) return;
+                const dy = e.deltaY;
+                if (dy === 0) return;
+                const atStart = carousel.scrollLeft <= 0;
+                const atEnd =
+                    Math.ceil(carousel.scrollLeft + carousel.clientWidth) >= carousel.scrollWidth - 1;
+                // Let page scroll if we'd go beyond bounds
+                if ((dy < 0 && atStart) || (dy > 0 && atEnd)) return;
+                e.preventDefault();
+                const multiplier = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? carousel.clientHeight : 1;
+                carousel.scrollBy({ left: dy * multiplier, behavior: 'auto' });
+            }}

Please verify across:

  • Mac trackpad
  • Standard mouse wheel on Windows/Linux
  • High zoom (125%/150%) where fractional pixels are common
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84df194 and f40a406.

📒 Files selected for processing (1)
  • src/lib/components/Carousel.svelte (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: tests
🔇 Additional comments (1)
src/lib/components/Carousel.svelte (1)

79-79: Good fix: forward button now respects scroll state.

Binding disabled to isEnd resolves the hardcoded-disabled bug.

Comment on lines 15 to 28
function calculateScrollAmount(prev = false) {
if (!carousel) return 0;
const direction = prev ? -1 : 1;
const carouselSize = carousel?.clientWidth;
const childSize = (carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
const carouselSize = carousel.clientWidth;
scroll = scroll || carouselSize;
const firstChild = carousel.querySelector('li') as HTMLElement;
if (!firstChild) return 0;
const childSize = firstChild.offsetWidth + gap;
const numberOfItems = Math.floor(carouselSize / childSize);
const overflow = scroll % childSize;
const amount = numberOfItems * childSize - overflow * direction;
scroll += amount * direction;
return amount * direction;
return numberOfItems * childSize * direction;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix zero-step bug when item is wider than viewport; prefer direct-child query and clamp page size.

If firstChild.offsetWidth + gap > carousel.clientWidth, Math.floor(...) becomes 0 so scrollBy({ left: 0 }) does nothing and buttons appear broken on narrow viewports. Also, querySelector('li') may match nested list items; use :scope > li.

-        const firstChild = carousel.querySelector('li') as HTMLElement;
+        const firstChild = carousel.querySelector(':scope > li') as HTMLElement | null;
         if (!firstChild) return 0;

-        const childSize = firstChild.offsetWidth + gap;
-        const numberOfItems = Math.floor(carouselSize / childSize);
-
-        return numberOfItems * childSize * direction;
+        const childSize = firstChild.offsetWidth + gap;
+        const itemsPerPage = Math.max(1, Math.floor(carouselSize / childSize));
+        return itemsPerPage * childSize * direction;

Optional typings outside this hunk (helps TS and DOM correctness):

// line 4
let carousel: HTMLUListElement;
// line 15
function calculateScrollAmount(prev = false): number {
🤖 Prompt for AI Agents
In src/lib/components/Carousel.svelte around lines 15 to 28, the calculation can
return 0 when an item (offsetWidth + gap) is larger than the carousel viewport
and querySelector('li') may match nested items; update the function to select
direct children via carousel.querySelector(':scope > li') and compute
numberOfItems as Math.max(1, Math.floor(carouselSize / childSize)) (so we always
scroll at least one item), ensure the function returns a number type, and
optionally add typing for carousel as HTMLUListElement and the function
signature as calculateScrollAmount(prev = false): number.

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.

🐛 Bug Report: Docs carousel component does't shift when clicked

2 participants