Skip to content

fix(material/list): add radio toggles #25933

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
Nov 22, 2022
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
26 changes: 13 additions & 13 deletions src/dev-app/list/list-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,21 @@ <h2>Selection list</h2>
color="primary">
<div mat-subheader>Groceries</div>

<mat-list-option value="bananas" checkboxPosition="before">Bananas</mat-list-option>
<mat-list-option selected value="oranges">Oranges</mat-list-option>
<mat-list-option value="apples" color="accent">Apples</mat-list-option>
<mat-list-option value="strawberries" color="warn">Strawberries</mat-list-option>
<mat-list-option value="bananas" togglePosition="before">Bananas</mat-list-option>
<mat-list-option selected value="oranges" color="accent">Oranges</mat-list-option>
<mat-list-option value="apples" color="warn">Apples</mat-list-option>
<mat-list-option value="strawberries" disabled>Strawberries</mat-list-option>
</mat-selection-list>

<mat-selection-list [disableRipple]="selectionListRippleDisabled">
<div mat-subheader>Dogs</div>

<mat-list-option checkboxPosition="before">
<mat-list-option togglePosition="before">
<img matListItemAvatar src="https://material.angular.io/assets/img/examples/shiba1.jpg">
<span matListItemTitle>Shiba Inu</span>
</mat-list-option>

<mat-list-option checkboxPosition="after">
<mat-list-option togglePosition="after">
<img matListItemAvatar src="https://material.angular.io/assets/img/examples/shiba2.jpg">
<span matListItemTitle>Other Shiba Inu</span>
</mat-list-option>
Expand Down Expand Up @@ -177,9 +177,9 @@ <h2>Single Selection list</h2>
<div mat-subheader>Favorite Grocery</div>

<mat-list-option value="bananas">Bananas</mat-list-option>
<mat-list-option selected value="oranges">Oranges</mat-list-option>
<mat-list-option value="apples">Apples</mat-list-option>
<mat-list-option value="strawberries" color="warn">Strawberries</mat-list-option>
<mat-list-option selected value="oranges" color="accent">Oranges</mat-list-option>
<mat-list-option value="apples" color="warn">Apples</mat-list-option>
<mat-list-option value="strawberries" disabled>Strawberries</mat-list-option>
</mat-selection-list>

<p>Selected: {{favoriteOptions | json}}</p>
Expand Down Expand Up @@ -239,19 +239,19 @@ <h2>Line alignment</h2>
<h2>Icon alignment in selection list</h2>

<mat-selection-list>
<mat-list-option value="bananas" [checkboxPosition]="checkboxPosition">
<mat-list-option value="bananas" [togglePosition]="togglePosition">
<mat-icon matListItemIcon>info</mat-icon>
Bananas
</mat-list-option>
<mat-list-option value="oranges" [checkboxPosition]="checkboxPosition">
<mat-list-option value="oranges" [togglePosition]="togglePosition">
<mat-icon matListItemIcon #ok>info</mat-icon>
Oranges
</mat-list-option>
<mat-list-option value="cake" [checkboxPosition]="checkboxPosition">
<mat-list-option value="cake" [togglePosition]="togglePosition">
<mat-icon matListItemIcon>info</mat-icon>
Cake
</mat-list-option>
<mat-list-option value="fries" [checkboxPosition]="checkboxPosition">
<mat-list-option value="fries" [togglePosition]="togglePosition">
<mat-icon matListItemIcon>info</mat-icon>
Fries
</mat-list-option>
Expand Down
6 changes: 3 additions & 3 deletions src/dev-app/list/list-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatListModule, MatListOptionCheckboxPosition} from '@angular/material/list';
import {MatListModule, MatListOptionTogglePosition} from '@angular/material/list';
import {MatIconModule} from '@angular/material/icon';
import {CommonModule} from '@angular/common';

Expand All @@ -23,7 +23,7 @@ import {CommonModule} from '@angular/common';
export class ListDemo {
items: string[] = ['Pepper', 'Salt', 'Paprika'];

checkboxPosition: MatListOptionCheckboxPosition = 'before';
togglePosition: MatListOptionTogglePosition = 'before';

contacts: {name: string; headline: string}[] = [
{name: 'Nancy', headline: 'Software engineer'},
Expand Down Expand Up @@ -75,7 +75,7 @@ export class ListDemo {
}

toggleCheckboxPosition() {
this.checkboxPosition = this.checkboxPosition === 'before' ? 'after' : 'before';
this.togglePosition = this.togglePosition === 'before' ? 'after' : 'before';
}

favoriteOptions: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/material/legacy-list/selection-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class MatLegacySelectionListChange {
/**
* Type describing possible positions of a checkbox in a list option
* with respect to the list item's text.
* @deprecated Use `MatListOptionCheckboxPosition` from `@angular/material/list` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
* @deprecated Use `MatListOptionTogglePosition` from `@angular/material/list` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
* @breaking-change 17.0.0
*/
export type MatLegacyListOptionCheckboxPosition = 'before' | 'after';
Expand Down
1 change: 1 addition & 0 deletions src/material/list/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ sass_library(
"//:mdc_sass_lib",
"//src/material/checkbox:checkbox_scss_lib",
"//src/material/core:core_scss_lib",
"//src/material/radio:radio_scss_lib",
],
)

Expand Down
9 changes: 6 additions & 3 deletions src/material/list/_list-option-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
@use '../core/mdc-helpers/mdc-helpers';
@use '../checkbox/checkbox-private';
@use './list-option-trailing-avatar-compat';
@use '../radio/radio-private';

// Mixin that overrides the selected item and checkbox colors for list options. By
// default, the MDC list uses the `primary` color for list items. The MDC checkbox
// inside list options by default uses the `primary` color too.
// Mixin that overrides the selected item and toggle indicator colors for list
// options. By default, the MDC list uses the `primary` color for list items.
// The MDC radio/checkbox inside list options by default uses the `primary`
// color too.
@mixin private-list-option-color-override($color-config, $color, $mdc-color) {
& .mdc-list-item__start, & .mdc-list-item__end {
@include checkbox-private.private-checkbox-styles-with-color($color-config, $color, $mdc-color);
@include radio-private.private-radio-color($color-config, $color);
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/material/list/list-item-sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ export class MatListItemMeta {}
/**
* @docs-private
*
* MDC uses the very intuitively named classes `.mdc-list-item__start` and `.mat-list-item__end`
* to position content such as icons or checkboxes that comes either before or after the text
* content respectively. This directive detects the placement of the checkbox and applies the
* MDC uses the very intuitively named classes `.mdc-list-item__start` and `.mat-list-item__end` to
* position content such as icons or checkboxes/radios that comes either before or after the text
* content respectively. This directive detects the placement of the checkbox/radio and applies the
* correct MDC class to position the icon/avatar on the opposite side.
*/
@Directive({
host: {
// MDC uses intuitively named classes `.mdc-list-item__start` and `.mat-list-item__end`
// to position content such as icons or checkboxes that comes either before or after the text
// content respectively. This directive detects the placement of the checkbox and applies the
// correct MDC class to position the icon/avatar on the opposite side.
// MDC uses intuitively named classes `.mdc-list-item__start` and `.mat-list-item__end` to
// position content such as icons or checkboxes/radios that comes either before or after the
// text content respectively. This directive detects the placement of the checkbox/radio and
// applies the correct MDC class to position the icon/avatar on the opposite side.
'[class.mdc-list-item__start]': '_isAlignedAtStart()',
'[class.mdc-list-item__end]': '!_isAlignedAtStart()',
},
Expand All @@ -72,8 +72,8 @@ export class _MatListItemGraphicBase {

_isAlignedAtStart() {
// By default, in all list items the graphic is aligned at start. In list options,
// the graphic is only aligned at start if the checkbox is at the end.
return !this._listOption || this._listOption?._getCheckboxPosition() === 'after';
// the graphic is only aligned at start if the checkbox/radio is at the end.
return !this._listOption || this._listOption?._getTogglePosition() === 'after';
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/material/list/list-option-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
import {InjectionToken} from '@angular/core';

/**
* Type describing possible positions of a checkbox in a list option
* Type describing possible positions of a checkbox or radio in a list option
* with respect to the list item's text.
*/
export type MatListOptionCheckboxPosition = 'before' | 'after';
export type MatListOptionTogglePosition = 'before' | 'after';

/**
* Interface describing a list option. This is used to avoid circular
* dependencies between the list-option and the styler directives.
* @docs-private
*/
export interface ListOption {
_getCheckboxPosition(): MatListOptionCheckboxPosition;
_getTogglePosition(): MatListOptionTogglePosition;
}

/**
Expand Down
22 changes: 21 additions & 1 deletion src/material/list/list-option.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
Save icons and the pseudo checkbox so that they can be re-used in the template without
Save icons and the pseudo checkbox/radio so that they can be re-used in the template without
duplication. Also content can only be injected once so we need to extract icons/avatars
into a template since we use it in multiple places.
-->
Expand All @@ -25,11 +25,27 @@
</div>
</ng-template>

<ng-template #radio>
<div class="mdc-radio" [class.mdc-radio--disabled]="disabled">
<input type="radio" class="mdc-radio__native-control"
[checked]="selected" [disabled]="disabled"/>
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
</div>
</div>
</ng-template>

<!-- Container for the checkbox at start. -->
<span class="mdc-list-item__start mat-mdc-list-option-checkbox-before"
*ngIf="_hasCheckboxAt('before')">
<ng-template [ngTemplateOutlet]="checkbox"></ng-template>
</span>
<!-- Container for the radio at the start. -->
<span class="mdc-list-item__start mat-mdc-list-option-radio-before"
*ngIf="_hasRadioAt('before')">
<ng-template [ngTemplateOutlet]="radio"></ng-template>
</span>
<!-- Conditionally renders icons/avatars before the list item text. -->
<ng-template [ngIf]="_hasIconsOrAvatarsAt('before')">
<ng-template [ngTemplateOutlet]="icons"></ng-template>
Expand All @@ -49,6 +65,10 @@
<span class="mdc-list-item__end" *ngIf="_hasCheckboxAt('after')">
<ng-template [ngTemplateOutlet]="checkbox"></ng-template>
</span>
<!-- Container for the radio at the end. -->
<span class="mdc-list-item__end" *ngIf="_hasRadioAt('after')">
<ng-template [ngTemplateOutlet]="radio"></ng-template>
</span>
<!-- Conditionally renders icons/avatars after the list item text. -->
<ng-template [ngIf]="_hasIconsOrAvatarsAt('after')">
<ng-template [ngTemplateOutlet]="icons"></ng-template>
Expand Down
47 changes: 33 additions & 14 deletions src/material/list/list-option.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
@use 'sass:map';
@use '@material/checkbox/checkbox' as mdc-checkbox;
@use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme;
@use '@material/radio/radio' as mdc-radio;
@use '@material/radio/radio-theme' as mdc-radio-theme;

@use '../core/mdc-helpers/mdc-helpers';
@use '../checkbox/checkbox-private';
@use '../radio/radio-private';
@use './list-option-trailing-avatar-compat';
@use './list-item-hcm-indicator';

Expand All @@ -12,42 +15,58 @@
@include list-option-trailing-avatar-compat.core-styles($query: mdc-helpers.$mdc-base-styles-query);

.mat-mdc-list-option {
// The MDC-based list-option uses the MDC checkbox for the selection indicators.
// We need to ensure that the checkbox styles are not included for the list-option.
// The MDC-based list-option uses the MDC checkbox/radio for the selection indicators.
// We need to ensure that the checkbox and radio styles are not included for the list-option.
@include mdc-helpers.disable-mdc-fallback-declarations {
@include mdc-checkbox.static-styles(
$query: mdc-helpers.$mdc-base-styles-without-animation-query);
@include mdc-radio.static-styles(
$query: mdc-helpers.$mdc-base-styles-without-animation-query);

&:not(._mat-animation-noopable) {
@include mdc-checkbox.static-styles($query: animation);
@include mdc-radio.static-styles($query: animation);
}
}

// We can't use the MDC checkbox here directly, because this checkbox is purely
// decorative and including the MDC one will bring in unnecessary JS.
.mdc-checkbox {
$config: map.merge(checkbox-private.$private-checkbox-theme-config, (
// Since this checkbox isn't interactive, we can exclude the focus/hover/press styles.
$without-ripple-config: (
// Since this checkbox/radio isn't interactive, we can exclude the focus/hover/press styles.
selected-focus-icon-color: null,
selected-hover-icon-color: null,
selected-pressed-icon-color: null,
unselected-focus-icon-color: null,
unselected-hover-icon-color: null,
unselected-pressed-icon-color: null,
));
);

// We can't use the MDC checkbox here directly, because this checkbox is purely
// decorative and including the MDC one will bring in unnecessary JS.
.mdc-checkbox {
$config: map.merge(checkbox-private.$private-checkbox-theme-config, $without-ripple-config);

// MDC theme styles also include structural styles so we have to include the theme at least
// once here. The values will be overwritten by our own theme file afterwards.
@include mdc-checkbox-theme.theme-styles($config);
}

// The internal checkbox is purely decorative, but because it's an `input`, the user can still
// focus it by tabbing or clicking. Furthermore, `mat-list-option` has the `option` role which
// doesn't allow a nested `input`. We use `display: none` both to remove it from the tab order
// and to prevent focus from reaching it through the screen reader's forms mode. Ideally we'd
// remove the `input` completely, but we can't because MDC uses a `:checked` selector to
// We can't use the MDC radio here directly, because this radio is purely
// decorative and including the MDC one will bring in unnecessary JS.
.mdc-radio {
$config: map.merge(radio-private.$private-radio-theme-config, $without-ripple-config);

// MDC theme styles also include structural styles so we have to include the theme at least
// once here. The values will be overwritten by our own theme file afterwards.
@include mdc-radio-theme.theme-styles($config);
}


// The internal checkbox/radio is purely decorative, but because it's an `input`, the user can
// still focus it by tabbing or clicking. Furthermore, `mat-list-option` has the `option` role
// which doesn't allow a nested `input`. We use `display: none` both to remove it from the tab
// order and to prevent focus from reaching it through the screen reader's forms mode. Ideally
// we'd remove the `input` completely, but we can't because MDC uses a `:checked` selector to
// toggle the selected styles.
.mdc-checkbox__native-control {
.mdc-checkbox__native-control, .mdc-radio__native-control {
display: none;
}
}
Expand Down
Loading