Skip to content

Commit

Permalink
fix(radio-button): focuses first focusable radio-button element in gr…
Browse files Browse the repository at this point in the history
…oup. (#7152)

**Related Issue:** #7113 

## Summary

This PR will focus the first focusable `calcite-radio-button` when the
user `Tab` in to the group.
  • Loading branch information
anveshmekala authored Jun 21, 2023
1 parent c2eb43f commit dd7ec60
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
renders
} from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { getFocusedElementProp } from "../../tests/utils";

describe("calcite-radio-button", () => {
describe("renders", () => {
Expand Down Expand Up @@ -66,6 +67,102 @@ describe("calcite-radio-button", () => {
focusable("calcite-radio-button", {
shadowFocusTargetSelector: ".container"
});

it("focuses first focusable item on Tab when new radio-button is added", async () => {
const page = await newE2EPage();
await page.setContent(html`
<div>
<calcite-label layout="inline" id="1">
<calcite-radio-button value="trees" disabled id="trees" name="Options"></calcite-radio-button>
Trees
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="shrubs" id="shrubs" name="Options"></calcite-radio-button>
Shrubs
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="flowers" id="flowers" name="Options"></calcite-radio-button>
Flowers
</calcite-label>
<calcite-button id="submit">submit</calcite-button>
</div>
`);

await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("shrubs");
await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("submit");

await page.evaluate(() => {
const firstRadioButton = document.querySelector('calcite-label[id="1"]');
const newRadioButton = `<calcite-label layout="inline">
<calcite-radio-button value="plants" name="Options" id="plants"></calcite-radio-button>
Plants
</calcite-label>`;
firstRadioButton.insertAdjacentHTML("beforebegin", newRadioButton);
});

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("plants");
await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("submit");

const radioButtonElement = await page.find('calcite-radio-button[id="plants"]');
radioButtonElement.setProperty("disabled", true);
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("shrubs");
});

it("focuses checked item on Tab when new radio-button is added", async () => {
const page = await newE2EPage();
await page.setContent(html`
<div>
<calcite-label layout="inline" id="1">
<calcite-radio-button value="trees" disabled id="trees" name="Options"></calcite-radio-button>
Trees
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="shrubs" id="shrubs" name="Options"></calcite-radio-button>
Shrubs
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="flowers" id="flowers" name="Options" checked></calcite-radio-button>
Flowers
</calcite-label>
<calcite-button id="submit">submit</calcite-button>
</div>
`);

await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("flowers");
await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "id")).toBe("submit");

await page.evaluate(() => {
const firstRadioButton = document.querySelector('calcite-label[id="1"]');
const newRadioButton = `<calcite-label layout="inline">
<calcite-radio-button value="plants" name="Options" id="plants"></calcite-radio-button>
Plants
</calcite-label>`;
firstRadioButton.insertAdjacentHTML("beforebegin", newRadioButton);
});

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("flowers");
});
});

describe("reflects", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Element,
Event,
EventEmitter,
forceUpdate,
h,
Host,
Listen,
Expand Down Expand Up @@ -73,6 +74,11 @@ export class RadioButton
/** When `true`, interaction is prevented and the component is displayed with lower opacity. */
@Prop({ reflect: true }) disabled = false;

@Watch("disabled")
disabledChanged(): void {
this.updateTabIndexOfOtherRadioButtonsInGroup();
}

/**
* The focused state of the component.
*
Expand All @@ -94,6 +100,11 @@ export class RadioButton
/** When `true`, the component is not displayed and is not focusable or checkable. */
@Prop({ reflect: true }) hidden = false;

@Watch("hidden")
hiddenChanged(): void {
this.updateTabIndexOfOtherRadioButtonsInGroup();
}

/**
* The hovered state of the component.
*
Expand Down Expand Up @@ -184,9 +195,11 @@ export class RadioButton
) as HTMLCalciteRadioButtonElement[];
};

isDefaultSelectable = (): boolean => {
isFocusable = (): boolean => {
const radioButtons = this.queryButtons();
return !radioButtons.some((radioButton) => radioButton.checked) && radioButtons[0] === this.el;
const firstFocusable = radioButtons.find((radioButton) => !radioButton.disabled);
const checked = radioButtons.find((radioButton) => radioButton.checked);
return firstFocusable === this.el && !checked;
};

check = (): void => {
Expand Down Expand Up @@ -291,11 +304,21 @@ export class RadioButton
});
}

private updateTabIndexOfOtherRadioButtonsInGroup(): void {
const radioButtons = this.queryButtons();
const otherFocusableRadioButtons = radioButtons.filter(
(radioButton) => radioButton.guid !== this.guid && !radioButton.disabled
);
otherFocusableRadioButtons.forEach((radioButton) => {
forceUpdate(radioButton);
});
}

private getTabIndex(): number | undefined {
if (this.disabled) {
return undefined;
}
return this.checked || this.isDefaultSelectable() ? 0 : -1;
return this.checked || this.isFocusable() ? 0 : -1;
}

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -446,6 +469,7 @@ export class RadioButton
connectInteractive(this);
connectLabel(this);
connectForm(this);
this.updateTabIndexOfOtherRadioButtonsInGroup();
}

componentWillLoad(): void {
Expand All @@ -464,6 +488,7 @@ export class RadioButton
disconnectInteractive(this);
disconnectLabel(this);
disconnectForm(this);
this.updateTabIndexOfOtherRadioButtonsInGroup();
}

componentDidRender(): void {
Expand Down

0 comments on commit dd7ec60

Please sign in to comment.