Skip to content

Commit 67e5a00

Browse files
committed
Merge branch 'master' into 10590/expansion-noop-animations
1 parent ae89371 commit 67e5a00

35 files changed

+477
-133
lines changed

src/cdk/a11y/live-announcer/live-announcer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,15 @@ export class CdkAriaLive implements OnDestroy {
118118
this._subscription = null;
119119
}
120120
} else if (!this._subscription) {
121-
this._subscription = this._ngZone.runOutsideAngular(
122-
() => this._contentObserver.observe(this._elementRef).subscribe(
123-
() => this._liveAnnouncer.announce(
124-
this._elementRef.nativeElement.innerText, this._politeness)));
121+
this._subscription = this._ngZone.runOutsideAngular(() => {
122+
return this._contentObserver
123+
.observe(this._elementRef)
124+
.subscribe(() => {
125+
// Note that we use textContent here, rather than innerText, in order to avoid a reflow.
126+
const element = this._elementRef.nativeElement;
127+
this._liveAnnouncer.announce(element.textContent, this._politeness);
128+
});
129+
});
125130
}
126131
}
127132
private _politeness: AriaLivePoliteness = 'off';

src/cdk/overlay/overlay-directives.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ describe('Overlay directives', () => {
232232
expect(backdrop.classList).toContain('mat-test-class');
233233
});
234234

235+
it('should set the custom panel class', () => {
236+
fixture.componentInstance.isOpen = true;
237+
fixture.detectChanges();
238+
239+
const panel
240+
= overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
241+
expect(panel.classList).toContain('cdk-test-panel-class');
242+
});
243+
235244
it('should set the offsetX', () => {
236245
fixture.componentInstance.offsetX = 5;
237246
fixture.componentInstance.isOpen = true;
@@ -478,6 +487,7 @@ describe('Overlay directives', () => {
478487
[cdkConnectedOverlayGrowAfterOpen]="growAfterOpen"
479488
[cdkConnectedOverlayPush]="push"
480489
cdkConnectedOverlayBackdropClass="mat-test-class"
490+
cdkConnectedOverlayPanelClass="cdk-test-panel-class"
481491
(backdropClick)="backdropClickHandler($event)"
482492
[cdkConnectedOverlayOffsetX]="offsetX"
483493
[cdkConnectedOverlayOffsetY]="offsetY"

src/cdk/overlay/overlay-directives.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
158158
/** The custom class to be set on the backdrop element. */
159159
@Input('cdkConnectedOverlayBackdropClass') backdropClass: string;
160160

161+
/** The custom class to add to the overlay pane element. */
162+
@Input('cdkConnectedOverlayPanelClass') panelClass: string | string[];
163+
161164
/** Margin between the overlay and the viewport edges. */
162165
@Input('cdkConnectedOverlayViewportMargin') viewportMargin: number = 0;
163166

@@ -296,6 +299,10 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
296299
overlayConfig.backdropClass = this.backdropClass;
297300
}
298301

302+
if (this.panelClass) {
303+
overlayConfig.panelClass = this.panelClass;
304+
}
305+
299306
return overlayConfig;
300307
}
301308

src/cdk/testing/event-objects.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export function createMouseEvent(type: string, x = 0, y = 0, button = 0) {
2626
button, /* button */
2727
null /* relatedTarget */);
2828

29+
// `initMouseEvent` doesn't allow us to pass the `buttons` and
30+
// defaults it to 0 which looks like a fake event.
31+
Object.defineProperty(event, 'buttons', {get: () => 1});
32+
2933
return event;
3034
}
3135

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -618,20 +618,25 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
618618
.withFlexibleDimensions(false)
619619
.withPush(false)
620620
.withPositions([
621-
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top'},
622-
{originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom'}
621+
{
622+
originX: 'start',
623+
originY: 'bottom',
624+
overlayX: 'start',
625+
overlayY: 'top'
626+
},
627+
{
628+
originX: 'start',
629+
originY: 'top',
630+
overlayX: 'start',
631+
overlayY: 'bottom',
632+
633+
// The overlay edge connected to the trigger should have squared corners, while
634+
// the opposite end has rounded corners. We apply a CSS class to swap the
635+
// border-radius based on the overlay position.
636+
panelClass: 'mat-autocomplete-panel-above'
637+
}
623638
]);
624639

625-
// The overlay edge connected to the trigger should have squared corners, while
626-
// the opposite end has rounded corners. We apply a CSS class to swap the
627-
// border-radius based on the overlay position.
628-
this._positionStrategy.positionChanges.subscribe(({connectionPair}) => {
629-
if (this.autocomplete) {
630-
this.autocomplete._classList['mat-autocomplete-panel-above'] =
631-
connectionPair.originY === 'top';
632-
}
633-
});
634-
635640
return this._positionStrategy;
636641
}
637642

src/lib/autocomplete/autocomplete.scss

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ $mat-autocomplete-panel-border-radius: 4px !default;
2626
visibility: hidden;
2727
}
2828

