Skip to content

fix(material/core): display checkmark for single-selection #25934

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

Closed
wants to merge 1 commit into from
Closed
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
7 changes: 7 additions & 0 deletions src/material/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ng_module(
),
assets = [
":selection/pseudo-checkbox/pseudo-checkbox.css",
":selection/pseudo-checkmark/pseudo-checkmark.css",
":option/option.css",
":option/optgroup.css",
] + glob(["**/*.html"]),
Expand Down Expand Up @@ -81,6 +82,12 @@ sass_binary(
deps = [":core_scss_lib"],
)

sass_binary(
name = "pseudo_checkmark_scss",
src = "selection/pseudo-checkmark/pseudo-checkmark.scss",
deps = [":core_scss_lib"],
)

sass_binary(
name = "option_scss",
src = "option/option.scss",
Expand Down
10 changes: 8 additions & 2 deletions src/material/core/option/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MatRippleModule} from '../ripple/index';
import {MatPseudoCheckboxModule} from '../selection/index';
import {MatPseudoCheckboxModule, MatPseudoCheckmarkModule} from '../selection/index';
import {MatCommonModule} from '../common-behaviors/common-module';
import {MatOption} from './option';
import {MatOptgroup} from './optgroup';

@NgModule({
imports: [MatRippleModule, CommonModule, MatCommonModule, MatPseudoCheckboxModule],
imports: [
MatRippleModule,
CommonModule,
MatCommonModule,
MatPseudoCheckboxModule,
MatPseudoCheckmarkModule,
],
exports: [MatOption, MatOptgroup],
declarations: [MatOption, MatOptgroup],
})
Expand Down
3 changes: 3 additions & 0 deletions src/material/core/option/option.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<mat-pseudo-checkbox *ngIf="multiple" class="mat-mdc-option-pseudo-checkbox"
[state]="selected ? 'checked' : 'unchecked'" [disabled]="disabled"></mat-pseudo-checkbox>

<mat-pseudo-checkmark *ngIf="!multiple" class="mat-mdc-option-pseudo-checkmark"
[state]="selected ? 'checked' : 'unchecked'" [disabled]="disabled"></mat-pseudo-checkmark>

<span class="mdc-list-item__primary-text"><ng-content></ng-content></span>

<!-- See a11y notes inside optgroup.ts for context behind this element. -->
Expand Down
2 changes: 2 additions & 0 deletions src/material/core/selection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@

export * from './pseudo-checkbox/pseudo-checkbox';
export * from './pseudo-checkbox/pseudo-checkbox-module';
export * from './pseudo-checkmark/pseudo-checkmark';
export * from './pseudo-checkmark/pseudo-checkmark-module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@use 'sass:math';
@use '../../style/checkbox-common';

// Padding inside of a pseudo checkmark.
$padding: checkbox-common.$border-width * 2;

/// Applies the styles that set the size of the pseudo checkmark
@mixin size($box-size) {
.mat-pseudo-checkmark {
width: $box-size;
height: $box-size;
}
}

