Skip to content

Commit 3945ed6

Browse files
committed
fix(material/radio): Ensure focus and selected states stay linked (#29082)
(cherry picked from commit 71297ad)
1 parent dfc19e2 commit 3945ed6

File tree

1 file changed

+37
-7
lines changed

1 file changed

+37
-7
lines changed

src/material/radio/radio.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
10+
import {UniqueSelectionDispatcher} from '@angular/cdk/collections';
911
import {
12+
ANIMATION_MODULE_TYPE,
1013
AfterContentInit,
1114
AfterViewInit,
1215
Attribute,
13-
booleanAttribute,
1416
ChangeDetectionStrategy,
1517
ChangeDetectorRef,
1618
Component,
@@ -19,24 +21,25 @@ import {
1921
DoCheck,
2022
ElementRef,
2123
EventEmitter,
22-
forwardRef,
2324
Inject,
2425
InjectionToken,
26+
Injector,
2527
Input,
26-
numberAttribute,
2728
OnDestroy,
2829
OnInit,
2930
Optional,
3031
Output,
3132
QueryList,
3233
ViewChild,
3334
ViewEncapsulation,
34-
ANIMATION_MODULE_TYPE,
35+
afterNextRender,
36+
booleanAttribute,
37+
forwardRef,
38+
inject,
39+
numberAttribute,
3540
} from '@angular/core';
36-
import {_MatInternalFormField, MatRipple, ThemePalette} from '@angular/material/core';
37-
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
38-
import {UniqueSelectionDispatcher} from '@angular/cdk/collections';
3941
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
42+
import {MatRipple, ThemePalette, _MatInternalFormField} from '@angular/material/core';
4043
import {Subscription} from 'rxjs';
4144

4245
// Increasing integer for generating unique ids for radio components.
@@ -532,6 +535,8 @@ export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy
532535
/** Whether animations are disabled. */
533536
_noopAnimations: boolean;
534537

538+
private _injector = inject(Injector);
539+
535540
constructor(
536541
@Optional() @Inject(MAT_RADIO_GROUP) radioGroup: MatRadioGroup,
537542
protected _elementRef: ElementRef,
@@ -695,6 +700,31 @@ export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy
695700
if (input) {
696701
input.setAttribute('tabindex', value + '');
697702
this._previousTabIndex = value;
703+
// Wait for any pending tabindex changes to be applied
704+
afterNextRender(
705+
() => {
706+
queueMicrotask(() => {
707+
// The radio group uses a "selection follows focus" pattern for tab management, so if this
708+
// radio button is currently focused and another radio button in the group becomes
709+
// selected, we should move focus to the newly selected radio button to maintain
710+
// consistency between the focused and selected states.
711+
if (
712+
group &&
713+
group.selected &&
714+
group.selected !== this &&
715+
document.activeElement === input
716+
) {
717+
group.selected?._inputElement.nativeElement.focus();
718+
// If this radio button still has focus, the selected one must be disabled. In this
719+
// case the radio group as a whole should lose focus.
720+
if (document.activeElement === input) {
721+
this._inputElement.nativeElement.blur();
722+
}
723+
}
724+
});
725+
},
726+
{injector: this._injector},
727+
);
698728
}
699729
}
700730
}

0 commit comments

Comments
 (0)