29+
.mat-autocomplete-panel-above & {
30+
border-radius: 0;
31+
border-top-left-radius: $mat-autocomplete-panel-border-radius;
32+
border-top-right-radius: $mat-autocomplete-panel-border-radius;
33+
}
34+
2935
@include cdk-high-contrast {
3036
outline: solid 1px;
3137
}
3238
}
3339

34-
.mat-autocomplete-panel-above {
35-
border-radius: 0;
36-
border-top-left-radius: $mat-autocomplete-panel-border-radius;
37-
border-top-right-radius: $mat-autocomplete-panel-border-radius;
38-
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,8 +1441,7 @@ describe('MatAutocomplete', () => {
14411441
expect(Math.floor(inputTop))
14421442
.toEqual(Math.floor(panelBottom), `Expected panel to fall back to above position.`);
14431443

1444-
expect(panel.querySelector('.mat-autocomplete-panel')!.classList)
1445-
.toContain('mat-autocomplete-panel-above');
1444+
expect(panel.classList).toContain('mat-autocomplete-panel-above');
14461445
}));
14471446

14481447
it('should allow the panel to expand when the number of results increases', fakeAsync(() => {

src/lib/button-toggle/button-toggle.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,15 @@ describe('MatButtonToggle without forms', () => {
277277
expect(buttonToggleInstances[0].checked).toBe(true);
278278
});
279279

280+
it('should set aria-disabled based on whether the group is disabled', () => {
281+
expect(groupNativeElement.getAttribute('aria-disabled')).toBe('false');
282+
283+
testComponent.isGroupDisabled = true;
284+
fixture.detectChanges();
285+
286+
expect(groupNativeElement.getAttribute('aria-disabled')).toBe('true');
287+
});
288+
280289
it('should update the group value when one of the toggles changes', () => {
281290
expect(groupInstance.value).toBeFalsy();
282291
buttonToggleLabelElements[0].click();

src/lib/button-toggle/button-toggle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class MatButtonToggleChange {
8585
host: {
8686
'role': 'group',
8787
'class': 'mat-button-toggle-group',
88+
'[attr.aria-disabled]': 'disabled',
8889
'[class.mat-button-toggle-vertical]': 'vertical'
8990
},
9091
exportAs: 'matButtonToggleGroup',
@@ -98,7 +99,7 @@ export class MatButtonToggleGroup extends _MatButtonToggleGroupMixinBase impleme
9899

99100
/**
100101
* Reference to the raw value that the consumer tried to assign. The real
101-
* value will exaclude any values from this one that don't correspond to a
102+
* value will exclude any values from this one that don't correspond to a
102103
* toggle. Useful for the cases where the value is assigned before the toggles
103104
* have been initialized or at the same that they're being swapped out.
104105
*/

src/lib/chips/chips.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ $mat-chip-remove-size: 18px;
151151
align-items: center;
152152
display: flex;
153153
overflow: hidden;
154+
155+
// Makes `<img>` tags behave like `background-size: cover`. Not supported
156+
// in IE, but we're using it as a progressive enhancement.
157+
object-fit: cover;
154158
}
155159

156160
input.mat-chip-input {

src/lib/core/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ng_module(
1717
":option/optgroup.css",
1818
] + glob(["**/*.html"]),
1919
deps = [
20+
"//src/cdk/a11y",
2021
"//src/cdk/bidi",
2122
"//src/cdk/coercion",
2223
"//src/cdk/keycodes",

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {ElementRef, NgZone} from '@angular/core';
99
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
10+
import {isFakeMousedownFromScreenReader} from '@angular/cdk/a11y';
1011
import {RippleRef, RippleState} from './ripple-ref';
1112

1213
export type RippleConfig = {
@@ -246,10 +247,13 @@ export class RippleRenderer {
246247

247248
/** Function being called whenever the trigger is being pressed using mouse. */
248249
private onMousedown = (event: MouseEvent) => {
250+
// Screen readers will fire fake mouse events for space/enter. Skip launching a
251+
// ripple in this case for consistency with the non-screen-reader experience.
252+
const isFakeMousedown = isFakeMousedownFromScreenReader(event);
249253
const isSyntheticEvent = this._lastTouchStartEvent &&
250254
Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout;
251255

252-
if (!this._target.rippleDisabled && !isSyntheticEvent) {
256+
if (!this._target.rippleDisabled && !isFakeMousedown && !isSyntheticEvent) {
253257
this._isPointerDown = true;
254258
this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig);
255259
}

src/lib/core/ripple/ripple.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createTouchEvent,
77
dispatchMouseEvent,
88
dispatchTouchEvent,
9+
createMouseEvent,
910
} from '@angular/cdk/testing';
1011
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
1112
import {
@@ -166,6 +167,15 @@ describe('MatRipple', () => {
166167
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
167168
}));
168169

170+
it('should ignore fake mouse events from screen readers', () => fakeAsync(() => {
171+
const event = createMouseEvent('mousedown');
172+
Object.defineProperty(event, 'buttons', {get: () => 0});
173+
174+
dispatchEvent(rippleTarget, event);
175+
tick(enterDuration);
176+
expect(rippleTarget.querySelector('.mat-ripple-element')).toBeFalsy();
177+
}));
178+
169179
it('removes ripple after timeout', fakeAsync(() => {
170180
dispatchMouseEvent(rippleTarget, 'mousedown');
171181
dispatchMouseEvent(rippleTarget, 'mouseup');

src/lib/datepicker/calendar-body.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class MatCalendarCell {
4242
host: {
4343
'class': 'mat-calendar-body',
4444
'role': 'grid',
45-
'attr.aria-readonly': 'true'
45+
'aria-readonly': 'true'
4646
},
4747
exportAs: 'matCalendarBody',
4848
encapsulation: ViewEncapsulation.None,

src/lib/expansion/accordion-base.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '@angular/core';
10+
import {CdkAccordion} from '@angular/cdk/accordion';
11+
12+
/** MatAccordion's display modes. */
13+
export type MatAccordionDisplayMode = 'default' | 'flat';
14+
15+
/**
16+
* Base interface for a `MatAccordion`.
17+
* @docs-private
18+
*/
19+
export interface MatAccordionBase extends CdkAccordion {
20+
/** Whether the expansion indicator should be hidden. */
21+
hideToggle: boolean;
22+
23+
/** Display mode used for all expansion panels in the accordion. */
24+
displayMode: MatAccordionDisplayMode;
25+
26+
/** Handles keyboard events coming in from the panel headers. */
27+
_handleHeaderKeydown: (event: KeyboardEvent) => void;
28+
29+
/** Handles focus events on the panel headers. */
30+
_handleHeaderFocus: (header: any) => void;
31+
}
32+
33+
34+
/**
35+
* Token used to provide a `MatAccordion` to `MatExpansionPanel`.
36+
* Used primarily to avoid circular imports between `MatAccordion` and `MatExpansionPanel`.
37+
*/
38+
export const MAT_ACCORDION = new InjectionToken<MatAccordionBase>('MAT_ACCORDION');

0 commit comments

Comments
 (0)