Skip to content

Conversation

Rajdeepc
Copy link
Contributor

@Rajdeepc Rajdeepc commented Sep 16, 2025

Description

Fixes MenuItem focus stealing from input elements on mouseover

Solution

  • Added isInputElement() method that detects all input types
  • Uses getRootNode().activeElement for reliable focus detection across shadow boundaries
  • Handles both document and shadow root contexts
  • Avoids expensive DOM queries and property enumeration
  • Uses ARIA roles (textbox, searchbox, combobox, spinbutton, slider) for generic detection
  • Regex pattern matching for Spectrum Web Components (/^(SP-SEARCH|SP-TEXTFIELD|SP-NUMBER-FIELD|SP-COMBOBOX|SP-COLOR-FIELD)$/)

No Breaking Changes

  • Existing menu functionality preserved
  • Menu items still receive focus when appropriate
  • No API changes
  • Backward compatible

Technical Details

Before

handleMouseover(event: MouseEvent): void {
    const target = event.target as HTMLElement;
    if (target === this) {
        this.focus(); // Always stole focus
        this.focused = false;
    }
}

After

handleMouseover(event: MouseEvent): void {
    const target = event.target as HTMLElement;
    if (target === this) {
        const rootNode = this.getRootNode() as Document | ShadowRoot;
        const activeElement = rootNode.activeElement as HTMLElement;
        
        // Only focus if no input element is currently active
        if (!activeElement || !this.isInputElement(activeElement)) {
            this.focus();
        }
        this.focused = false;
    }
}

Motivation and context

MenuItem was stealing focus from input elements (searchboxes, textfields, etc.) when users hovered over menu items while typing. This created a poor user experience where:

  • Users typing in search boxes would lose focus when hovering over menu items
  • Input was interrupted mid-typing, forcing users to click back into the input
  • This affected all Spectrum Web Components input types (sp-search, sp-textfield, sp-number-field, sp-combobox, sp-color-field)
  • Native HTML inputs (input, textarea, select) were also affected
  • Contenteditable elements experienced the same issue

Related issue(s)

Screenshots (if appropriate)


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Descriptive Test Statement

    1. Go here
    2. Click in any input field (search, textfield, number-field, etc.)
    3. Start typing
    4. Hover over menu items while continuing to type
    5. Verify that focus remains in the input field and typing continues uninterrupted

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Copy link

changeset-bot bot commented Sep 16, 2025

🦋 Changeset detected

Latest commit: e36b58b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 84 packages
Name Type
@spectrum-web-components/menu Minor
@spectrum-web-components/breadcrumbs Minor
@spectrum-web-components/combobox Minor
@spectrum-web-components/picker Minor
@spectrum-web-components/custom-vars-viewer Minor
example-project-rollup Patch
example-project-webpack Patch
@spectrum-web-components/story-decorator Minor
@spectrum-web-components/bundle Minor
@spectrum-web-components/action-menu Minor
documentation Patch
@spectrum-web-components/eslint-plugin Minor
@spectrum-web-components/accordion Minor
@spectrum-web-components/action-bar Minor
@spectrum-web-components/action-button Minor
@spectrum-web-components/action-group Minor
@spectrum-web-components/alert-banner Minor
@spectrum-web-components/alert-dialog Minor
@spectrum-web-components/asset Minor
@spectrum-web-components/avatar Minor
@spectrum-web-components/badge Minor
@spectrum-web-components/button-group Minor
@spectrum-web-components/button Minor
@spectrum-web-components/card Minor
@spectrum-web-components/checkbox Minor
@spectrum-web-components/clear-button Minor
@spectrum-web-components/close-button Minor
@spectrum-web-components/coachmark Minor
@spectrum-web-components/color-area Minor
@spectrum-web-components/color-field Minor
@spectrum-web-components/color-handle Minor
@spectrum-web-components/color-loupe Minor
@spectrum-web-components/color-slider Minor
@spectrum-web-components/color-wheel Minor
@spectrum-web-components/contextual-help Minor
@spectrum-web-components/dialog Minor
@spectrum-web-components/divider Minor
@spectrum-web-components/dropzone Minor
@spectrum-web-components/field-group Minor
@spectrum-web-components/field-label Minor
@spectrum-web-components/help-text Minor
@spectrum-web-components/icon Minor
@spectrum-web-components/icons-ui Minor
@spectrum-web-components/icons-workflow Minor
@spectrum-web-components/icons Minor
@spectrum-web-components/iconset Minor
@spectrum-web-components/illustrated-message Minor
@spectrum-web-components/infield-button Minor
@spectrum-web-components/link Minor
@spectrum-web-components/meter Minor
@spectrum-web-components/modal Minor
@spectrum-web-components/number-field Minor
@spectrum-web-components/overlay Minor
@spectrum-web-components/picker-button Minor
@spectrum-web-components/popover Minor
@spectrum-web-components/progress-bar Minor
@spectrum-web-components/progress-circle Minor
@spectrum-web-components/radio Minor
@spectrum-web-components/search Minor
@spectrum-web-components/sidenav Minor
@spectrum-web-components/slider Minor
@spectrum-web-components/split-view Minor
@spectrum-web-components/status-light Minor
@spectrum-web-components/swatch Minor
@spectrum-web-components/switch Minor
@spectrum-web-components/table Minor
@spectrum-web-components/tabs Minor
@spectrum-web-components/tags Minor
@spectrum-web-components/textfield Minor
@spectrum-web-components/thumbnail Minor
@spectrum-web-components/toast Minor
@spectrum-web-components/tooltip Minor
@spectrum-web-components/top-nav Minor
@spectrum-web-components/tray Minor
@spectrum-web-components/underlay Minor
@spectrum-web-components/vrt-compare Minor
@spectrum-web-components/base Minor
@spectrum-web-components/grid Minor
@spectrum-web-components/opacity-checkerboard Minor
@spectrum-web-components/reactive-controllers Minor
@spectrum-web-components/shared Minor
@spectrum-web-components/styles Minor
@spectrum-web-components/theme Minor
@spectrum-web-components/truncated Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Rajdeepc Rajdeepc added bug Something isn't working Component: Menu SEV 1 labels Sep 16, 2025
@Rajdeepc Rajdeepc self-assigned this Sep 16, 2025
Copy link
Contributor

