Skip to content

feat(Menu): improve scrollIntoView performance#7423

Merged
ArgoZhang merged 5 commits intomainfrom
feat-menu
Dec 26, 2025
Merged

feat(Menu): improve scrollIntoView performance#7423
ArgoZhang merged 5 commits intomainfrom
feat-menu

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 26, 2025

Link issues

fixes #7422

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Improve menu and layout scrolling behavior and cleanup related JavaScript data and event handling.

Enhancements:

  • Adjust menu scroll-into-view logic to poll for active links and scroll them into view with limited retries, and ensure associated timers are cleared on disposal.
  • Simplify NavMenu JavaScript by storing navbar and menu elements directly and cleaning up event handlers using destructured references.
  • Refine URL processing in the Menu component by using character literals for query and hash delimiters.
  • Tweak anchor scrolling logic to delay smooth scrolling until after a short timeout to improve reliability.

Copilot AI review requested due to automatic review settings December 26, 2025 02:35
@bb-auto bb-auto Bot added the enhancement New feature or request label Dec 26, 2025
@bb-auto bb-auto Bot added this to the v10.1.0 milestone Dec 26, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 26, 2025

Reviewer's Guide

Refactors menu and navigation JavaScript to store richer state objects, introduces an interval-based scrollIntoView mechanism that retries briefly until active links exist to improve reliability/performance, cleans up event handler registration/disposal, and makes minor code-style fixes in C# and anchor scrolling logic.

Sequence diagram for the new interval-based menu scrollIntoView behavior

