Skip to content

Commit 4267b8c

Browse files
authored
fix(radio-button-group): no longer focus first radio button on label click and adds setFocus method. (#7050)
**Related Issue:** #6698 ## Summary This PR will remove the focus flash from the first `calcite-radio-button` in group on label click Additional enhancements: - Adds `setFocus( )` method in `calcite-radio-button-group` - Users will be able to select label text in chrome. #6357 Note: With this PR, `delegateFocus` is removed from `calcite-radio-button-group` which is causing #6698. Because the tabIndex is set to 0 for the first focusable element on page load to enable keyboard navigation into the group it makes `delegateFocus` to apply focus styles on the first focusable element when the user clicks on the label (invokes .focus( )) resulting in the behavior mentioned in the issue 6698. To resolve this, `delegateFocus` is removed and added `setFocus( )` method.
1 parent eec0785 commit 4267b8c

File tree

2 files changed

+114
-9
lines changed

2 files changed

+114
-9
lines changed

packages/calcite-components/src/components/radio-button-group/radio-button-group.e2e.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { newE2EPage } from "@stencil/core/testing";
2-
import { accessible, defaults, hidden, reflects, renders } from "../../tests/commonTests";
2+
import { accessible, defaults, focusable, hidden, reflects, renders } from "../../tests/commonTests";
33
import { html } from "../../../support/formatting";
4+
import { getFocusedElementProp } from "../../tests/utils";
45

56
describe("calcite-radio-button-group", () => {
67
describe("renders", () => {
@@ -20,6 +21,22 @@ describe("calcite-radio-button-group", () => {
2021
]);
2122
});
2223

24+
describe("is focusable", () => {
25+
focusable(
26+
html`<calcite-radio-button-group name="Options" layout="vertical">
27+
<calcite-label layout="inline">
28+
<calcite-radio-button value="flowers" disabled></calcite-radio-button>
29+
Flowers
30+
</calcite-label>
31+
<calcite-label layout="inline">
32+
<calcite-radio-button value="trees"></calcite-radio-button>
33+
Trees
34+
</calcite-label>
35+
</calcite-radio-button-group>`,
36+
{ focusTargetSelector: "calcite-radio-button" }
37+
);
38+
});
39+
2340
describe("honors hidden attribute", () => {
2441
hidden("calcite-radio-button");
2542

@@ -452,4 +469,52 @@ describe("calcite-radio-button-group", () => {
452469
expect(changeEvent).toHaveReceivedEventTimes(3);
453470
expect(await getSelectedItemValue()).toBe("three");
454471
});
472+
473+
it("should focus the checked radio-button on setFocus()", async () => {
474+
const page = await newE2EPage();
475+
await page.setContent(html`
476+
<calcite-radio-button-group name="Options" layout="vertical">
477+
<calcite-label layout="inline">
478+
<calcite-radio-button value="trees" disabled id="trees"></calcite-radio-button>
479+
Trees
480+
</calcite-label>
481+
<calcite-label layout="inline">
482+
<calcite-radio-button value="shrubs" id="shrubs"></calcite-radio-button>
483+
Shrubs
484+
</calcite-label>
485+
<calcite-label layout="inline">
486+
<calcite-radio-button value="flowers" id="flowers" checked></calcite-radio-button>
487+
Flowers
488+
</calcite-label>
489+
</calcite-radio-button-group>
490+
`);
491+
const group = await page.find("calcite-radio-button-group");
492+
await group.callMethod("setFocus");
493+
await page.waitForChanges();
494+
expect(await getFocusedElementProp(page, "id")).toBe("flowers");
495+
});
496+
497+
it("should focus the first focusable radio-button on setFocus()", async () => {
498+
const page = await newE2EPage();
499+
await page.setContent(html`
500+
<calcite-radio-button-group name="Options" layout="vertical">
501+
<calcite-label layout="inline">
502+
<calcite-radio-button value="trees" disabled id="trees"></calcite-radio-button>
503+
Trees
504+
</calcite-label>
505+
<calcite-label layout="inline">
506+
<calcite-radio-button value="shrubs" id="shrubs"></calcite-radio-button>
507+
Shrubs
508+
</calcite-label>
509+
<calcite-label layout="inline">
510+
<calcite-radio-button value="flowers" id="flowers"></calcite-radio-button>
511+
Flowers
512+
</calcite-label>
513+
</calcite-radio-button-group>
514+
`);
515+
const group = await page.find("calcite-radio-button-group");
516+
await group.callMethod("setFocus");
517+
await page.waitForChanges();
518+
expect(await getFocusedElementProp(page, "id")).toBe("shrubs");
519+
});
455520
});

packages/calcite-components/src/components/radio-button-group/radio-button-group.tsx

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@ import {
66
h,
77
Host,
88
Listen,
9+
Method,
910
Prop,
11+
State,
1012
VNode,
1113
Watch
1214
} from "@stencil/core";
1315
import { createObserver } from "../../utils/observers";
1416
import { Layout, Scale } from "../interfaces";
17+
import {
18+
componentLoaded,
19+
LoadableComponent,
20+
setComponentLoaded,
21+
setUpLoadableComponent
22+
} from "../../utils/loadable";
1523

1624
/**
1725
* @slot - A slot for adding `calcite-radio-button`s.
1826
*/
1927
@Component({
2028
tag: "calcite-radio-button-group",
2129
styleUrl: "radio-button-group.scss",
22-
shadow: {
23-
delegatesFocus: true
24-
}
30+
shadow: true
2531
})
26-
export class RadioButtonGroup {
32+
export class RadioButtonGroup implements LoadableComponent {
2733
//--------------------------------------------------------------------------
2834
//
2935
// Element
@@ -91,6 +97,8 @@ export class RadioButtonGroup {
9197

9298
mutationObserver = createObserver("mutation", () => this.passPropsToRadioButtons());
9399

100+
@State() radioButtons: HTMLCalciteRadioButtonElement[] = [];
101+
94102
//--------------------------------------------------------------------------
95103
//
96104
// Lifecycle
@@ -102,6 +110,14 @@ export class RadioButtonGroup {
102110
this.mutationObserver?.observe(this.el, { childList: true, subtree: true });
103111
}
104112

113+
componentWillLoad(): void {
114+
setUpLoadableComponent(this);
115+
}
116+
117+
componentDidLoad(): void {
118+
setComponentLoaded(this);
119+
}
120+
105121
disconnectedCallback(): void {
106122
this.mutationObserver?.disconnect();
107123
}
@@ -113,10 +129,11 @@ export class RadioButtonGroup {
113129
//--------------------------------------------------------------------------
114130

115131
private passPropsToRadioButtons = (): void => {
116-
const radioButtons = this.el.querySelectorAll("calcite-radio-button");
117-
this.selectedItem = Array.from(radioButtons).find((radioButton) => radioButton.checked) || null;
118-
if (radioButtons.length > 0) {
119-
radioButtons.forEach((radioButton) => {
132+
this.radioButtons = Array.from(this.el.querySelectorAll("calcite-radio-button"));
133+
this.selectedItem =
134+
Array.from(this.radioButtons).find((radioButton) => radioButton.checked) || null;
135+
if (this.radioButtons.length > 0) {
136+
this.radioButtons.forEach((radioButton) => {
120137
radioButton.disabled = this.disabled || radioButton.disabled;
121138
radioButton.hidden = this.hidden;
122139
radioButton.name = this.name;
@@ -126,6 +143,10 @@ export class RadioButtonGroup {
126143
}
127144
};
128145

146+
private getFocusableRadioButton(): HTMLCalciteRadioButtonElement | null {
147+
return this.radioButtons.find((radiobutton) => !radiobutton.disabled) ?? null;
148+
}
149+
129150
//--------------------------------------------------------------------------
130151
//
131152
// Events
@@ -137,6 +158,25 @@ export class RadioButtonGroup {
137158
*/
138159
@Event({ cancelable: false }) calciteRadioButtonGroupChange: EventEmitter<void>;
139160

161+
//--------------------------------------------------------------------------
162+
//
163+
// Public Method
164+
//
165+
//--------------------------------------------------------------------------
166+
167+
/** Sets focus on the fist focusable `calcite-radio-button` element in the component. */
168+
@Method()
169+
async setFocus(): Promise<void> {
170+
await componentLoaded(this);
171+
if (this.selectedItem && !this.selectedItem.disabled) {
172+
this.selectedItem.setFocus();
173+
return;
174+
}
175+
if (this.radioButtons.length > 0) {
176+
this.getFocusableRadioButton()?.setFocus();
177+
}
178+
}
179+
140180
//--------------------------------------------------------------------------
141181
//
142182
// Event Listeners

0 commit comments

Comments
 (0)