Skip to content

fix: prevent multiple carousels from responding to arrow keys at the same time#752

Merged
tomaszpacior merged 3 commits intomainfrom
684-bug-incorrect-keyboard-handling-on-product-details-page
Mar 9, 2026
Merged

fix: prevent multiple carousels from responding to arrow keys at the same time#752
tomaszpacior merged 3 commits intomainfrom
684-bug-incorrect-keyboard-handling-on-product-details-page

Conversation

@tomaszpacior
Copy link
Copy Markdown
Contributor

@tomaszpacior tomaszpacior commented Feb 27, 2026

What does this PR do?

  • fix: prevent multiple carousels from responding to arrow keys at the same time

Related Ticket(s)

Key Changes

  • Introduced managed keyboard navigation for Swiper-based carousels so only one active carousel responds to ArrowLeft / ArrowRight at a time.

  • Added a shared active-carousel manager and reusable keyboard hook in @o2s/ui:

    • carouselKeyboardManager.ts
    • useManagedCarouselKeyboard.ts
  • Extended carousel API with:

    • keyboardControlMode (swiper-native | managed | off)
    • keyboardCarouselId
  • Removed defaultKeyboardActive from the API and implementation.

  • Updated Carousel, ProductGallery, and ProductCarousel to use the managed keyboard flow.

  • Added automatic keyboardCarouselId resolution in ProductCarousel when not provided in props (manual wiring is no longer required in typical usage).

  • Side effects:

    • Keyboard behavior is deterministic when multiple carousels are visible on one page.
    • Arrow-key takeover no longer happens by default; keyboard control starts after carousel focus/pointer interaction.
    • Existing carousel behavior in swiper-native mode is preserved (backward-compatible default in Carousel).

How to test

  • Setup:

    • Pull branch and install dependencies if needed (npm install).
    • Run app as usual (frontend with mocked/content backend as used locally).
  • Test steps:

    1. Open a Product Details page where both:
      • product image gallery, and
      • recommended products carousel
        are visible at the same time.
    2. Without focusing/clicking any carousel, press ArrowRight / ArrowLeft.
      • Expected: page keeps native arrow-key behavior (no default carousel takeover).
    3. Click/focus product gallery and press arrows.
      • Expected: only ProductDetails gallery moves.
    4. Click/focus recommended products carousel and press arrows.
      • Expected: only recommended products carousel moves.
    5. Open gallery lightbox and use arrows.
      • Expected: lightbox carousel responds; background carousels do not.
    6. Close lightbox and press arrows again.
      • Expected: control returns to the main product gallery after interaction.
    7. (Optional) verify touch/swipe on mobile viewport still works as before.
  • Validation run:

    • npm run build (repo root) ✅
    • (Optional, package-level) npm run lint in:
      • packages/ui
      • packages/blocks/product-details
      • packages/blocks/recommended-products

Summary by CodeRabbit

  • New Features

    • Enhanced keyboard navigation for product image galleries and carousels with a managed keyboard control mode.
    • Only the active carousel responds to left/right arrow keys, preventing conflicting keyboard interactions.
    • Keyboard control now activates via focus or pointer interaction and automatically resolves carousel identifiers.
    • Product gallery lightbox now seamlessly transitions keyboard focus when opened and closed.
  • Chores

    • Added patch entries for affected packages.

@tomaszpacior tomaszpacior linked an issue Feb 27, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3fd8559c-b040-417a-8406-aa0a264a1883

📥 Commits

Reviewing files that changed from the base of the PR and between acedbfd and c6b127a.

📒 Files selected for processing (2)
  • packages/ui/src/components/Carousel/Carousel.tsx
  • packages/ui/src/components/ProductCarousel/ProductCarousel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/ProductCarousel/ProductCarousel.tsx

Walkthrough

Adds a managed keyboard navigation system so only the active Swiper-based carousel handles arrow keys. Introduces a global active-carousel manager, a useManagedCarouselKeyboard hook, prop surface for keyboardControlMode/keyboardCarouselId across Carousel/ProductCarousel/ProductGallery, and enables managed mode in ProductDetails and RecommendedProducts.

Changes

Cohort / File(s) Summary
Changeset
/.changeset/wet-paws-cough.md
Patch entry documenting the keyboard navigation coordination fix for Swiper-based carousels.
Carousel Keyboard Infra
packages/ui/src/components/Carousel/carouselKeyboardManager.ts, packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts
New global active-carousel state manager and a hook that subscribes, toggles Swiper keyboard plugin, exposes activation API, and coordinates enable/disable behavior across carousel instances.
Core Carousel
packages/ui/src/components/Carousel/Carousel.tsx, packages/ui/src/components/Carousel/Carousel.types.ts
Added keyboardControlMode and keyboardCarouselId props, per-instance id generation, integrated useManagedCarouselKeyboard, dynamic keyboardConfig, focus/pointer capture handlers, and lifecycle wiring for managed mode.
ProductGallery
packages/ui/src/components/ProductGallery/ProductGallery.tsx, packages/ui/src/components/ProductGallery/ProductGallery.types.ts
Added keyboardControlMode/keyboardCarouselId props, derived main/lightbox IDs, tracked lightbox swiper state and initial slide, wired managed keyboard hook for main and lightbox, and activation on focus/pointer and lightbox open/close.
ProductCarousel
packages/ui/src/components/ProductCarousel/ProductCarousel.tsx, packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts
Added optional keyboardControlMode and keyboardCarouselId props, generate per-instance id when absent, and forward to underlying Carousel.
Block Integrations
packages/blocks/product-details/src/frontend/ProductDetails.client.tsx, packages/blocks/recommended-products/src/frontend/RecommendedProducts.client.tsx
Set keyboardControlMode="managed" on ProductGallery/ProductCarousel usages to opt those block components into managed keyboard behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Main as Main Gallery<br/>(Carousel)
    participant Manager as Carousel Keyboard<br/>Manager
    participant Lightbox as Lightbox<br/>(Carousel)
    participant SwiperMain as Swiper<br/>Instance (main)
    participant SwiperLight as Swiper<br/>Instance (lightbox)

    User->>Main: focus / pointer interaction
    Main->>Manager: activateManagedKeyboard(mainId)
    Manager->>Manager: setActiveCarouselId(mainId)
    Manager-->>Main: notify active -> enable keyboard
    Manager-->>Lightbox: notify inactive -> disable keyboard

    User->>Main: press ArrowKey
    Main->>SwiperMain: keyboard navigation

    User->>Main: open lightbox
    Lightbox->>Manager: activateManagedKeyboard(lightboxId)
    Manager->>Manager: setActiveCarouselId(lightboxId)
    Manager-->>Main: notify inactive -> disable keyboard
    Manager-->>Lightbox: notify active -> enable keyboard

    User->>Lightbox: press ArrowKey
    Lightbox->>SwiperLight: keyboard navigation

    User->>Lightbox: close
    Lightbox->>Manager: clearActiveCarouselId(lightboxId)
    Manager->>Manager: revert/clear active
    Manager-->>Main: notify active -> enable keyboard
Loading
sequenceDiagram
    participant Component as Carousel<br/>Component
    participant Hook as useManagedCarousel<br/>Keyboard Hook
    participant Manager as Global Carousel<br/>Manager
    participant Swiper as Swiper<br/>Instance

    Component->>Hook: init(managed, id, swiper)
    Hook->>Manager: subscribeActiveCarousel()
    Manager-->>Hook: current active id
    Hook->>Hook: set local enabled state

    Component->>Component: user focus/pointer
    Component->>Hook: activateManagedKeyboard()
    Hook->>Manager: setActiveCarouselId(id)
    Manager->>Manager: notify subscribers

    Manager-->>Hook: active id changed
    Hook->>Swiper: enable or disable keyboard plugin
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • marcinkrasowski

Poem

