Skip to content

Commit 95acccc

Browse files
crisbetommalerba
authored andcommitted
feat(checkbox): align with 2018 material design spec (#12493)
Aligns the checkbox component with the latest Material design spec.
1 parent 79801e0 commit 95acccc

File tree

11 files changed

+80
-95
lines changed

11 files changed

+80
-95
lines changed

e2e/components/checkbox-e2e.spec.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,10 @@ describe('checkbox', () => {
1414
expect(inputEl.getAttribute('checked'))
1515
.toBeTruthy('Expect checkbox "checked" property to be true');
1616

17-
await browser.wait(ExpectedConditions.not(
18-
ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element')))));
19-
2017
checkboxEl.click();
2118

2219
expect(inputEl.getAttribute('checked'))
2320
.toBeFalsy('Expect checkbox "checked" property to be false');
24-
25-
await browser.wait(ExpectedConditions.not(
26-
ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element')))));
2721
});
2822

2923
it('should toggle the checkbox when pressing space', () => {

src/lib/checkbox/_checkbox-theme.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
}
6161

6262
.mat-checkbox-disabled {
63-
&.mat-checkbox-checked, &.mat-checkbox-indeterminate {
63+
&.mat-checkbox-checked:not(.mat-checkbox-indeterminate) {
6464
.mat-checkbox-background {
6565
background-color: $disabled-color;
6666
}
@@ -92,15 +92,15 @@
9292

9393
.mat-checkbox:not(.mat-checkbox-disabled) {
9494
&.mat-primary .mat-checkbox-ripple .mat-ripple-element {
95-
background-color: mat-color($primary, 0.26);
95+
background-color: mat-color($primary);
9696
}
9797

9898
&.mat-accent .mat-checkbox-ripple .mat-ripple-element {
99-
background-color: mat-color($accent, 0.26);
99+
background-color: mat-color($accent);
100100
}
101101

102102
&.mat-warn .mat-checkbox-ripple .mat-ripple-element {
103-
background-color: mat-color($warn, 0.26);
103+
background-color: mat-color($warn);
104104
}
105105
}
106106
}

src/lib/checkbox/checkbox.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
<div matRipple class="mat-checkbox-ripple"
2020
[matRippleTrigger]="label"
2121
[matRippleDisabled]="_isRippleDisabled()"
22-
[matRippleRadius]="25"
22+
[matRippleRadius]="20"
2323
[matRippleCentered]="true"
2424
[matRippleAnimation]="{enterDuration: 150}">
25+
<div class="mat-ripple-element mat-checkbox-persistent-ripple"></div>
2526
</div>
2627
<div class="mat-checkbox-frame"></div>
2728
<div class="mat-checkbox-background">

src/lib/checkbox/checkbox.scss

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ $_mat-checkbox-mark-path-length: 22.910259;
1111
$_mat-checkbox-indeterminate-checked-easing-function: cubic-bezier(0.14, 0, 0, 1);
1212

1313
// The ripple size of the checkbox
14-
$_mat-checkbox-ripple-radius: 25px;
14+
$_mat-checkbox-ripple-radius: 20px;
1515

1616
// The amount of spacing between the checkbox and its label.
1717
$_mat-checkbox-item-spacing: $mat-toggle-padding;
@@ -164,13 +164,6 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
164164
}
165165
}
166166

167-
// Applied to elements that are considered "marks" within the checkbox, e.g. the checkmark and
168-
// the mixedmark.
169-
%mat-checkbox-mark {
170-
$width-padding-inset: 2 * $mat-checkbox-border-width;
171-
width: calc(100% - #{$width-padding-inset});
172-
}
173-
174167
// Applied to elements that appear to make up the outer box of the checkmark, such as the frame
175168
// that contains the border and the actual background element that contains the marks.
176169
%mat-checkbox-outer-box {
@@ -189,6 +182,10 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
189182

190183
cursor: pointer;
191184
-webkit-tap-highlight-color: transparent;
185+
186+
.mat-ripple-element:not(.mat-checkbox-persistent-ripple) {
187+
opacity: 0.16;
188+
}
192189
}
193190

194191
.mat-checkbox-layout {
@@ -260,10 +257,29 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
260257
}
261258
}
262259

260+
.mat-checkbox-persistent-ripple {
261+
width: 100%;
262+
height: 100%;
263+
transform: none;
264+
265+
.mat-checkbox-inner-container:hover & {
266+
opacity: 0.04;
267+
}
268+
269+
.mat-checkbox.cdk-focused & {
270+
opacity: 0.12;
271+
}
272+
273+
// We do this here, rather than having a `:not(.mat-checkbox-disabled)`
274+
// above in the `:hover`, because the `:not` will bump the specificity
275+
// a lot and will cause it to overide the focus styles.
276+
&, .mat-checkbox.mat-disabled .mat-checkbox-inner-container:hover & {
277+
opacity: 0;
278+
}
279+
}
280+
263281
.mat-checkbox-checkmark {
264282
@include mat-fill;
265-
@extend %mat-checkbox-mark;
266-
267283
width: 100%;
268284
}
269285

@@ -278,10 +294,11 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
278294
.mat-checkbox-mixedmark {
279295
$height: floor($_mat-checkbox-mark-stroke-size);
280296

281-
@extend %mat-checkbox-mark;
297+
width: calc(100% - 6px);
282298
height: $height;
283299
opacity: 0;
284300
transform: scaleX(0) rotate(0deg);
301+
border-radius: 2px;
285302

286303
@include cdk-high-contrast {
287304
height: 0;
@@ -335,6 +352,12 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
335352
opacity: 1;
336353
transform: scaleX(1) rotate(0deg);
337354
}
355+
356+
&.mat-checkbox-disabled {
357+
.mat-checkbox-inner-container {
358+
opacity: 0.5;
359+
}
360+
}
338361
}
339362

340363

src/lib/checkbox/checkbox.spec.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
ComponentFixture,
33
fakeAsync,
44
TestBed,
5-
tick,
65
flush,
76
flushMicrotasks,
87
} from '@angular/core/testing';
@@ -11,7 +10,6 @@ import {Component, DebugElement, ViewChild, Type} from '@angular/core';
1110
import {By} from '@angular/platform-browser';
1211
import {dispatchFakeEvent} from '@angular/cdk/testing';
1312
import {MatCheckbox, MatCheckboxChange, MatCheckboxModule} from './index';
14-
import {defaultRippleAnimationConfig} from '@angular/material/core';
1513
import {MAT_CHECKBOX_CLICK_ACTION} from './checkbox-config';
1614
import {MutationObserverFactory} from '@angular/cdk/observers';
1715

@@ -379,74 +377,59 @@ describe('MatCheckbox', () => {
379377
expect(inputElement.value).toBe('basic_checkbox');
380378
});
381379

382-
it('should show a ripple when focused by a keyboard action', fakeAsync(() => {
383-
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
384-
.toBe(0, 'Expected no ripples on load.');
385-
386-
dispatchFakeEvent(inputElement, 'keydown');
387-
dispatchFakeEvent(inputElement, 'focus');
388-
389-
tick(defaultRippleAnimationConfig.enterDuration);
390-
391-
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
392-
.toBe(1, 'Expected ripple after element is focused.');
393-
394-
dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
395-
tick(defaultRippleAnimationConfig.exitDuration);
396-
397-
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
398-
.toBe(0, 'Expected no ripple after element is blurred.');
399-
}));
400-
401380
it('should remove the SVG checkmark from the tab order', () => {
402381
expect(checkboxNativeElement.querySelector('svg')!.getAttribute('focusable')).toBe('false');
403382
});
404383

405384
describe('ripple elements', () => {
406385

407386
it('should show ripples on label mousedown', () => {
408-
expect(checkboxNativeElement.querySelector('.mat-ripple-element')).toBeFalsy();
387+
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
388+
389+
expect(checkboxNativeElement.querySelector(rippleSelector)).toBeFalsy();
409390

410391
dispatchFakeEvent(labelElement, 'mousedown');
411392
dispatchFakeEvent(labelElement, 'mouseup');
412393

413-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
394+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
414395
});
415396

416397
it('should not show ripples when disabled', () => {
398+
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
417399
testComponent.isDisabled = true;
418400
fixture.detectChanges();
419401

420402
dispatchFakeEvent(labelElement, 'mousedown');
421403
dispatchFakeEvent(labelElement, 'mouseup');
422404

423-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
405+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
424406

425407
testComponent.isDisabled = false;
426408
fixture.detectChanges();
427409

428410
dispatchFakeEvent(labelElement, 'mousedown');
429411
dispatchFakeEvent(labelElement, 'mouseup');
430412

431-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
413+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
432414
});
433415

434416
it('should remove ripple if matRippleDisabled input is set', () => {
417+
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
435418
testComponent.disableRipple = true;
436419
fixture.detectChanges();
437420

438421
dispatchFakeEvent(labelElement, 'mousedown');
439422
dispatchFakeEvent(labelElement, 'mouseup');
440423

441-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
424+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
442425

443426
testComponent.disableRipple = false;
444427
fixture.detectChanges();
445428

446429
dispatchFakeEvent(labelElement, 'mousedown');
447430
dispatchFakeEvent(labelElement, 'mouseup');
448431

449-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
432+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
450433
});
451434
});
452435

@@ -842,19 +825,20 @@ describe('MatCheckbox', () => {
842825
});
843826

844827
it('should toggle checkbox ripple disabledness correctly', () => {
828+
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
845829
const labelElement = checkboxNativeElement.querySelector('label') as HTMLLabelElement;
846830

847831
testComponent.isDisabled = true;
848832
fixture.detectChanges();
849833
dispatchFakeEvent(labelElement, 'mousedown');
850834
dispatchFakeEvent(labelElement, 'mouseup');
851-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
835+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
852836

853837
testComponent.isDisabled = false;
854838
fixture.detectChanges();
855839
dispatchFakeEvent(labelElement, 'mousedown');
856840
dispatchFakeEvent(labelElement, 'mouseup');
857-
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
841+
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
858842
});
859843
});
860844

src/lib/checkbox/checkbox.ts

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

9-
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
9+
import {FocusMonitor} from '@angular/cdk/a11y';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {
12-
AfterViewChecked,
13-
AfterViewInit,
1412
Attribute,
1513
ChangeDetectionStrategy,
1614
ChangeDetectorRef,
@@ -26,6 +24,7 @@ import {
2624
Output,
2725
ViewChild,
2826
ViewEncapsulation,
27+
AfterViewChecked,
2928
} from '@angular/core';
3029
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
3130
import {
@@ -42,7 +41,6 @@ import {
4241
mixinDisabled,
4342
mixinDisableRipple,
4443
mixinTabIndex,
45-
RippleRef,
4644
} from '@angular/material/core';
4745
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
4846
import {MAT_CHECKBOX_CLICK_ACTION, MatCheckboxClickAction} from './checkbox-config';
@@ -133,8 +131,7 @@ export const _MatCheckboxMixinBase:
133131
changeDetection: ChangeDetectionStrategy.OnPush
134132
})
135133
export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAccessor,
136-
AfterViewChecked, AfterViewInit, OnDestroy, CanColor, CanDisable, HasTabIndex,
137-
CanDisableRipple {
134+
AfterViewChecked, OnDestroy, CanColor, CanDisable, HasTabIndex, CanDisableRipple {
138135

139136
/**
140137
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
@@ -195,9 +192,6 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
195192

196193
private _controlValueAccessorChangeFn: (value: any) => void = () => {};
197194

198-
/** Reference to the focused state ripple. */
199-
private _focusRipple: RippleRef | null;
200-
201195
constructor(elementRef: ElementRef<HTMLElement>,
202196
private _changeDetectorRef: ChangeDetectorRef,
203197
private _focusMonitor: FocusMonitor,
@@ -209,20 +203,25 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
209203
super(elementRef);
210204

211205
this.tabIndex = parseInt(tabIndex) || 0;
212-
}
213206

214-
ngAfterViewInit() {
215-
this._focusMonitor
216-
.monitor(this._inputElement)
217-
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
207+
this._focusMonitor.monitor(elementRef, true).subscribe(focusOrigin => {
208+
if (!focusOrigin) {
209+
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
210+
// Angular does not expect events to be raised during change detection, so any state change
211+
// (such as a form control's 'ng-touched') will cause a changed-after-checked error.
212+
// See https://github.com/angular/angular/issues/17793. To work around this, we defer
213+
// telling the form control it has been touched until the next tick.
214+
Promise.resolve().then(() => this._onTouched());
215+
}
216+
});
218217
}
219218

220219
ngAfterViewChecked() {
221220
this._calculateRippleRadius();
222221
}
223222

224223
ngOnDestroy() {
225-
this._focusMonitor.stopMonitoring(this._inputElement);
224+
this._focusMonitor.stopMonitoring(this._elementRef);
226225
}
227226

228227
/**
@@ -344,34 +343,14 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
344343
}
345344

346345
private _emitChangeEvent() {
347-
let event = new MatCheckboxChange();
346+
const event = new MatCheckboxChange();
348347
event.source = this;
349348
event.checked = this.checked;
350349

351350
this._controlValueAccessorChangeFn(this.checked);
352351
this.change.emit(event);
353352
}
354353

355-
/** Function is called whenever the focus changes for the input element. */
356-
private _onInputFocusChange(focusOrigin: FocusOrigin) {
357-
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
358-
if (!this._focusRipple && focusOrigin === 'keyboard') {
359-
this._focusRipple = this.ripple.launch(0, 0, {persistent: true});
360-
} else if (!focusOrigin) {
361-
if (this._focusRipple) {
362-
this._focusRipple.fadeOut();
363-
this._focusRipple = null;
364-
}
365-
366-
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
367-
// Angular does not expect events to be raised during change detection, so any state change
368-
// (such as a form control's 'ng-touched') will cause a changed-after-checked error.
369-
// See https://github.com/angular/angular/issues/17793. To work around this, we defer telling
370-
// the form control it has been touched until the next tick.
371-
Promise.resolve().then(() => this._onTouched());
372-
}
373-
}
374-
375354
/** Toggles the `checked` state of the checkbox. */
376355
toggle(): void {
377356
this.checked = !this.checked;

src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
background: mat-color(map-get($theme, warn));
4444
}
4545

46-
.mat-pseudo-checkbox-checked,
47-
.mat-pseudo-checkbox-indeterminate {
46+
.mat-pseudo-checkbox-checked {
4847
&.mat-pseudo-checkbox-disabled {
4948
background: $disabled-color;
5049
}

0 commit comments

Comments
 (0)