github-actions bot commented Sep 16, 2025

📚 Branch Preview

🔍 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-5732

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@Rajdeepc Rajdeepc changed the title Rajdeep/jira 1027 fix(menu): MenuItem focus stealing from input elements on mouseover Sep 16, 2025
@Rajdeepc Rajdeepc marked this pull request as ready for review September 16, 2025 12:01
@Rajdeepc Rajdeepc requested a review from a team as a code owner September 16, 2025 12:01
Copy link
Contributor

@castastrophe castastrophe left a comment

Choose a reason for hiding this comment

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

A quick first pass review; I'll validate once the test case is written up!

`;
};

export const InputsWithMenu = (): TemplateResult => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a great test case but I don't think we need a new story for it. If you wanted to replicate the behavior in a story, we could probably use the play functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's already a comprehensive test coverage for the focus behavior in packages/menu/test/menu.test.ts (lines 848-984) with the test 'does not steal focus from input elements on mouseover' that validates all the same input types and scenarios. Let me know if I am missing something here?

Copy link
Contributor

Choose a reason for hiding this comment

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

My main thought with not adding this as a story is that it's a lot of manual instructions for the viewer of the page to complete to get the experience you're demonstrating. Maybe we defer adding any new stories until we have a chance to talk about Storybook docs more as a team?

<!-- Color Field Input -->
<div>
<label for="demo-color">Color Field:</label>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<label for="demo-color">Color Field:</label>
<label for="demo-color">Color field:</label>

Minor nit: try to use sentence case headings in our demos to reinforce the design system content standards where possible!

* Regular expression pattern to match Spectrum Web Components input elements.
* Used to identify components that should maintain focus during menu interactions.
*/
export const INPUT_COMPONENT_PATTERN =
Copy link
Contributor

Choose a reason for hiding this comment

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

@rubencarvalho Let me know what you think about this! I dig it since we do reuse this logic and it feels like a more global concept to have updated in one place.

Copy link
Contributor

@castastrophe castastrophe left a comment

Choose a reason for hiding this comment

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

If you can add a manual test case to the PR description so I can do that last bit of validation, the rest of this looks really great.

@Rajdeepc
Copy link
Contributor Author

If you can add a manual test case to the PR description so I can do that last bit of validation, the rest of this looks really great.

@castastrophe I’ve already added a manual test case to verify this behavior with multiple input elements. Could you let me know if you’re looking to validate something different?

@blunteshwar
Copy link
Contributor

Can you update the Image Hash for this.

Copy link
Contributor

github-actions bot commented Sep 24, 2025

Tachometer results

Chrome

menu permalink

test-basic

Version Bytes Avg Time vs remote vs branch
npm latest 534 kB 175.89ms - 179.29ms - faster ✔
2% - 5%
4.19ms - 9.36ms
branch 513 kB 182.43ms - 186.30ms slower ❌
2% - 5%
4.19ms - 9.36ms
-
Firefox

menu permalink

test-basic

Version Bytes Avg Time vs remote vs branch
npm latest 534 kB 343.15ms - 353.09ms - faster ✔
1% - 5%
3.22ms - 17.38ms
branch 513 kB 353.38ms - 363.46ms slower ❌
1% - 5%
3.22ms - 17.38ms
-

@castastrophe
Copy link
Contributor

@castastrophe I’ve already added a manual test case to verify this behavior with multiple input elements. Could you let me know if you’re looking to validate something different?

@Rajdeepc Oh I see it now! The title was the template language so I thought it hadn't been updated. I validated and it looks great!

};

InputsWithMenu.parameters = {
tags: ['!dev'],
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants