Skip to content

fix(list): allow for list and list items to be disabled #17584

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
Jan 24, 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
6 changes: 5 additions & 1 deletion src/material/list/list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ mat-action-list {
font: inherit;
outline: inherit;
-webkit-tap-highlight-color: transparent;

text-align: left;

[dir='rtl'] & {
text-align: right;
}
Expand All @@ -300,6 +300,10 @@ mat-action-list {
outline: none;
}

.mat-list-item-disabled {
pointer-events: none;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also disable tooltips, I think we'd want to avoid

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we prevent clicking on it though? We've done something similar to support disabled tabs in the tab nav bar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the tabs thing is why I thought about this (someone mentioned it in a bug). I don't know of another way to do this other than preventDefault / stopPropagation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't rely on preventDefault/stopPropagation, because we don't have a way of ensuring that our event listeners are invoked before the user's.

}

@include cdk-high-contrast {
.mat-selection-list:focus {
outline-style: dotted;
Expand Down
48 changes: 46 additions & 2 deletions src/material/list/list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('MatList', () => {
ListWithOneAnchorItem, ListWithOneItem, ListWithTwoLineItem, ListWithThreeLineItem,
ListWithAvatar, ListWithItemWithCssClass, ListWithDynamicNumberOfLines,
ListWithMultipleItems, ListWithManyLines, NavListWithOneAnchorItem, ActionListWithoutType,
ActionListWithType, ListWithIndirectDescendantLines
ActionListWithType, ListWithIndirectDescendantLines, ListWithDisabledItems,
],
});

Expand Down Expand Up @@ -275,8 +275,40 @@ describe('MatList', () => {
expect(listItems[0].nativeElement.className).toContain('mat-2-line');
expect(listItems[1].nativeElement.className).toContain('mat-2-line');
});
});

it('should be able to disable a single list item', () => {
const fixture = TestBed.createComponent(ListWithDisabledItems);
const listItems: HTMLElement[] =
Array.from(fixture.nativeElement.querySelectorAll('mat-list-item'));
fixture.detectChanges();

expect(listItems.map(item => {
return item.classList.contains('mat-list-item-disabled');
})).toEqual([false, false, false]);

fixture.componentInstance.firstItemDisabled = true;
fixture.detectChanges();

expect(listItems.map(item => {
return item.classList.contains('mat-list-item-disabled');
})).toEqual([true, false, false]);
});

it('should be able to disable the entire list', () => {
const fixture = TestBed.createComponent(ListWithDisabledItems);
const listItems: HTMLElement[] =
Array.from(fixture.nativeElement.querySelectorAll('mat-list-item'));
fixture.detectChanges();

expect(listItems.every(item => item.classList.contains('mat-list-item-disabled'))).toBe(false);

fixture.componentInstance.listDisabled = true;
fixture.detectChanges();

expect(listItems.every(item => item.classList.contains('mat-list-item-disabled'))).toBe(true);
});

});

class BaseTestList {
items: any[] = [
Expand Down Expand Up @@ -425,3 +457,15 @@ class ListWithMultipleItems extends BaseTestList { }
})
class ListWithIndirectDescendantLines extends BaseTestList {
}


@Component({template: `
<mat-list [disabled]="listDisabled">
<mat-list-item [disabled]="firstItemDisabled">One</mat-list-item>
<mat-list-item>Two</mat-list-item>
<mat-list-item>Three</mat-list-item>
</mat-list>`})
class ListWithDisabledItems {
firstItemDisabled = false;
listDisabled = false;
}
33 changes: 25 additions & 8 deletions src/material/list/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {BooleanInput} from '@angular/cdk/coercion';
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';
import {
AfterContentInit,
ChangeDetectionStrategy,
Expand All @@ -21,22 +21,26 @@ import {
OnChanges,
OnDestroy,
ChangeDetectorRef,
Input,
} from '@angular/core';
import {
CanDisable,
CanDisableCtor,
CanDisableRipple,
CanDisableRippleCtor,
MatLine,
setLines,
mixinDisableRipple,
mixinDisabled,
} from '@angular/material/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

// Boilerplate for applying mixins to MatList.
/** @docs-private */
class MatListBase {}
const _MatListMixinBase: CanDisableRippleCtor & typeof MatListBase =
mixinDisableRipple(MatListBase);
const _MatListMixinBase: CanDisableRippleCtor & CanDisableCtor & typeof MatListBase =
mixinDisabled(mixinDisableRipple(MatListBase));

// Boilerplate for applying mixins to MatListItem.
/** @docs-private */
Expand All @@ -53,12 +57,12 @@ const _MatListItemMixinBase: CanDisableRippleCtor & typeof MatListItemBase =
},
templateUrl: 'list.html',
styleUrls: ['list.css'],
inputs: ['disableRipple'],
inputs: ['disableRipple', 'disabled'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges,
OnDestroy {
export class MatNavList extends _MatListMixinBase implements CanDisable, CanDisableRipple,
OnChanges, OnDestroy {
/** Emits when the state of the list changes. */
_stateChanges = new Subject<void>();

Expand All @@ -71,6 +75,7 @@ export class MatNavList extends _MatListMixinBase implements CanDisableRipple, O
}

static ngAcceptInputType_disableRipple: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;
}

@Component({
Expand All @@ -81,11 +86,12 @@ export class MatNavList extends _MatListMixinBase implements CanDisableRipple, O
'class': 'mat-list mat-list-base'
},
styleUrls: ['list.css'],
inputs: ['disableRipple'],
inputs: ['disableRipple', 'disabled'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
export class MatList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges,
OnDestroy {
/** Emits when the state of the list changes. */
_stateChanges = new Subject<void>();

Expand Down Expand Up @@ -120,6 +126,7 @@ export class MatList extends _MatListMixinBase implements CanDisableRipple, OnCh
}

static ngAcceptInputType_disableRipple: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;
}

/**
Expand Down Expand Up @@ -158,6 +165,7 @@ export class MatListSubheaderCssMatStyler {}
exportAs: 'matListItem',
host: {
'class': 'mat-list-item',
'[class.mat-list-item-disabled]': 'disabled',
// @breaking-change 8.0.0 Remove `mat-list-item-avatar` in favor of `mat-list-item-with-avatar`.
'[class.mat-list-item-avatar]': '_avatar || _icon',
'[class.mat-list-item-with-avatar]': '_avatar || _icon',
Expand Down Expand Up @@ -202,6 +210,14 @@ export class MatListItem extends _MatListItemMixinBase implements AfterContentIn
}
}

/** Whether the option is disabled. */
@Input()
get disabled() { return this._disabled || !!(this._list && this._list.disabled); }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled = false;

ngAfterContentInit() {
setLines(this._lines, this._element);
}
Expand All @@ -223,4 +239,5 @@ export class MatListItem extends _MatListItemMixinBase implements AfterContentIn
}

static ngAcceptInputType_disableRipple: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;
}
15 changes: 10 additions & 5 deletions tools/public_api_guard/material/list.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
export declare const MAT_SELECTION_LIST_VALUE_ACCESSOR: any;

export declare class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
export declare class MatList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges, OnDestroy {
_stateChanges: Subject<void>;
constructor(_elementRef: ElementRef<HTMLElement>);
_getListType(): 'list' | 'action-list' | null;
ngOnChanges(): void;
ngOnDestroy(): void;
static ngAcceptInputType_disableRipple: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatList, "mat-list, mat-action-list", ["matList"], { "disableRipple": "disableRipple"; }, {}, never>;
static ngAcceptInputType_disabled: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatList, "mat-list, mat-action-list", ["matList"], { "disableRipple": "disableRipple"; "disabled": "disabled"; }, {}, never>;
static ɵfac: i0.ɵɵFactoryDef<MatList>;
}

Expand All @@ -25,13 +26,16 @@ export declare class MatListItem extends _MatListItemMixinBase implements AfterC
_avatar: MatListAvatarCssMatStyler;
_icon: MatListIconCssMatStyler;
_lines: QueryList<MatLine>;
get disabled(): boolean;
set disabled(value: boolean);
constructor(_element: ElementRef<HTMLElement>, _changeDetectorRef: ChangeDetectorRef, navList?: MatNavList, list?: MatList);
_getHostElement(): HTMLElement;
_isRippleDisabled(): boolean;
ngAfterContentInit(): void;
ngOnDestroy(): void;
static ngAcceptInputType_disableRipple: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatListItem, "mat-list-item, a[mat-list-item], button[mat-list-item]", ["matListItem"], { "disableRipple": "disableRipple"; }, {}, ["_avatar", "_icon", "_lines"]>;
static ngAcceptInputType_disabled: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatListItem, "mat-list-item, a[mat-list-item], button[mat-list-item]", ["matListItem"], { "disableRipple": "disableRipple"; "disabled": "disabled"; }, {}, ["_avatar", "_icon", "_lines"]>;
static ɵfac: i0.ɵɵFactoryDef<MatListItem>;
}

Expand Down Expand Up @@ -82,12 +86,13 @@ export declare class MatListSubheaderCssMatStyler {
static ɵfac: i0.ɵɵFactoryDef<MatListSubheaderCssMatStyler>;
}

export declare class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
export declare class MatNavList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges, OnDestroy {
_stateChanges: Subject<void>;
ngOnChanges(): void;
ngOnDestroy(): void;
static ngAcceptInputType_disableRipple: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatNavList, "mat-nav-list", ["matNavList"], { "disableRipple": "disableRipple"; }, {}, never>;
static ngAcceptInputType_disabled: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatNavList, "mat-nav-list", ["matNavList"], { "disableRipple": "disableRipple"; "disabled": "disabled"; }, {}, never>;
static ɵfac: i0.ɵɵFactoryDef<MatNavList>;
}

Expand Down