🐰
I hopped between slides with a curious stare,
Now only one carousel answers the air.
Focus finds home, arrows skip the rest,
Lightboxes sing, the keyboard's at its best. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preventing multiple carousels from responding to arrow keys simultaneously, which directly aligns with the changeset's core objective.
Description check ✅ Passed The PR description covers all required template sections: it clearly explains what the PR does, references a related ticket, details key changes and side effects, and provides comprehensive testing instructions with setup and validation steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 684-bug-incorrect-keyboard-handling-on-product-details-page

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
Copy Markdown
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.

🧹 Nitpick comments (5)
packages/ui/src/components/ProductGallery/ProductGallery.types.ts (1)

18-20: Consider using CarouselProps type references for consistency.

Similar to ProductCarousel.types.ts, these props could reference CarouselProps['...'] to ensure types stay synchronized with the source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/ProductGallery/ProductGallery.types.ts` around
lines 18 - 20, Update the three prop types in ProductGallery.types.ts
(keyboardControlMode, defaultKeyboardActive, keyboardCarouselId) to reference
the canonical CarouselProps types instead of duplicating types; e.g., replace
their standalone types with CarouselProps['keyboardControlMode'],
CarouselProps['defaultKeyboardActive'] and CarouselProps['keyboardCarouselId']
so they stay synchronized with ProductCarousel's source-of-truth definitions.
packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts (1)

17-19: Minor type reference inconsistency.

keyboardControlMode correctly references CarouselProps['keyboardControlMode'], but defaultKeyboardActive and keyboardCarouselId use inline types. Consider using CarouselProps['defaultKeyboardActive'] and CarouselProps['keyboardCarouselId'] for consistency and to ensure types stay in sync.

♻️ Optional: Use consistent type references
     keyboardControlMode?: CarouselProps['keyboardControlMode'];
-    defaultKeyboardActive?: boolean;
-    keyboardCarouselId?: string;
+    defaultKeyboardActive?: CarouselProps['defaultKeyboardActive'];
+    keyboardCarouselId?: CarouselProps['keyboardCarouselId'];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts` around
lines 17 - 19, The three props in ProductCarousel.types are inconsistent: while
keyboardControlMode already uses CarouselProps['keyboardControlMode'], change
defaultKeyboardActive and keyboardCarouselId to reference the corresponding
types on CarouselProps as well (use CarouselProps['defaultKeyboardActive'] and
CarouselProps['keyboardCarouselId']) so all three (keyboardControlMode,
defaultKeyboardActive, keyboardCarouselId) are consistently typed against the
CarouselProps definition.
packages/ui/src/components/Carousel/carouselKeyboardManager.ts (1)

1-35: Consider adding 'use client' directive for SSR safety.

This module maintains singleton state via module-level variables. If accidentally imported in a Server Component context during SSR, the shared state could behave unexpectedly. Given that the consuming hook (useManagedCarouselKeyboard.ts) already has 'use client', this may be fine in practice, but adding the directive here would make the boundary explicit.

The implementation is clean and follows a standard pub/sub pattern.

🛡️ Optional: Add client directive
+`use client`;
+
 type ActiveCarouselListener = (activeCarouselId: string | null) => void;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/Carousel/carouselKeyboardManager.ts` around lines
1 - 35, This module holds module-level singleton state (activeCarouselId,
listeners) and should be explicitly marked as a client module to avoid
accidental SSR import; add the 'use client' directive as the first statement in
carouselKeyboardManager.ts so the module (and its exports: getActiveCarouselId,
setActiveCarouselId, clearActiveCarouselId, subscribeActiveCarousel,
notifyListeners) always runs on the client and won’t be bundled/executed during
server rendering.
packages/ui/src/components/ProductCarousel/ProductCarousel.tsx (1)

91-94: Prevent carouselConfig from unintentionally overriding explicit keyboard props.

With current order, carouselConfig wins over keyboardControlMode, defaultKeyboardActive, and keyboardCarouselId, which can be surprising for callers.

♻️ Proposed refactor
-                keyboardControlMode={keyboardControlMode}
-                defaultKeyboardActive={defaultKeyboardActive}
-                keyboardCarouselId={keyboardCarouselId}
-                {...carouselConfig}
+                {...carouselConfig}
+                keyboardControlMode={keyboardControlMode}
+                defaultKeyboardActive={defaultKeyboardActive}
+                keyboardCarouselId={keyboardCarouselId}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/ProductCarousel/ProductCarousel.tsx` around lines
91 - 94, The current JSX spreads carouselConfig after explicit keyboard props so
its values can unintentionally override keyboardControlMode,
defaultKeyboardActive, and keyboardCarouselId; update ProductCarousel JSX to
spread {...carouselConfig} before passing the explicit props
(keyboardControlMode, defaultKeyboardActive, keyboardCarouselId) so the explicit
prop values win, ensuring callers' keyboard props are not overwritten by
carouselConfig.
packages/ui/src/components/ProductGallery/ProductGallery.tsx (1)