/// Applies the legacy size styles to the pseudo-checkmark
@mixin legacy-size() {
@include size(check-common.$legacy-size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@forward '../../density/private/compatibility' as mat-*;
@forward '../../theming/palette'; // TODO: hide unused colors
@forward '../../theming/palette' as mat-*; // TODO: hide unused colors
@forward '../../theming/theming' as mat-*;
@import '../../theming/theming';
@forward 'pseudo-checkmark-theme' hide color, theme, typography;
@forward 'pseudo-checkmark-theme' as mat-pseudo-checkmark-* hide mat-pseudo-checkmark-density;

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@use 'sass:map';
@use '../../theming/theming';

@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$is-dark-theme: map.get($config, is-dark);
$primary: map.get($config, primary);
$accent: map.get($config, accent);
$warn: map.get($config, warn);

// TODO: implement colors
}

@mixin typography($config-or-theme) {}

@mixin _density($config-or-theme) {}

@mixin theme($theme-or-color-config) {
$theme: theming.private-legacy-get-theme($theme-or-color-config);
@include theming.private-check-duplicate-theme-styles($theme, 'mat-pseudo-checkmark') {
$color: theming.get-color-config($theme);
$density: theming.get-density-config($theme);
$typography: theming.get-typography-config($theme);

@if $color != null {
@include color($color);
}
@if $density != null {
@include _density($density);
}
@if $typography != null {
@include typography($typography);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModule} from '@angular/core';
import {MatPseudoCheckmark} from './pseudo-checkmark';
import {MatCommonModule} from '../../common-behaviors/common-module';

@NgModule({
imports: [MatCommonModule],
exports: [MatPseudoCheckmark],
declarations: [MatPseudoCheckmark],
})
export class MatPseudoCheckmarkModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// TODO: implement styles
@use 'sass:math';
@use '../../style/checkbox-common';
@use '../../style/private';
@use '../../style/variables';
@use './pseudo-checkmark-common';

// TODO: implement a visual checkmark
.mat-pseudo-checkmark {
display: inline-block;
box-sizing: border-box;
position: relative;
flex-shrink: 0;
&::after {
position: absolute;
opacity: 0;
font-family: monospace;
content: '✔️'; // TODO: draw a checkmark with CSS. this is jsut a placeholder
}

&.mat-pseudo-checkmark-checked::after {
opacity: 1;
}
}

.mat-pseudo-checkmark-disabled {
cursor: default;
}

@include pseudo-checkmark-common.size(checkbox-common.$size);
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {
Component,
ViewEncapsulation,
Input,
ChangeDetectionStrategy,
Inject,
Optional,
} from '@angular/core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';

export type MatPseudoCheckmarkState = 'unchecked' | 'checked';

/**
* Component that shows a simplified checkmark without including any kind of "real" checkmark.
* Meant to be used when the checkmark is purely decorative and a large number of them will be
* included, such as for the options in a single-select. Uses no SVGs or complex animations.
* Note that theming is meant to be handled by the parent element, e.g.
* `mat-primary .mat-pseudo-checkmark`.
*
* Note that this component will be completely invisible to screen-reader users. This is *not*
* interchangeable with `<mat-radio>` and should *not* be used if the user would directly
* interact with the checkmark. The pseudo-checkmark should only be used as an implementation detail
* of more complex components that appropriately handle selected / checked state.
* @docs-private
*/
@Component({
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'mat-pseudo-checkmark',
styleUrls: ['pseudo-checkmark.css'],
template: '',
host: {
'class': 'mat-pseudo-checkmark',
'[class.mat-pseudo-checkmark-checked]': 'state === "checked"',
'[class.mat-pseudo-checkmark-disabled]': 'disabled',
'[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"',
},
})
export class MatPseudoCheckmark {
/** Display state of the checkbox. */
@Input() state: MatPseudoCheckmarkState = 'unchecked';

/** Whether the checkbox is disabled. */
@Input() disabled: boolean = false;

constructor(@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) {}
}
28 changes: 27 additions & 1 deletion tools/public_api_guard/material/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export class MatOptionModule {
// (undocumented)
static ɵinj: i0.ɵɵInjectorDeclaration<MatOptionModule>;
// (undocumented)
static ɵmod: i0.ɵɵNgModuleDeclaration<MatOptionModule, [typeof i1_3.MatOption, typeof i2.MatOptgroup], [typeof i3.MatRippleModule, typeof i4.CommonModule, typeof i1_2.MatCommonModule, typeof i6.MatPseudoCheckboxModule], [typeof i1_3.MatOption, typeof i2.MatOptgroup]>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MatOptionModule, [typeof i1_3.MatOption, typeof i2.MatOptgroup], [typeof i3.MatRippleModule, typeof i4.CommonModule, typeof i1_2.MatCommonModule, typeof i6.MatPseudoCheckboxModule, typeof i7.MatPseudoCheckmarkModule], [typeof i1_3.MatOption, typeof i2.MatOptgroup]>;
}

// @public
Expand Down Expand Up @@ -360,6 +360,32 @@ export class MatPseudoCheckboxModule {
// @public
export type MatPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate';

// @public
export class MatPseudoCheckmark {
constructor(_animationMode?: string | undefined);
// (undocumented)
_animationMode?: string | undefined;
disabled: boolean;
state: MatPseudoCheckmarkState;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatPseudoCheckmark, "mat-pseudo-checkmark", never, { "state": "state"; "disabled": "disabled"; }, {}, never, never, false, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatPseudoCheckmark, [{ optional: true; }]>;
}

// @public (undocumented)
export class MatPseudoCheckmarkModule {
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatPseudoCheckmarkModule, never>;
// (undocumented)
static ɵinj: i0.ɵɵInjectorDeclaration<MatPseudoCheckmarkModule>;
// (undocumented)
static ɵmod: i0.ɵɵNgModuleDeclaration<MatPseudoCheckmarkModule, [typeof i1_6.MatPseudoCheckmark], [typeof i1_2.MatCommonModule], [typeof i1_6.MatPseudoCheckmark]>;
}

// @public (undocumented)
export type MatPseudoCheckmarkState = 'unchecked' | 'checked';

// @public (undocumented)
export class MatRipple implements OnInit, OnDestroy, RippleTarget {
constructor(_elementRef: ElementRef<HTMLElement>, ngZone: NgZone, platform: Platform, globalOptions?: RippleGlobalOptions, _animationMode?: string | undefined);
Expand Down