sequenceDiagram
    participant BlazorMenu as BlazorMenuComponent
    participant MenuJs as Menu_razor_js
    participant Data as DataModule
    participant DOM as Document
    participant Timer as IntervalTimer

    BlazorMenu->>MenuJs: init(id)
    MenuJs->>DOM: querySelector(#id)
    DOM-->>MenuJs: element
    MenuJs->>Data: set(id, menu)
    MenuJs->>MenuJs: scrollElementToView(menu)

    rect rgb(230,230,250)
        MenuJs->>Timer: setInterval(callback, 100)
        loop up to 3 attempts
            Timer->>DOM: querySelectorAll(.nav-link.active)
            alt active links found
                DOM-->>Timer: links
                Timer->>MenuJs: callback(links)
                MenuJs->>Timer: clearInterval(handler)
                MenuJs->>DOM: link.scrollIntoView(smooth, center)
            else no active links
                DOM-->>Timer: []
                Timer->>Timer: increment count
            end
        end
    end

    BlazorMenu->>MenuJs: update(id)
    MenuJs->>Data: get(id)
    Data-->>MenuJs: menu
    MenuJs->>DOM: update collapses
    MenuJs->>MenuJs: scrollElementToView(menu)

    BlazorMenu->>MenuJs: scrollToView(id)
    MenuJs->>Data: get(id)
    Data-->>MenuJs: menu
    MenuJs->>MenuJs: scrollElementToView(menu)

    BlazorMenu->>MenuJs: dispose(id)
    MenuJs->>Data: get(id)
    Data-->>MenuJs: menu
    MenuJs->>Data: remove(id)
    alt menu.handler exists
        MenuJs->>Timer: clearInterval(menu.handler)
        MenuJs->>MenuJs: menu.handler = null
    end
    MenuJs->>DOM: remove collapse event listeners
Loading

Sequence diagram for NavMenu state storage and event handler cleanup

sequenceDiagram
    participant Layout as ComponentLayout
    participant NavMenuJs as NavMenu_razor_js
    participant Data as DataModule
    participant EventHandler as EventHandlerModule
    participant DOM as Document

    Layout->>NavMenuJs: init(id)
    NavMenuJs->>DOM: querySelector(.navbar-toggler)
    DOM-->>NavMenuJs: navbar
    NavMenuJs->>DOM: querySelector(.sidebar-content)
    DOM-->>NavMenuJs: menu
    NavMenuJs->>Data: set(id, { navbar, menu })

    NavMenuJs->>EventHandler: on(navbar, click, handler)
    NavMenuJs->>EventHandler: on(menu, click, .nav-link, handler)

    actor User
    User->>navbar: click
    EventHandler-->>navmenu: delegated click
    navmenu->>menu: toggle(show)

    User->>menu: click .nav-link
    EventHandler-->>navlink: delegated click
    navlink->>navlink: read href
    alt href != #
        navlink->>menu: remove(show)
    end

    Layout->>NavMenuJs: dispose(id)
    NavMenuJs->>Data: get(id)
    Data-->>NavMenuJs: { navbar, menu }
    NavMenuJs->>Data: remove(id)
    NavMenuJs->>EventHandler: off(navbar, click)
    NavMenuJs->>EventHandler: off(menu, click, .nav-link)
Loading

Class diagram for Menu and NavMenu JavaScript state objects

classDiagram
    class MenuModule {
        +init(id)
        +update(id)
        +scrollToView(id)
        +dispose(id)
        -scrollElementToView(menu)
    }

    class MenuState {
        +element
        +collapses
        +handler
    }

    class NavMenuModule {
        +init(id)
        +dispose(id)
    }

    class NavMenuState {
        +navbar
        +menu
    }

    class DataModule {
        +set(id, value)
        +get(id)
        +remove(id)
    }

    MenuModule --> MenuState : stores
    NavMenuModule --> NavMenuState : stores
    DataModule --> MenuState : values
    DataModule --> NavMenuState : values

    class ComponentLayoutModule {
        +scrollToAnchor()
    }

    class DOM

    MenuModule ..> DataModule : uses
    NavMenuModule ..> DataModule : uses
    NavMenuModule ..> NavMenuState : destructures
    ComponentLayoutModule ..> DOM : queries elements
Loading

File-Level Changes

Change Details Files
Change Menu scrollIntoView behavior to use a short-lived retry interval tied to the menu instance and clean it up on dispose.
  • Modify scrollElementToView to accept the full menu object instead of just the DOM element so it can track a handler reference.
  • Within scrollElementToView, gate behavior on the data-bb-scroll-view attribute as before but use setInterval to look for .nav-link.active elements up to a small retry count before invoking scrollIntoView.
  • Store the interval id on menu.handler and clear it (and null it out) when an active link is found or when the menu is disposed.
  • Update init, update, and scrollToView to call scrollElementToView(menu) instead of passing menu.element.
src/BootstrapBlazor/Components/Menu/Menu.razor.js
Tighten lifecycle management for NavMenu event handlers and simplify stored state.
  • Change init to store a plain object with navbar and menu references instead of a named navmenu object.
  • Register click handlers directly on navbar and menu variables while toggling/removing the 'show' class on the sidebar content as before.
  • In dispose, destructure navbar and menu from the stored data object, remove the entry from Data, and then detach click handlers using EventHandler.off.
src/BootstrapBlazor.Server/Components/Layout/NavMenu.razor.js
Apply small correctness and style improvements in C# URL parsing and anchor scrolling.
  • In Menu.razor.cs GetUrl, use character literals in IndexOf to trim query string and hash fragments more consistently.
  • In ComponentLayout.razor.js scrollToAnchor, move clearTimeout before scrollIntoView and adjust ordering for slightly clearer timeout cleanup semantics.
src/BootstrapBlazor/Components/Menu/Menu.razor.cs
src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor.js

Assessment against linked issues

Issue Objective Addressed Explanation
#7422 Modify the Menu component's scrollIntoView logic to improve performance (e.g., avoid unnecessary or mistimed scroll operations).
#7422 Ensure any timers or handlers introduced for improved scrollIntoView behavior are properly cleaned up to avoid performance degradation or leaks.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArgoZhang ArgoZhang merged commit ec559ab into main Dec 26, 2025
7 of 9 checks passed
@ArgoZhang ArgoZhang deleted the feat-menu branch December 26, 2025 02:35
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In scrollElementToView, the retry interval is only cleared when an active link is found; if no .nav-link.active appears within the retry window the interval will run indefinitely, so consider clearing the interval once the retry limit is reached or before assigning a new handler.
  • scrollElementToView now assigns menu.handler without clearing any existing interval first; if init/update/scrollToView are called multiple times, this can leave orphaned intervals running, so ensure any existing handler is cleared before creating a new one.
  • In NavMenu.dispose, data is used without a null/undefined check; adding a guard when Data.get(id) returns nothing will prevent runtime errors on double-dispose or mismatched IDs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `scrollElementToView`, the retry interval is only cleared when an active link is found; if no `.nav-link.active` appears within the retry window the interval will run indefinitely, so consider clearing the interval once the retry limit is reached or before assigning a new handler.
- `scrollElementToView` now assigns `menu.handler` without clearing any existing interval first; if `init/update/scrollToView` are called multiple times, this can leave orphaned intervals running, so ensure any existing handler is cleared before creating a new one.
- In `NavMenu.dispose`, `data` is used without a null/undefined check; adding a guard when `Data.get(id)` returns nothing will prevent runtime errors on double-dispose or mismatched IDs.

## Individual Comments

### Comment 1
<location> `src/BootstrapBlazor/Components/Menu/Menu.razor.js:110-119` </location>
<code_context>
-            link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
-        }
+        let count = 0;
+        menu.handler = setInterval(() => {
+            if (count < 3) {
+                var links = [...element.querySelectorAll('.nav-link.active')]
+                if (links.length > 0) {
+                    clearInterval(menu.handler);
+                    menu.handler = null;
+
+                    var link = links.pop()
+                    link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
+                }
+            }
+            count++;
+        }, 100);
     }
</code_context>

<issue_to_address>
**issue (bug_risk):** Interval is never cleared when no active links are found, causing a runaway timer.

Because the interval is only cleared when `.nav-link.active` exists, if none ever appear the timer runs indefinitely and `count` grows unbounded. Add a guard to clear the interval once `count` reaches the max number of attempts (e.g., `count >= 3`) to prevent a perpetual timer and wasted work.
</issue_to_address>

### Comment 2
<location> `src/BootstrapBlazor/Components/Menu/Menu.razor.js:107-110` </location>
<code_context>
 }

-const scrollElementToView = element => {
+const scrollElementToView = menu => {
+    const { element } = menu;
     const scroll = element.hasAttribute('data-bb-scroll-view')
     if (scroll) {
-        var links = [...element.querySelectorAll('.nav-link.active')]
-        if (links.length > 0) {
-            var link = links.pop()
-            link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
-        }
+        let count = 0;
+        menu.handler = setInterval(() => {
+            if (count < 3) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Repeated calls to `scrollElementToView` can create multiple active intervals on the same menu instance.

If `scrollElementToView(menu)` is invoked again before a previous interval completes, the new call overwrites `menu.handler` but the earlier intervals keep running via their closures. Consider clearing any existing interval before starting a new one (e.g. `if (menu.handler) clearInterval(menu.handler);`) so only a single timer runs per menu instance.

```suggestion
    const scroll = element.hasAttribute('data-bb-scroll-view')
    if (scroll) {
        let count = 0;

        if (menu.handler) {
            clearInterval(menu.handler);
            menu.handler = null;
        }

        menu.handler = setInterval(() => {
```
</issue_to_address>

### Comment 3
<location> `src/BootstrapBlazor/Components/Menu/Menu.razor.js:100` </location>
<code_context>
     })
 }

-const scrollElementToView = element => {
+const scrollElementToView = menu => {
+    const { element } = menu;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the scroll retry logic into an element-based helper with its own timer management instead of storing timer state on the menu object.

You can keep the new “wait for `.nav-link.active`” behavior while reducing coupling and side effects by:

* Making `scrollElementToView` operate on the element again.
* Managing the retry state and timer locally (or in a small helper) instead of mutating `menu`.
* Centralizing timer cleanup in one place.

One concrete option:

```js
// module-scope map to track timers per menu element
const scrollTimers = new Map();

const clearScrollTimer = (element) => {
  const handle = scrollTimers.get(element);
  if (handle) {
    clearTimeout(handle);
    scrollTimers.delete(element);
  }
};

const scrollElementToView = (element) => {
  if (!element.hasAttribute('data-bb-scroll-view')) {
    return;
  }

  clearScrollTimer(element); // avoid multiple concurrent timers

  let count = 0;
  const tryScroll = () => {
    const links = [...element.querySelectorAll('.nav-link.active')];
    if (links.length > 0) {
      scrollTimers.delete(element);
      const link = links[links.length - 1];
      link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
      return;
    }

    if (++count < 3) {
      const handle = setTimeout(tryScroll, 100);
      scrollTimers.set(element, handle);
    } else {
      scrollTimers.delete(element);
    }
  };

  const handle = setTimeout(tryScroll, 100);
  scrollTimers.set(element, handle);
};
```

Call sites stay simple and decoupled from timer state:

```js
// init/update/scrollToView
scrollElementToView(menu.element);
```

And `dispose` only needs to clear the timer for this element (no `menu.handler` property):

```js
export function dispose(id) {
  const menu = Data.get(id);
  Data.remove(id);

  clearScrollTimer(menu.element);

  menu.collapses.forEach(el => {
    const target = getTargetElement(el);
    const collapse = bootstrap.Collapse.getInstance(target);
    if (collapse !== null) {
      menu.disposeCollapse(collapse, el);
    }
  });
}
```

This preserves the retry behavior and cleanup while:

* Removing the `handler` field from `menu`.
* Keeping `scrollElementToView` element-focused and side-effect-limited.
* Localizing timer logic and cleanup to a small helper, which is easier to reason about.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +110 to +119
menu.handler = setInterval(() => {
if (count < 3) {
var links = [...element.querySelectorAll('.nav-link.active')]
if (links.length > 0) {
clearInterval(menu.handler);
menu.handler = null;

var link = links.pop()
link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Interval is never cleared when no active links are found, causing a runaway timer.

Because the interval is only cleared when .nav-link.active exists, if none ever appear the timer runs indefinitely and count grows unbounded. Add a guard to clear the interval once count reaches the max number of attempts (e.g., count >= 3) to prevent a perpetual timer and wasted work.

})
}

const scrollElementToView = element => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring the scroll retry logic into an element-based helper with its own timer management instead of storing timer state on the menu object.

You can keep the new “wait for .nav-link.active” behavior while reducing coupling and side effects by:

  • Making scrollElementToView operate on the element again.
  • Managing the retry state and timer locally (or in a small helper) instead of mutating menu.
  • Centralizing timer cleanup in one place.

One concrete option:

// module-scope map to track timers per menu element
const scrollTimers = new Map();

const clearScrollTimer = (element) => {
  const handle = scrollTimers.get(element);
  if (handle) {
    clearTimeout(handle);
    scrollTimers.delete(element);
  }
};

const scrollElementToView = (element) => {
  if (!element.hasAttribute('data-bb-scroll-view')) {
    return;
  }

  clearScrollTimer(element); // avoid multiple concurrent timers

  let count = 0;
  const tryScroll = () => {
    const links = [...element.querySelectorAll('.nav-link.active')];
    if (links.length > 0) {
      scrollTimers.delete(element);
      const link = links[links.length - 1];
      link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
      return;
    }

    if (++count < 3) {
      const handle = setTimeout(tryScroll, 100);
      scrollTimers.set(element, handle);
    } else {
      scrollTimers.delete(element);
    }
  };

  const handle = setTimeout(tryScroll, 100);
  scrollTimers.set(element, handle);
};

Call sites stay simple and decoupled from timer state:

// init/update/scrollToView
scrollElementToView(menu.element);

And dispose only needs to clear the timer for this element (no menu.handler property):

export function dispose(id) {
  const menu = Data.get(id);
  Data.remove(id);

  clearScrollTimer(menu.element);

  menu.collapses.forEach(el => {
    const target = getTargetElement(el);
    const collapse = bootstrap.Collapse.getInstance(target);
    if (collapse !== null) {
      menu.disposeCollapse(collapse, el);
    }
  });
}

This preserves the retry behavior and cleanup while:

  • Removing the handler field from menu.
  • Keeping scrollElementToView element-focused and side-effect-limited.
  • Localizing timer logic and cleanup to a small helper, which is easier to reason about.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b852288) to head (925a92b).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7423   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          748       748           
  Lines        32798     32798           
  Branches      4551      4551           
=========================================
  Hits         32798     32798           
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
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 improves the scrollIntoView performance for the Menu component by implementing a polling mechanism to wait for active links to be rendered before attempting to scroll them into view. The implementation addresses timing issues where the scroll operation was attempted before elements were fully available in the DOM.

Key changes:

  • Implemented a retry mechanism with setInterval to poll for active menu links (up to 3 attempts at 100ms intervals)
  • Added proper cleanup of interval handlers in the dispose method
  • Refactored code for better maintainability with destructuring and improved variable scoping

Reviewed changes

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

File Description
src/BootstrapBlazor/Components/Menu/Menu.razor.js Added polling mechanism to wait for active links before scrolling; added interval cleanup in dispose method
src/BootstrapBlazor/Components/Menu/Menu.razor.cs Changed string literals from double quotes to single quotes for consistency
src/BootstrapBlazor.Server/Components/Layout/NavMenu.razor.js Refactored to use destructuring and direct variable references for cleaner code
src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor.js Reordered clearTimeout call relative to scrollIntoView operation

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

Comment on lines +110 to +122
menu.handler = setInterval(() => {
if (count < 3) {
var links = [...element.querySelectorAll('.nav-link.active')]
if (links.length > 0) {
clearInterval(menu.handler);
menu.handler = null;

var link = links.pop()
link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
}
count++;
}, 100);
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

If scrollElementToView is called multiple times before the active links are found (e.g., during rapid updates), multiple intervals will be created and stored in menu.handler, but only the last one will be tracked. Previous intervals will become orphaned and continue running indefinitely. Consider clearing any existing handler before creating a new one.

Suggested change
menu.handler = setInterval(() => {
if (count < 3) {
var links = [...element.querySelectorAll('.nav-link.active')]
if (links.length > 0) {
clearInterval(menu.handler);
menu.handler = null;
var link = links.pop()
link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
}
count++;
}, 100);
// Clear any existing interval before creating a new one to avoid orphaned handlers
if (menu.handler) {
clearInterval(menu.handler);
menu.handler = null;
}
const handler = setInterval(() => {
if (count < 3) {
var links = [...element.querySelectorAll('.nav-link.active')]
if (links.length > 0) {
clearInterval(handler);
if (menu.handler === handler) {
menu.handler = null;
}
var link = links.pop()
link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
} else {
// Stop retrying after the maximum number of attempts
clearInterval(handler);
if (menu.handler === handler) {
menu.handler = null;
}
}
count++;
}, 100);
menu.handler = handler;

Copilot uses AI. Check for mistakes.

var link = links.pop()
link.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The interval handler is not cleared when the maximum number of attempts (count >= 3) is reached. This causes the interval to continue running indefinitely, wasting resources. Add cleanup logic to clear the interval and set handler to null when count reaches 3.

Suggested change
}
}
} else {
clearInterval(menu.handler);
menu.handler = null;

Copilot uses AI. Check for mistakes.
Comment on lines 8 to +9
const handler = setTimeout(() => {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
clearTimeout(handler)
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The clearTimeout is called before the scrollIntoView operation. If there's an error during scrollIntoView, the timeout would have already been cleared, potentially masking the issue. Consider moving the clearTimeout call after the scrollIntoView operation completes.

Suggested change
clearTimeout(handler)
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
try {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
} finally {
clearTimeout(handler)
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Menu): improve scrollIntoView performance

2 participants