82-87: Use a lightbox-specific keyboard config at Line 229.

Line 229 currently reuses the main gallery keyboardConfig. Wiring the lightbox Swiper to the lightbox hook’s config keeps this path decoupled and less fragile to future hook changes.

♻️ Proposed refactor
-    const { activateManagedKeyboard: activateLightboxKeyboard } = useManagedCarouselKeyboard({
+    const { activateManagedKeyboard: activateLightboxKeyboard, keyboardConfig: lightboxKeyboardConfig } =
+        useManagedCarouselKeyboard({
         keyboardControlMode,
         carouselId: lightboxCarouselId,
         swiper: lightboxMainSwiper,
         shouldEnable: (activeCarouselId) => activeCarouselId === lightboxCarouselId && isLightboxOpen,
-    });
+    });

...
-                            keyboard={keyboardConfig}
+                            keyboard={lightboxKeyboardConfig}

Also applies to: 229-229

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/ProductGallery/ProductGallery.tsx` around lines 82
- 87, The lightbox Swiper is currently wired to the main gallery's
keyboardConfig, making it fragile; instead use the keyboard configuration
returned by the lightbox-specific useManagedCarouselKeyboard hook (the value
tied to activateManagedKeyboard: activateLightboxKeyboard from
useManagedCarouselKeyboard) when wiring the lightbox Swiper (lightboxMainSwiper)
so the lightbox path is decoupled — replace references to the shared
keyboardConfig at the lightbox hook usage (around where lightboxMainSwiper is
configured) with the keyboard config/activation provided by
activateLightboxKeyboard and ensure shouldEnable uses lightboxCarouselId and
isLightboxOpen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/src/components/Carousel/carouselKeyboardManager.ts`:
- Around line 1-35: This module holds module-level singleton state
(activeCarouselId, listeners) and should be explicitly marked as a client module
to avoid accidental SSR import; add the 'use client' directive as the first
statement in carouselKeyboardManager.ts so the module (and its exports:
getActiveCarouselId, setActiveCarouselId, clearActiveCarouselId,
subscribeActiveCarousel, notifyListeners) always runs on the client and won’t be
bundled/executed during server rendering.

In `@packages/ui/src/components/ProductCarousel/ProductCarousel.tsx`:
- Around line 91-94: The current JSX spreads carouselConfig after explicit
keyboard props so its values can unintentionally override keyboardControlMode,
defaultKeyboardActive, and keyboardCarouselId; update ProductCarousel JSX to
spread {...carouselConfig} before passing the explicit props
(keyboardControlMode, defaultKeyboardActive, keyboardCarouselId) so the explicit
prop values win, ensuring callers' keyboard props are not overwritten by
carouselConfig.

In `@packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts`:
- Around line 17-19: The three props in ProductCarousel.types are inconsistent:
while keyboardControlMode already uses CarouselProps['keyboardControlMode'],
change defaultKeyboardActive and keyboardCarouselId to reference the
corresponding types on CarouselProps as well (use
CarouselProps['defaultKeyboardActive'] and CarouselProps['keyboardCarouselId'])
so all three (keyboardControlMode, defaultKeyboardActive, keyboardCarouselId)
are consistently typed against the CarouselProps definition.

In `@packages/ui/src/components/ProductGallery/ProductGallery.tsx`:
- Around line 82-87: The lightbox Swiper is currently wired to the main
gallery's keyboardConfig, making it fragile; instead use the keyboard
configuration returned by the lightbox-specific useManagedCarouselKeyboard hook
(the value tied to activateManagedKeyboard: activateLightboxKeyboard from
useManagedCarouselKeyboard) when wiring the lightbox Swiper (lightboxMainSwiper)
so the lightbox path is decoupled — replace references to the shared
keyboardConfig at the lightbox hook usage (around where lightboxMainSwiper is
configured) with the keyboard config/activation provided by
activateLightboxKeyboard and ensure shouldEnable uses lightboxCarouselId and
isLightboxOpen.

In `@packages/ui/src/components/ProductGallery/ProductGallery.types.ts`:
- Around line 18-20: Update the three prop types in ProductGallery.types.ts
(keyboardControlMode, defaultKeyboardActive, keyboardCarouselId) to reference
the canonical CarouselProps types instead of duplicating types; e.g., replace
their standalone types with CarouselProps['keyboardControlMode'],
CarouselProps['defaultKeyboardActive'] and CarouselProps['keyboardCarouselId']
so they stay synchronized with ProductCarousel's source-of-truth definitions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c390a3 and 26962ac.

📒 Files selected for processing (11)
  • .changeset/wet-paws-cough.md
  • packages/blocks/product-details/src/frontend/ProductDetails.client.tsx
  • packages/blocks/recommended-products/src/frontend/RecommendedProducts.client.tsx
  • packages/ui/src/components/Carousel/Carousel.tsx
  • packages/ui/src/components/Carousel/Carousel.types.ts
  • packages/ui/src/components/Carousel/carouselKeyboardManager.ts
  • packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts
  • packages/ui/src/components/ProductCarousel/ProductCarousel.tsx
  • packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts
  • packages/ui/src/components/ProductGallery/ProductGallery.tsx
  • packages/ui/src/components/ProductGallery/ProductGallery.types.ts

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 27, 2026

Coverage Report for packages/configs/vitest-config

Status Category Percentage Covered / Total
🔵 Lines 80.87% 1192 / 1474
🔵 Statements 80.34% 1267 / 1577
🔵 Functions 78.66% 446 / 567
🔵 Branches 72.13% 1017 / 1410
File CoverageNo changed files found.
Generated in workflow #431 for commit c6b127a by the Vitest Coverage Report Action

Comment thread packages/blocks/product-details/src/frontend/ProductDetails.client.tsx Outdated
Comment thread packages/blocks/recommended-products/src/frontend/RecommendedProducts.client.tsx Outdated
Comment thread .changeset/wet-paws-cough.md Outdated
@tomaszpacior tomaszpacior marked this pull request as draft March 3, 2026 09:17
Remove default keyboard-active behavior so arrow keys stay native
until a carousel is focused or clicked.

Auto-generate keyboardCarouselId in ProductCarousel when missing
to reduce manual prop wiring.

Made-with: Cursor
Copy link
Copy Markdown
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 (2)
packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts (1)

63-72: Consider documenting the enabled: false initial state for managed mode.

When keyboardControlMode === 'managed', this returns enabled: false (since 'managed' !== 'swiper-native'). The keyboard is then toggled via swiper.keyboard.enable()/disable() in the effect above. This is correct but non-obvious—a brief inline comment would help future maintainers understand that managed mode intentionally starts disabled and is controlled imperatively.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts` around
lines 63 - 72, The keyboardConfig computed in useManagedCarouselKeyboard
currently sets enabled: keyboardControlMode === 'swiper-native', which means
managed mode intentionally starts with enabled: false and is toggled later via
swiper.keyboard.enable()/disable(); add a brief inline comment next to the
keyboardConfig (or above the useMemo) explaining that when keyboardControlMode
=== 'managed' the config intentionally initializes as disabled and that the
effect uses swiper.keyboard.enable()/disable() to control it imperatively so
future maintainers understand the rationale.
packages/blocks/product-details/src/frontend/ProductDetails.client.tsx (1)

138-138: Explicit but redundant prop value.

ProductGallery already defaults to keyboardControlMode="managed" (see ProductGallery.tsx line 30). This explicit setting is redundant but documents intent clearly at the call site. Consider removing it for brevity, or keep it for explicit documentation—either approach is valid.

This addresses the past review comment asking about defaults: 'managed' is indeed the default for ProductGallery, so no explicit prop is strictly required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/product-details/src/frontend/ProductDetails.client.tsx` at
line 138, The call to ProductGallery includes the explicit prop
keyboardControlMode="managed" which is redundant because ProductGallery defaults
to 'managed'; remove the explicit keyboardControlMode prop from the
ProductGallery JSX at the call site to clean up the code (or if you prefer to
keep it for clarity, you may leave it—choose one approach and make it consistent
across usages of ProductGallery).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ui/src/components/ProductCarousel/ProductCarousel.tsx`:
- Around line 93-95: The prop spread ordering currently allows carouselConfig to
override the explicit props keyboardControlMode and keyboardCarouselId; update
the JSX where keyboardControlMode and keyboardCarouselId are passed (the
component call using keyboardControlMode={keyboardControlMode}
keyboardCarouselId={resolvedKeyboardCarouselId} {...carouselConfig}) so that
{...carouselConfig} appears before the explicit props, ensuring the explicit
keyboardControlMode and keyboardCarouselId values win when both are provided.

---

Nitpick comments:
In `@packages/blocks/product-details/src/frontend/ProductDetails.client.tsx`:
- Line 138: The call to ProductGallery includes the explicit prop
keyboardControlMode="managed" which is redundant because ProductGallery defaults
to 'managed'; remove the explicit keyboardControlMode prop from the
ProductGallery JSX at the call site to clean up the code (or if you prefer to
keep it for clarity, you may leave it—choose one approach and make it consistent
across usages of ProductGallery).

In `@packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts`:
- Around line 63-72: The keyboardConfig computed in useManagedCarouselKeyboard
currently sets enabled: keyboardControlMode === 'swiper-native', which means
managed mode intentionally starts with enabled: false and is toggled later via
swiper.keyboard.enable()/disable(); add a brief inline comment next to the
keyboardConfig (or above the useMemo) explaining that when keyboardControlMode
=== 'managed' the config intentionally initializes as disabled and that the
effect uses swiper.keyboard.enable()/disable() to control it imperatively so
future maintainers understand the rationale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46d7082a-82b4-48bf-aace-5ed2d2c30fb3

📥 Commits

Reviewing files that changed from the base of the PR and between 26962ac and acedbfd.

📒 Files selected for processing (10)
  • .changeset/wet-paws-cough.md
  • packages/blocks/product-details/src/frontend/ProductDetails.client.tsx
  • packages/blocks/recommended-products/src/frontend/RecommendedProducts.client.tsx
  • packages/ui/src/components/Carousel/Carousel.tsx
  • packages/ui/src/components/Carousel/Carousel.types.ts
  • packages/ui/src/components/Carousel/useManagedCarouselKeyboard.ts
  • packages/ui/src/components/ProductCarousel/ProductCarousel.tsx
  • packages/ui/src/components/ProductCarousel/ProductCarousel.types.ts
  • packages/ui/src/components/ProductGallery/ProductGallery.tsx
  • packages/ui/src/components/ProductGallery/ProductGallery.types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ui/src/components/ProductGallery/ProductGallery.types.ts
  • .changeset/wet-paws-cough.md
  • packages/ui/src/components/Carousel/Carousel.types.ts

Comment thread packages/ui/src/components/ProductCarousel/ProductCarousel.tsx Outdated
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 9, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
o2s-docs Skipped Skipped Mar 9, 2026 11:44am

Request Review

@vercel vercel Bot temporarily deployed to Preview – o2s-docs March 9, 2026 11:44 Inactive
@tomaszpacior tomaszpacior merged commit cc2e932 into main Mar 9, 2026
15 checks passed
@tomaszpacior tomaszpacior deleted the 684-bug-incorrect-keyboard-handling-on-product-details-page branch March 9, 2026 12:15
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] Incorrect keyboard handling on product details page

2 participants