Fix: Wide Alignment + Block Tree Selection#70
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves two significant editor-specific issues within the carousel block: slides failing to scroll completely when using Wide or Full alignment, and unreliable selection of slides from the List View. The solution involves integrating a debounced resize observer to ensure the carousel accurately adapts to layout changes and implementing logic to synchronize the carousel's scroll position with the selected block, while also mitigating conflicts with Gutenberg's native scroll behavior. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively resolves two editor-only bugs in the carousel block related to wide alignment and block tree selection. The solution, which involves a custom debounced ResizeObserver and logic to handle slide selection from the List View, is well-implemented. The refactoring of observer logic into separate hooks is a great improvement for code modularity and maintainability. My review includes suggestions to enhance the robustness of the mutation detection, improve code clarity by removing magic numbers in setTimeout calls, and simplify some logic for better readability.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Pull request overview
Fixes editor-only carousel issues when using Wide/Full alignment and when selecting slides via the List View (block tree), by ensuring Embla re-measures on meaningful width changes and scrolls to the currently selected slide in the editor.
Changes:
- Add debounced
ResizeObserver+ deferredreInit()to keep Embla measurements correct after editor layout/alignment changes. - Subscribe to block-editor selection to scroll Embla to the selected slide (including nested selection), and neutralize Gutenberg’s native
scrollLeft/scrollTopinterference. - Refactor observer logic into
useEmblaResizeObserveranduseEmblaQueryLoopObserverhooks.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/blocks/carousel/viewport/edit.tsx | Adds editor selection-driven scrolling, native scroll reset, deferred reInit(), and wires in new observer hooks. |
| src/blocks/carousel/styles/_core.scss | Adds alignment-related width styling for .alignfull in the shared block stylesheet. |
| src/blocks/carousel/hooks/useEmblaResizeObserver.ts | New hook: debounced width-change observer that triggers Embla reInit(). |
| src/blocks/carousel/hooks/useEmblaQueryLoopObserver.ts | New hook: debounced mutation observer for Query Loop slide-count changes that re-initializes Embla. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect( () => { | ||
| const api = emblaRef.current | ||
| ? ( emblaRef.current as { [ EMBLA_KEY ]?: EmblaCarouselType } )[ EMBLA_KEY ] | ||
| : null; | ||
| if ( api ) { | ||
| setTimeout( () => api.reInit(), 10 ); | ||
| if ( emblaApiRef.current ) { | ||
| // Defer until after React's commit phase so the new slide DOM is ready. | ||
| setTimeout( () => emblaApiRef.current?.reInit(), 0 ); | ||
| } | ||
| }, [ slideCount ] ); |
There was a problem hiding this comment.
The setTimeout used to call reInit() on slide count changes isn’t cleared on unmount or when slideCount changes again. That can fire after Embla has been destroyed and cause a reInit on a stale instance. Store the timeout id and clearTimeout it in the effect cleanup (and/or switch to queueMicrotask/requestAnimationFrame if the goal is just post-commit deferral).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…te updates during unmount
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const { insertBlock } = useDispatch( 'core/block-editor' ); | ||
|
|
||
| useEmblaResizeObserver( viewportEl, emblaApiRef ); | ||
| useEmblaQueryLoopObserver( viewportEl, initEmblaRef ); |
There was a problem hiding this comment.
useEmblaResizeObserver calls emblaRef.current.reInit() (non-destructive), while useEmblaQueryLoopObserver calls initEmblaRef.current() (full recreate). The two hooks use fundamentally different reinit strategies with no explanation in useEmblaResizeObserver of why a non-destructive reInit is sufficient there but not for Query Loop. If the answer is that Query Loop changes the container DOM structure (requiring a destroy/recreate), that is not commented, making the design choice invisible to future maintainers.
There was a problem hiding this comment.
@sanketio, I have made the following changes in my recent commits and added the documentation as needed.
- I refactored
useEmblaResizeObserverto handle both viewport resizes (alignment changes) and slide resizes (column style changes). Previously, it only watched the viewport, which is why column changes weren't triggering a reInit. Now it observes both the viewport AND the first slide element, using a WeakMap to track widths per-element. When any observed element changes by more than 1px, it triggers reInit.
- I've added clarifying JSDoc to both hooks, explaining the different strategies:
useEmblaResizeObserver: Uses reInit() because resize only affects measurements and scroll positions — the DOM structure (container + slides) remains unchanged, so Embla's cached element references stay valid.
useEmblaQueryLoopObserver: Uses full destroy/recreate because Query Loop changes can replace the .wp-block-post-template element or swap out its children entirely. Embla caches references to the container and slide elements on init, so when those DOM nodes are replaced, a fresh instance is required.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| debounceTimer = setTimeout( syncIfChanged, QUERY_LOOP_DEBOUNCE_MS ); | ||
| } ); | ||
|
|
||
| mutationObserver.observe( viewportEl, { |
There was a problem hiding this comment.
useEmblaResizeObserver and useEmblaQueryLoopObserver both independently create a MutationObserver on viewportEl with { childList: true, subtree: true }. Every single Gutenberg DOM mutation — including typing inside a block or toolbar UI updates — fires both observers simultaneously. The two then independently start their own debounce timers (50ms and 150ms respectively).
In the specific scenario where Query Loop loads new posts:
useEmblaResizeObserver's MutationObserver fires → 50ms debounce →observeFirstSlide()→ attachesResizeObserverto new first slide.useEmblaQueryLoopObserver's MutationObserver fires → 150ms debounce →init()(full destroy/recreate of Embla).- The ResizeObserver attached in step 1 now fires because
init()replaces the component tree → triggersreInit()200ms later on the freshly-created instance.
The result is three carousel initializations cascading from one user action. These should be coordinated through a single shared MutationObserver, if possible.
There was a problem hiding this comment.
Thanks for the detailed breakdown — the cascade scenario made the bug really clear.
The core issue was that two separate MutationObserver instances were watching the same element and racing against each other. The 50ms vs 150ms debounce gap was essentially a ticking clock that guaranteed the ResizeObserver would attach to the new slide just before the full init replaced it, leaving a stray reInit() queued on a fresh instance.
I've merged useEmblaResizeObserver and useEmblaQueryLoopObserver into a single useCarouselObservers hook. Here's how the coordination works:
- One
MutationObserverdrives everything. Slide observation updates and Query Loop detection both happen inside a singleprocessMutationscall behind a 150ms debounce, so there's no more race between two independent handlers. fullInitPendingflag is set before callinginitEmblaRef.current()and held open for the fullRESIZE_DEBOUNCE_MSwindow. AnyResizeObservercallback that fires during the init DOM churn hits an early return and is suppressed.resizeTimeris reused for the suppression window, so the existing cleanup in thereturncovers it automatically with no extra bookkeeping.
Your three-step cascade is no longer possible — there's only one observer reacting to DOM mutations, and a full init explicitly locks out any resize-triggered reInit() for its entire duration.
…r improved carousel init
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…erver in useCarouselObservers
…nt stale references
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| if ( changed && currentCount === 0 ) { | ||
| // Template removed or emptied — destroy to avoid stale references. | ||
| emblaRef.current?.destroy(); | ||
| emblaRef.current = undefined; | ||
| return false; | ||
| } |
There was a problem hiding this comment.
When the Query Loop becomes empty (currentCount === 0), this hook destroys emblaRef.current and sets it to undefined, but it doesn’t also clear the Embla instance stored on the viewport element (via Symbol.for('carousel-system.carousel')) or update editor state (e.g., canScrollPrev/Next, context emblaApi). That can leave the controls reading a destroyed/stale instance and keep nav state enabled even though there are no slides. Consider centralizing Embla teardown in the viewport init effect (so it can also delete the DOM key and reset nav/context state), or pass callbacks/viewport cleanup helpers into the hook so the destroy path fully cleans up all references/state.
* refactor: update changelog script to use npx and remove conventional-changelog-cli dependency - Changed the changelog script in package.json to use npx for conventional-changelog-cli. - Removed conventional-changelog-cli from dependencies in package-lock.json. - Cleaned up unnecessary dependencies related to conventional changelog generation. * chore: update CHANGELOG for version 1.0.1 with bug fixes and new features * chore: remove unused dependency scrivo/highlight.php from composer files * feat: add .distignore file to exclude unnecessary files from WordPress.org distribution * feat: add localization support and update package.json for repository and bugs metadata * feat: add readme.txt with plugin details and installation instructions * feat: update license and version in carousel-kit.php; add index.php for plugin structure * feat: add screenshot for plugin preview in WordPress.org * feat: update .distignore to refine exclusions for WordPress.org distribution * fix(styles): remove unnecessary grid-template-columns property * refactor(styles): improve transition effects and clean up unused properties * fix: CSS linting issues * refactor: Remove unrelated changes from current PR * feat: add placeholder logos and update hero carousel pattern * feat: update .distignore to exclude README.md and enhance carousel-kit.php for direct access protection * feat: Updated pot file * feat: update PHP requirement to 8.2, bump stable tag to 1.0.3, and add changelog entries for fixes * chore: format package.json for consistency in spacing * chore: remove upgrade notice section from readme.txt * chore: remove version field from package.json * feat: update PHP requirement to 8.2 and reorder contributors in plugin header * chore: remove version field from package-lock.json * docs: add link to full changelog in readme.txt * chore: remove version field from package-lock.json * fix: update file paths in phpcs and phpstan configuration * feat: implement autoloader for PHP classes and refactor constant definitions * fix: ignore phpcs warning for including pattern file from a fixed directory * fix: update script paths in composer.json and improve constant definitions in tests * fix: update POT-Creation-Date and add missing autoloader error message * chore: update @wordpress/scripts to version 31.5.0 * feat: add new screenshot image and remove outdated PNG file * fix: clean up .distignore by removing unnecessary files and directories * fix: remove outdated screenshots description from readme * fix: update dist target to exclude additional configuration and development files * fix: ensure direct access to Autoloader.php exits gracefully * fix: update package dependencies for minimatch and serialize-javascript * fix: update script paths in composer.json to use local binaries * feat: update README with minimum requirements and add uninstall functionality * fix: update WordPress minimum requirement to 6.6 in README, INSTALLATION, and readme.txt * fix: update contributors list in plugin header and readme.txt * fix: update package-lock.json to remove unused dependency and upgrade svgo and immutable versions * fix: update tested up to version in readme.txt from 6.9.1 to 6.9 * fix: correct release link in INSTALLATION.md and update node_modules exclusion in phpstan.neon.dist * fix: update @wordpress/scripts version to allow minor updates * chore(release): Prepare v1.0.4 release (#76) * chore: update version to 1.0.4 and enhance readme with changelog details * fix: update stable tag in readme.txt to 1.0.4 * fix: update changelog links to point to the correct repository * fix: update contributors list in README and readme.txt * docs: update Composer installation instructions in INSTALLATION.md * docs: fix formatting in Composer installation instructions in INSTALLATION.md * fix: correct release link format in CHANGELOG.md for version 1.0.1 * fix: remove hardcoded plugin version from Makefile and update installation instructions in INSTALLATION.md * docs: update note on WPackagist availability in INSTALLATION.md Co-authored-by: Utsav Patel <75293077+up1512001@users.noreply.github.com> * Fix: Wide Alignment + Block Tree Selection (#70) * fix: resolve slide translation and Block Tree selection issues with wide alignment * feat: add hooks for observing DOM mutations and resize events in carousel * fix: adjust full and wide alignment styles for carousel container * fix: clarify comment on full alignment breaking out of container * fix: refactor slide count change detection logic in useEmblaQueryLoopObserver * fix: improve slide count detection and optimize viewport scroll handling * fix: add BlockEditorSelectors interface for improved type safety in edit component * fix: ensure viewportEl is set only when node is not null to avoid state updates during unmount * fix: add box-sizing property to ensure no horizontal scroll bar in editor * fix: improve resize observer logic to prevent unnecessary reinitializations * fix: refactor viewportEl state management to prevent unnecessary reinitializations * fix: update documentation for Embla observers to clarify initialization logic * fix: enhance resize observer to track column size changes * fix: improve first slide observation logic to prevent unnecessary re-observations * fix: consolidate resize and mutation observers into a unified hook for improved carousel init * fix: update comment to reflect change from manual debounced ResizeObserver in useCarouselObservers * fix: handle empty template case by destroying Embla instance to prevent stale references Co-authored-by: Aviral Mittal aviral.ideabox@gmail.com Co-authored-by: Vishal Kotak vishalkotak200@gmail.com Co-authored-by: Aishwarrya Pande <169024277+AishwarryaPande@users.noreply.github.com> Co-authored-by: Raj Patel <71687258+imrraaj@users.noreply.github.com> Co-authored-by: Sanket Parmar <7807348+sanketio@users.noreply.github.com> * fix: add direct access protection to example pattern files (#84) Co-authored-by: rtCamp <1128395+rtCamp@users.noreply.github.com> Co-authored-by: Gagan Deep Singh <1535505+gagan0123@users.noreply.github.com> Co-authored-by: Sagar Tamang <8264719+mi5t4n@users.noreply.github.com> Co-authored-by: Aviral Mittal <68884287+aviral-mittal@users.noreply.github.com> Co-authored-by: Vishal Kotak <34092861+vishal4669@users.noreply.github.com> * feat: add new asset images and replace old screenshot (#86) --------- Co-authored-by: Utsav Patel <75293077+up1512001@users.noreply.github.com> Co-authored-by: Aishwarrya Pande <169024277+AishwarryaPande@users.noreply.github.com> Co-authored-by: Raj Patel <71687258+imrraaj@users.noreply.github.com> Co-authored-by: Sanket Parmar <7807348+sanketio@users.noreply.github.com> Co-authored-by: rtCamp <1128395+rtCamp@users.noreply.github.com> Co-authored-by: Gagan Deep Singh <1535505+gagan0123@users.noreply.github.com> Co-authored-by: Sagar Tamang <8264719+mi5t4n@users.noreply.github.com> Co-authored-by: Aviral Mittal <68884287+aviral-mittal@users.noreply.github.com> Co-authored-by: Vishal Kotak <34092861+vishal4669@users.noreply.github.com>
Bug Fix: Wide Alignment + Block Tree Selection
The Problem
Two issues showed up in the block editor when using the carousel with Wide or Full alignment:
Both worked perfectly on the frontend — this was editor-only.
Root Cause
For the scroll issue: Embla measures the viewport width once at startup to calculate how far to translate slides. We had disabled its built-in resize watcher (
watchResize: false) because Gutenberg's block toolbar popping in/out would cause tiny layout shifts that triggered unwanted recalculations. But that also meant Embla never re-measured when the width actually changed (like switching to Wide alignment), so its math was wrong.For the List View issue: There was simply no code to tell Embla "scroll to slide 3" when you clicked Slide 3 in the List View. On top of that, Gutenberg's own
scrollIntoView()was setting a nativescrollLefton the viewport, which broke Embla's transform-based positioning.The Fix
ResizeObserverthat only fires when the width changes by more than 1px. This ignores the tiny toolbar shifts but catches real layout changes.scrollLeftthat Gutenberg'sscrollIntoViewsets.useEmblaResizeObserveranduseEmblaQueryLoopObserverinto a singleuseCarouselObservershook. The two hooks were independently creating aMutationObserveron the same element, which caused a three-step cascade when Query Loop loaded new posts — aResizeObserverwould attach to the new slide just before a full init replaced it, leaving a strayreInit()queued on the fresh instance. The consolidated hook uses oneMutationObserver, afullInitPendingflag to suppress resize callbacks during init DOM churn, and a coordinated debounce to prevent the cascade entirely.Why This Is Safe
watchResizeis still handled..emblaviewport div only — it can't affect page scroll or other elements.Issue: #69