Skip to content

Ix 3264 focus#2614

Draft
varun-srinivasa wants to merge 5 commits into
mainfrom
ix-3264-focus
Draft

Ix 3264 focus#2614
varun-srinivasa wants to merge 5 commits into
mainfrom
ix-3264-focus

Conversation

@varun-srinivasa

Copy link
Copy Markdown
Collaborator

💡 What is the current behavior?

GitHub Issue Number: #

🆕 What is the new behavior?

🏁 Checklist

A pull request can only be merged if all of these conditions are met (where applicable):

  • 🦮 Accessibility (a11y) features were implemented
  • 🗺️ Internationalization (i18n) - no hard coded strings
  • 📲 Responsiveness - components handle viewport changes and content overflow gracefully
  • 📕 Add or update a Storybook story
  • 📄 Documentation was reviewed/updated siemens/ix-docs
  • 🧪 Unit tests were added/updated and pass (pnpm test)
  • 📸 Visual regression tests were added/updated and pass (Guide)
  • 🧐 Static code analysis passes (pnpm lint)
  • 🏗️ Successful compilation (pnpm build, changes pushed)

👨‍💻 Help & support

@netlify

netlify Bot commented Jun 22, 2026

Copy link
Copy Markdown

Deploy Preview for ix-storybook ready!

Name Link
🔨 Latest commit d6ae1bd
🔍 Latest deploy log https://app.netlify.com/projects/ix-storybook/deploys/6a38df53b0a5dd000883e697
😎 Deploy Preview https://deploy-preview-2614--ix-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d6ae1bd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b7ba4a96-3bf8-45c6-9260-1f85dda9d5e3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ix-3264-focus

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.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a unified focus-visible handling mechanism across multiple input and select components using a new FocusVisibleController and IxFocusableComponent base class, along with comprehensive regression tests. The review feedback highlights three key areas for improvement: resolving a potential lifecycle issue in select.tsx where the focus controller is not re-initialized upon DOM re-attachment, preventing visual flickering on focusout by checking if the newly focused element is still within the host, and adding a guard clause in setFocus to prevent state leaks when a null target is provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 504 to +505
override componentDidLoad() {
this.focusController = startFocusVisible(this.hostElement);

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.

high

In Stencil, componentDidLoad only runs once on initial load. If the component is detached and re-attached to the DOM, disconnectedCallback is called (which destroys the focusController), but componentDidLoad is not called again. This will leave the focusController undefined and break the focus-visible behavior and programmatic focusing after re-attachment.

To fix this, we should initialize the focusController in connectedCallback instead, and ensure we call super.connectedCallback() to preserve mixin lifecycles. Additionally, use a guard flag (such as hasDisconnected) to ensure that this initialization logic is not executed twice on initial mount if it is already handled by componentDidLoad.

  private hasDisconnected = false;

  override connectedCallback() {
    super.connectedCallback();
    if (this.hasDisconnected) {
      this.focusController = startFocusVisible(this.hostElement);
    }
  }

  override componentDidLoad() {
References
  1. When implementing connectedCallback in Stencil components to handle DOM re-attachment, use a guard flag (such as hasDisconnected) to ensure that initialization logic (like registering event listeners or observers) is not executed twice on initial mount if it is already handled by componentWillLoad or componentDidLoad.

Comment on lines +52 to +54
const onFocusout = () => {
hostEl.classList.remove(IX_FOCUS_VISIBLE_ACTIVE);
};

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.

medium

Unconditionally removing the focus-visible class on any focusout event can cause visual flickering or incorrect focus ring states when focus moves internally between elements within the same component.

Using event.relatedTarget to check if the new focused element is still a child of the host element prevents this unnecessary class removal.

Suggested change
const onFocusout = () => {
hostEl.classList.remove(IX_FOCUS_VISIBLE_ACTIVE);
};
const onFocusout = (event: FocusEvent) => {
if (event.relatedTarget && hostEl.contains(event.relatedTarget as Node)) {
return;
}
hostEl.classList.remove(IX_FOCUS_VISIBLE_ACTIVE);
};

Comment on lines +66 to +71
const setFocus = (target?: HTMLElement | null) => {
// Flag the next focusin as keyboard-like so IX_FOCUS_VISIBLE_ACTIVE
// is applied even though no key was pressed.
programmaticFocusPending = true;
target?.focus();
};

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.

medium

If setFocus is called with a null or undefined target, programmaticFocusPending is still set to true. This can leak the pending state, causing the next unrelated focus event on the host element to incorrectly receive the focus-visible class.

Adding a guard clause to return early if no target is provided prevents this state leak.

  const setFocus = (target?: HTMLElement | null) => {
    if (!target) {
      return;
    }
    // Flag the next focusin as keyboard-like so IX_FOCUS_VISIBLE_ACTIVE
    // is applied even though no key was pressed.
    programmaticFocusPending = true;
    target.focus();
  };

@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants