Skip to content

fix: focus monitor-based styles not working in some cases inside shadow dom #19422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/cdk/a11y/focus-monitor/focus-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
OnDestroy,
Optional,
Output,
AfterViewInit,
} from '@angular/core';
import {Observable, of as observableOf, Subject, Subscription} from 'rxjs';
import {coerceElement} from '@angular/cdk/coercion';
Expand Down Expand Up @@ -562,19 +563,24 @@ function getTarget(event: Event): HTMLElement|null {
@Directive({
selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
})
export class CdkMonitorFocus implements OnDestroy {
export class CdkMonitorFocus implements AfterViewInit, OnDestroy {
private _monitorSubscription: Subscription;
@Output() cdkFocusChange = new EventEmitter<FocusOrigin>();

constructor(private _elementRef: ElementRef<HTMLElement>, private _focusMonitor: FocusMonitor) {
constructor(private _elementRef: ElementRef<HTMLElement>, private _focusMonitor: FocusMonitor) {}

ngAfterViewInit() {
this._monitorSubscription = this._focusMonitor.monitor(
this._elementRef,
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
.subscribe(origin => this.cdkFocusChange.emit(origin));
this._elementRef,
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
.subscribe(origin => this.cdkFocusChange.emit(origin));
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
this._monitorSubscription.unsubscribe();

if (this._monitorSubscription) {
this._monitorSubscription.unsubscribe();
}
}
}
5 changes: 4 additions & 1 deletion src/material/button-toggle/button-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
ViewEncapsulation,
InjectionToken,
Inject,
AfterViewInit,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {
Expand Down Expand Up @@ -394,7 +395,7 @@ const _MatButtonToggleMixinBase: CanDisableRippleCtor & typeof MatButtonToggleBa
'(focus)': 'focus()',
}
})
export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit,
export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit, AfterViewInit,
CanDisableRipple, OnDestroy {

private _isSingleSelector = false;
Expand Down Expand Up @@ -512,7 +513,9 @@ export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit
group._syncButtonToggle(this, this._checked);
}
}
}

ngAfterViewInit() {
this._focusMonitor.monitor(this._elementRef, true);
}

Expand Down
1 change: 1 addition & 0 deletions src/material/button/button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('MatButton', () => {

it('should be able to focus button with a specific focus origin', () => {
const fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const buttonDebugEl = fixture.debugElement.query(By.css('button'));
const buttonInstance = buttonDebugEl.componentInstance as MatButton;

Expand Down
9 changes: 6 additions & 3 deletions src/material/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Optional,
Inject,
Input,
AfterViewInit,
} from '@angular/core';
import {
CanColor,
Expand Down Expand Up @@ -79,7 +80,7 @@ const _MatButtonMixinBase: CanDisableRippleCtor & CanDisableCtor & CanColorCtor
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatButton extends _MatButtonMixinBase
implements OnDestroy, CanDisable, CanColor, CanDisableRipple, FocusableOption {
implements AfterViewInit, OnDestroy, CanDisable, CanColor, CanDisableRipple, FocusableOption {

/** Whether the button is round. */
readonly isRoundButton: boolean = this._hasHostAttributes('mat-fab', 'mat-mini-fab');
Expand Down Expand Up @@ -108,13 +109,15 @@ export class MatButton extends _MatButtonMixinBase
// the class is applied to derived classes.
elementRef.nativeElement.classList.add('mat-button-base');

this._focusMonitor.monitor(this._elementRef, true);

if (this.isRoundButton) {
this.color = DEFAULT_ROUND_BUTTON_COLOR;
}
}

ngAfterViewInit() {
this._focusMonitor.monitor(this._elementRef, true);
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
}
Expand Down
14 changes: 7 additions & 7 deletions src/material/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc

this.tabIndex = parseInt(tabIndex) || 0;

this._focusMonitor.monitor(elementRef, true).subscribe(focusOrigin => {
// TODO: Remove this after the `_clickAction` parameter is removed as an injection parameter.
this._clickAction = this._clickAction || this._options.clickAction;
}

ngAfterViewInit() {
this._focusMonitor.monitor(this._elementRef, true).subscribe(focusOrigin => {
if (!focusOrigin) {
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
// Angular does not expect events to be raised during change detection, so any state change
Expand All @@ -230,16 +235,11 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
// telling the form control it has been touched until the next tick.
Promise.resolve().then(() => {
this._onTouched();
_changeDetectorRef.markForCheck();
this._changeDetectorRef.markForCheck();
});
}
});

// TODO: Remove this after the `_clickAction` parameter is removed as an injection parameter.
this._clickAction = this._clickAction || this._options.clickAction;
}

ngAfterViewInit() {
this._syncIndeterminate(this._indeterminate);
}

Expand Down
17 changes: 10 additions & 7 deletions src/material/expansion/expansion-panel-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ViewEncapsulation,
Optional,
Inject,
AfterViewInit,
} from '@angular/core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {merge, Subscription, EMPTY} from 'rxjs';
Expand Down Expand Up @@ -64,7 +65,7 @@ import {MatAccordionTogglePosition} from './accordion-base';
'(keydown)': '_keydown($event)',
},
})
export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
export class MatExpansionPanelHeader implements AfterViewInit, OnDestroy, FocusableOption {
private _parentChangeSubscription = Subscription.EMPTY;

constructor(
Expand Down Expand Up @@ -99,12 +100,6 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
.pipe(filter(() => panel._containsFocus()))
.subscribe(() => _focusMonitor.focusVia(_element, 'program'));

_focusMonitor.monitor(_element).subscribe(origin => {
if (origin && panel.accordion) {
panel.accordion._handleHeaderFocus(this);
}
});

if (defaultOptions) {
this.expandedHeight = defaultOptions.expandedHeight;
this.collapsedHeight = defaultOptions.collapsedHeight;
Expand Down Expand Up @@ -201,6 +196,14 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
this._focusMonitor.focusVia(this._element, origin, options);
}

ngAfterViewInit() {
this._focusMonitor.monitor(this._element).subscribe(origin => {
if (origin && this.panel.accordion) {
this.panel.accordion._handleHeaderFocus(this);
}
});
}

ngOnDestroy() {
this._parentChangeSubscription.unsubscribe();
this._focusMonitor.stopMonitoring(this._element);
Expand Down
6 changes: 3 additions & 3 deletions src/material/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
NgZone,
OnChanges,
OnDestroy,
OnInit,
Optional,
Self,
HostListener,
AfterViewInit,
} from '@angular/core';
import {FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {
Expand Down Expand Up @@ -88,7 +88,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
providers: [{provide: MatFormFieldControl, useExisting: MatInput}],
})
export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<any>, OnChanges,
OnDestroy, OnInit, DoCheck, CanUpdateErrorState {
OnDestroy, AfterViewInit, DoCheck, CanUpdateErrorState {
protected _uid = `mat-input-${nextUniqueId++}`;
protected _previousNativeValue: any;
private _inputValueAccessor: {value: any};
Expand Down Expand Up @@ -277,7 +277,7 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
}
}

ngOnInit() {
ngAfterViewInit() {
if (this._platform.isBrowser) {
this._autofillMonitor.monitor(this._elementRef.nativeElement).subscribe(event => {
this.autofilled = event.isAutofilled;
Expand Down
19 changes: 11 additions & 8 deletions src/material/menu/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Optional,
Input,
HostListener,
AfterViewInit,
} from '@angular/core';
import {
CanDisable, CanDisableCtor,
Expand Down Expand Up @@ -57,7 +58,7 @@ const _MatMenuItemMixinBase: CanDisableRippleCtor & CanDisableCtor & typeof MatM
templateUrl: 'menu-item.html',
})
export class MatMenuItem extends _MatMenuItemMixinBase
implements FocusableOption, CanDisable, CanDisableRipple, OnDestroy {
implements FocusableOption, CanDisable, CanDisableRipple, AfterViewInit, OnDestroy {

/** ARIA role for the menu item. */
@Input() role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem';
Expand Down Expand Up @@ -85,13 +86,6 @@ export class MatMenuItem extends _MatMenuItemMixinBase
// @breaking-change 8.0.0 make `_focusMonitor` and `document` required params.
super();

if (_focusMonitor) {
// Start monitoring the element so it gets the appropriate focused classes. We want
// to show the focus style for menu items only when the focus was not caused by a
// mouse or touch interaction.
_focusMonitor.monitor(this._elementRef, false);
}

if (_parentMenu && _parentMenu.addItem) {
_parentMenu.addItem(this);
}
Expand All @@ -110,6 +104,15 @@ export class MatMenuItem extends _MatMenuItemMixinBase
this._focused.next(this);
}

ngAfterViewInit() {
if (this._focusMonitor) {
// Start monitoring the element so it gets the appropriate focused classes. We want
// to show the focus style for menu items only when the focus was not caused by a
// mouse or touch interaction.
this._focusMonitor.monitor(this._elementRef, false);
}
}

ngOnDestroy() {
if (this._focusMonitor) {
this._focusMonitor.stopMonitoring(this._elementRef);
Expand Down
6 changes: 3 additions & 3 deletions src/material/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ import {
Inject,
Input,
OnDestroy,
OnInit,
Optional,
Output,
ViewChild,
ViewEncapsulation,
NgZone,
AfterViewInit,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {
Expand Down Expand Up @@ -156,7 +156,7 @@ const _MatSliderMixinBase:
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatSlider extends _MatSliderMixinBase
implements ControlValueAccessor, OnDestroy, CanDisable, CanColor, OnInit, HasTabIndex {
implements ControlValueAccessor, OnDestroy, CanDisable, CanColor, AfterViewInit, HasTabIndex {
/** Whether the slider is inverted. */
@Input()
get invert(): boolean { return this._invert; }
Expand Down Expand Up @@ -511,7 +511,7 @@ export class MatSlider extends _MatSliderMixinBase
});
}

ngOnInit() {
ngAfterViewInit() {
this._focusMonitor
.monitor(this._elementRef, true)
.subscribe((origin: FocusOrigin) => {
Expand Down
15 changes: 9 additions & 6 deletions src/material/sort/sort-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ViewEncapsulation,
Inject,
ElementRef,
AfterViewInit,
} from '@angular/core';
import {CanDisable, CanDisableCtor, mixinDisabled} from '@angular/material/core';
import {FocusMonitor} from '@angular/cdk/a11y';
Expand Down Expand Up @@ -95,7 +96,7 @@ interface MatSortHeaderColumnDef {
]
})
export class MatSortHeader extends _MatSortHeaderMixinBase
implements CanDisable, MatSortable, OnDestroy, OnInit {
implements CanDisable, MatSortable, OnDestroy, OnInit, AfterViewInit {
private _rerenderSubscription: Subscription;

/**
Expand Down Expand Up @@ -168,11 +169,6 @@ export class MatSortHeader extends _MatSortHeaderMixinBase

changeDetectorRef.markForCheck();
});

// We use the focus monitor because we also want to style
// things differently based on the focus origin.
_focusMonitor.monitor(_elementRef, true)
.subscribe(origin => this._setIndicatorHintVisible(!!origin));
}

ngOnInit() {
Expand All @@ -188,6 +184,13 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
this._sort.register(this);
}

ngAfterViewInit() {
// We use the focus monitor because we also want to style
// things differently based on the focus origin.
this._focusMonitor.monitor(this._elementRef, true)
.subscribe(origin => this._setIndicatorHintVisible(!!origin));
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
this._sort.deregister(this);
Expand Down
8 changes: 6 additions & 2 deletions src/material/stepper/step-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
OnDestroy,
ViewEncapsulation,
TemplateRef,
AfterViewInit,
} from '@angular/core';
import {Subscription} from 'rxjs';
import {MatStepLabel} from './step-label';
Expand All @@ -35,7 +36,7 @@ import {CdkStepHeader, StepState} from '@angular/cdk/stepper';
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatStepHeader extends CdkStepHeader implements OnDestroy {
export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDestroy {
private _intlSubscription: Subscription;

/** State of the given step. */
Expand Down Expand Up @@ -71,10 +72,13 @@ export class MatStepHeader extends CdkStepHeader implements OnDestroy {
_elementRef: ElementRef<HTMLElement>,
changeDetectorRef: ChangeDetectorRef) {
super(_elementRef);
_focusMonitor.monitor(_elementRef, true);
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
}

ngAfterViewInit() {
this._focusMonitor.monitor(this._elementRef, true);
}

ngOnDestroy() {
this._intlSubscription.unsubscribe();
this._focusMonitor.stopMonitoring(this._elementRef);
Expand Down
Loading