Skip to content

feat(tabs): refactored for focusgroup navigation controller - #6494

Merged
nikkimk merged 13 commits into
mainfrom
nikkimk/tabs-focusgroup-controller
Jul 14, 2026
Merged

feat(tabs): refactored for focusgroup navigation controller#6494
nikkimk merged 13 commits into
mainfrom
nikkimk/tabs-focusgroup-controller

Conversation

@nikkimk

@nikkimk nikkimk commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Refactor of 2nd-gen swc-tabs to leverage live focusgroup navigation controller.

Motivation and context

Centralizing roving tabindex/arrow key navigation into a single controller for the components that use it.

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

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).

  • Keyboard (required — document steps below) — What to test for: Focus order is logical; Tab reaches the component and all interactive descendants; Enter/Space activate where appropriate; arrow keys work for tabs, menus, sliders, etc.; no focus traps; Escape dismisses when applicable; focus indicator is visible.
    1. Go to the Tabs docs
    2. Tab to each example.
    3. Use arrow keys to navigate.
    4. Determine that there are no regressions in the keyboard behavior.

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7f4eea4

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

This PR includes changesets to release 2 packages
Name Type
@spectrum-web-components/core Minor
@adobe/spectrum-wc 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

@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 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-6494

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.

@coveralls

coveralls commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29361234753

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage remained the same at 96.257%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 39169
Covered Lines: 37903
Line Coverage: 96.77%
Relevant Branches: 6460
Covered Branches: 6018
Branch Coverage: 93.16%
Branches in Coverage %: Yes
Coverage Strength: 461.17 hits per line

💛 - Coveralls

@nikkimk nikkimk added a11y Issues or PRs related to accessibility Status:Ready for review PR ready for review or re-review. labels Jul 10, 2026
@nikkimk
nikkimk marked this pull request as ready for review July 10, 2026 15:28
@nikkimk
nikkimk requested a review from a team as a code owner July 10, 2026 15:28
* - **`programmatic`** — {@link FocusgroupNavigationController.setActiveItem} or
* {@link FocusgroupNavigationController.focusFirstItemByTextPrefix} was called directly.
*/
export type FocusgroupActiveChangeReason =

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.

nit: I like reason, but on the Popover I called it source... for kind of the same thing (when the popover is closed, source values can be programatic, esc, or click-outside). Do you think I should change the one on Popover?

@miwha-adobe miwha-adobe 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.

Nice! I like breaking it up.

A few things: it looks like while tabs.selected is fixed, there's still a mismatch on the clicked tab's own .selected inside the handler. This test should surface it:

// ──────────────────────────────────────────────────────────────
// TEST: Change event — selected is the new tab inside the handler
// ──────────────────────────────────────────────────────────────

export const ChangeHandlerSelectedTest: Story = {
  render: () => html`
    <swc-tabs selected="1" accessible-label="Change handler selected test">
      <swc-tab tab-id="1">Tab 1</swc-tab>
      <swc-tab tab-id="2">Tab 2</swc-tab>
      <swc-tab-panel tab-id="1"><p>Panel 1</p></swc-tab-panel>
      <swc-tab-panel tab-id="2"><p>Panel 2</p></swc-tab-panel>
    </swc-tabs>
  `,
  play: async ({ canvasElement, step }) => {
    const tabs = await getComponent<Tabs>(canvasElement, 'swc-tabs');
    const tab2 = canvasElement.querySelector('swc-tab[tab-id="2"]') as Tab;

    await step(
      'clicked tab reflects the new selection inside the change handler',
      async () => {
        let containerInHandler: string | undefined;
        let clickedTabInHandler: boolean | undefined;
        tabs.addEventListener(
          'change',
          () => {
            containerInHandler = tabs.selected;
            clickedTabInHandler = tab2.selected;
          },
          { once: true }
        );

        tab2.click();
        await tabs.updateComplete;

        expect(containerInHandler).toBe('2'); // passes today
        expect(clickedTabInHandler).toBe(true); // fails today: reads false
      }
    );
  },
};

Also added a question regarding the disabled selection!

if (tab.disabled) {
return;
}
this.selectTarget(tab);

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.

Is this intended? Disabled can receive focus, but arrow-keying onto a disabled tab jumps the selection to the first tab (items.find(...) Recording:

disabled.mov

@nikkimk
nikkimk requested a review from miwha-adobe July 13, 2026 14:20
@nikkimk nikkimk added the 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. label Jul 13, 2026

@miwha-adobe miwha-adobe 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.

LGTM! ✨

@nikkimk nikkimk added the Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. label Jul 14, 2026

@rubencarvalho rubencarvalho 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.

Looks great! I just left 1 comment, non blocking!

// When disabled, getItems() returns [] so the controller's cleanup
// loop in refresh() iterates nothing. Explicitly remove all tabs from
// the tab order so the tablist is fully inert.
for (const tab of this._tabs) {

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.

I'm not sure if this should or not be responsibility of the controller, but it's probably fine for now!

@nikkimk nikkimk self-assigned this Jul 14, 2026
@nikkimk nikkimk added the run_vrt Triggers the Chromatic VRT run for 2nd-gen label Jul 14, 2026
@nikkimk
nikkimk enabled auto-merge (squash) July 14, 2026 19:17
@nikkimk
nikkimk merged commit 9e7995a into main Jul 14, 2026
34 of 37 checks passed
@nikkimk
nikkimk deleted the nikkimk/tabs-focusgroup-controller branch July 14, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. a11y Issues or PRs related to accessibility run_vrt Triggers the Chromatic VRT run for 2nd-gen Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants