Skip to content

Preserves scroll position on focused section when "show only" checkbox is checked#4071

Open
FadhlanR wants to merge 1 commit intomainfrom
cs-10260-persist-scroll-position-when-show-only-checkbox-is-clicked
Open

Preserves scroll position on focused section when "show only" checkbox is checked#4071
FadhlanR wants to merge 1 commit intomainfrom
cs-10260-persist-scroll-position-when-show-only-checkbox-is-clicked

Conversation

@FadhlanR
Copy link
Contributor

Screen.Recording.2026-02-26.at.15.45.56.mov

@github-actions
Copy link

Preview deployments

@github-actions
Copy link

Host Test Results

    1 files  ±0      1 suites  ±0   1h 35m 14s ⏱️ + 1m 24s
1 880 tests +1  1 865 ✅ +1  15 💤 ±0  0 ❌ ±0 
1 895 runs  +1  1 880 ✅ +1  15 💤 ±0  0 ❌ ±0 

Results for commit 7567946. ± Comparison against base commit 61ceac3.

@FadhlanR FadhlanR marked this pull request as ready for review February 26, 2026 09:41
@FadhlanR FadhlanR requested a review from a team February 26, 2026 09:41
@jurgenwerk jurgenwerk requested a review from Copilot February 26, 2026 10:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a feature to preserve the scroll position of a focused section when the "Show only" checkbox is toggled in the card search interface. When users check/uncheck "Show only", sections expand or collapse, which would normally cause the focused section to shift on screen. This feature maintains the visual position of the focused section during these DOM changes.

Changes:

  • Added a new ScrollAnchor modifier that monitors DOM mutations and adjusts scroll position to keep an anchor element in place
  • Integrated the modifier into the search-content component with dynamic anchor selection based on focused section
  • Added comprehensive test coverage for the scroll preservation behavior

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/host/app/modifiers/scroll-anchor.ts New modifier that tracks element positions and adjusts scroll to preserve anchor position during DOM mutations
packages/host/app/components/card-search/search-content.gts Integrates ScrollAnchor modifier, adds data-section-sid attributes, and manages scrollAnchorSid state
packages/host/tests/integration/components/operator-mode-card-catalog-test.gts Adds test to verify scroll position preservation when toggling "Show only" checkbox

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +51
this.#element = element;
this.#anchorSelector = anchorSelector ?? null;
this.#trackSelector = trackSelector ?? null;

if (!this.#observer) {
this.#scrollHandler = this.capturePositions.bind(this);
element.addEventListener('scroll', this.#scrollHandler, {
passive: true,
});

this.#observer = new MutationObserver(this.handleMutations.bind(this));
this.#observer.observe(element, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class'],
});

this.capturePositions();
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

When the anchorSelector or trackSelector arguments change on a subsequent modify() call, the positions are not immediately recaptured. The modifier only calls capturePositions() during initial setup (line 50). If these selectors change dynamically (which they do based on scrollAnchorSid in search-content.gts), the position map may not include the new anchor element until the next scroll event or mutation. Consider calling capturePositions() after updating the selectors if they have changed.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

I can’t say I understand this, does it seem like a problem?

Comment on lines +906 to +908
scrollContainer.scrollTop = 50;
scrollContainer.dispatchEvent(new Event('scroll'));
await settled();
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The test manually dispatches a scroll event and then immediately awaits settled(). However, the scroll event listener is registered with the passive option, meaning it won't block. The test might not give enough time for the capturePositions callback to execute before checking positions. Consider waiting for a microtask or animation frame after dispatching the scroll event, or verify that settled() properly waits for all pending microtasks.

Copilot uses AI. Check for mistakes.
);

// Uncheck: sections expand, position should still be preserved
scrollContainer.dispatchEvent(new Event('scroll'));
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

This scroll event dispatch appears unnecessary at this point in the test. The event is dispatched after unchecking the show-only checkbox but before capturing the "before" position for the second assertion. If the intent is to ensure positions are captured, this should happen before the first click at line 912, not here. Consider removing this line or clarifying its purpose with a comment.

Suggested change
scrollContainer.dispatchEvent(new Event('scroll'));

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@backspace backspace left a comment

Choose a reason for hiding this comment

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

I tried this in the preview deployment, works as expected. There‘s still some jumping when I’ve scrolled to an extreme and there aren’t that many realms collapsing, but that seems unavoidable to me.

Image

Comment on lines +32 to +51
this.#element = element;
this.#anchorSelector = anchorSelector ?? null;
this.#trackSelector = trackSelector ?? null;

if (!this.#observer) {
this.#scrollHandler = this.capturePositions.bind(this);
element.addEventListener('scroll', this.#scrollHandler, {
passive: true,
});

this.#observer = new MutationObserver(this.handleMutations.bind(this));
this.#observer.observe(element, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class'],
});

this.capturePositions();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I can’t say I understand this, does it seem like a problem?

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.

3 participants