Skip to content

Commit 1f4c3ab

Browse files
committed
set up MDCListFoundation
1 parent 1c09ad6 commit 1f4c3ab

File tree

1 file changed

+99
-57
lines changed

1 file changed

+99
-57
lines changed

src/material-experimental/mdc-list/list-base.ts

Lines changed: 99 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@
77
*/
88

99
import {Platform} from '@angular/cdk/platform';
10+
import {DOCUMENT} from '@angular/common';
1011
import {
1112
AfterContentInit,
13+
AfterViewInit,
14+
ContentChildren,
1215
Directive,
1316
ElementRef,
14-
forwardRef,
1517
HostBinding,
18+
HostListener,
19+
Inject,
1620
NgZone,
1721
OnDestroy,
18-
QueryList,
19-
ViewChildren
22+
QueryList
2023
} from '@angular/core';
2124
import {RippleConfig, RippleRenderer, RippleTarget, setLines} from '@angular/material/core';
22-
import {MDCListAdapter} from '@material/list';
25+
import {MDCListAdapter, MDCListFoundation} from '@material/list';
2326
import {Subscription} from 'rxjs';
2427
import {startWith} from 'rxjs/operators';
2528

@@ -31,60 +34,12 @@ function toggleClass(el: Element, className: string, on: boolean) {
3134
}
3235
}
3336

34-
@Directive()
35-
/** @docs-private */
36-
export abstract class MatListBase {
37-
// @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
38-
// metadata is not inherited by child classes, instead the host binding data is defined in a way
39-
// that can be inherited.
40-
// tslint:disable-next-line:no-host-decorator-in-concrete
41-
@HostBinding('class.mdc-list--non-interactive')
42-
_isNonInteractive: boolean = false;
43-
44-
@ViewChildren(forwardRef(() => MatListItemBase)) _items: QueryList<MatListItemBase>;
45-
46-
protected adapter: MDCListAdapter = {
47-
getListItemCount: () => this._items.length,
48-
listItemAtIndexHasClass:
49-
(index, className) => this._itemAtIndex(index)._element.classList.contains(className),
50-
addClassForElementIndex:
51-
(index, className) => this._itemAtIndex(index)._element.classList.add(className),
52-
removeClassForElementIndex:
53-
(index, className) => this._itemAtIndex(index)._element.classList.remove(className),
54-
getAttributeForElementIndex:
55-
(index, attr) => this._itemAtIndex(index)._element.getAttribute(attr),
56-
setAttributeForElementIndex:
57-
(index, attr, value) => this._itemAtIndex(index)._element.setAttribute(attr, value),
58-
setTabIndexForListItemChildren:
59-
(index, value) => this._itemAtIndex(index)._element.tabIndex = value as unknown as number,
60-
getFocusedElementIndex:
61-
() => this._items.map(i => i._element).findIndex(e => e === this.doc?.activeELement),
62-
isFocusInsideList: () => this.element.nativeElement.contains(this.doc?.activeElement),
63-
isRootFocused: () => this.element.nativeElement === this.doc?.activeElement,
64-
focusItemAtIndex: index => this._itemAtIndex(index)._element.focus(),
65-
66-
// The following methods have a dummy implementation in the base class because they are only
67-
// applicable to certain types of lists
68-
hasCheckboxAtIndex: () => false,
69-
hasRadioAtIndex: () => false,
70-
setCheckedCheckboxOrRadioAtIndex: () => {},
71-
isCheckboxCheckedAtIndex: () => false,
72-
notifyAction: () => {},
73-
74-
// TODO(mmalerba): Determine if we need to implement this.
75-
getPrimaryTextAtIndex: () => '',
76-
};
77-
78-
constructor(protected element: ElementRef<HTMLElement>, protected doc: any) {}
79-
80-
private _itemAtIndex(index: number): MatListItemBase {
81-
return this._items.toArray()[index];
82-
}
83-
}
84-
8537
@Directive()
8638
/** @docs-private */
8739
export abstract class MatListItemBase implements AfterContentInit, OnDestroy, RippleTarget {
40+
@HostBinding('tabindex')
41+
_tabIndex = -1;
42+
8843
lines: QueryList<ElementRef<Element>>;
8944

9045
rippleConfig: RippleConfig = {};
@@ -98,8 +53,8 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
9853

9954
private _rippleRenderer: RippleRenderer;
10055

101-
constructor(private _elementRef: ElementRef<HTMLElement>, protected _ngZone: NgZone,
102-
listBase: MatListBase, platform: Platform) {
56+
protected constructor(private _elementRef: ElementRef<HTMLElement>, protected _ngZone: NgZone,
57+
listBase: MatListBase, platform: Platform) {
10358
this._element = this._elementRef.nativeElement;
10459
this.rippleDisabled = listBase._isNonInteractive;
10560
if (!listBase._isNonInteractive) {
@@ -139,3 +94,90 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
13994
this._rippleRenderer._removeTriggerEvents();
14095
}
14196
}
97+
98+
@Directive()
99+
/** @docs-private */
100+
export abstract class MatListBase implements AfterViewInit, OnDestroy {
101+
@HostBinding('class.mdc-list--non-interactive')
102+
_isNonInteractive: boolean = false;
103+
104+
@HostListener('keydown', ['$event'])
105+
_handleKeydown(event: KeyboardEvent) {
106+
const index = this._indexForElement(event.target as HTMLElement);
107+
this._foundation.handleKeydown(
108+
event, this._itemAtIndex(index)._element === event.target, index);
109+
}
110+
111+
@HostListener('click', ['$event'])
112+
_handleClick(event: MouseEvent) {
113+
this._foundation.handleClick(this._indexForElement(event.target as HTMLElement), false);
114+
}
115+
116+
@HostListener('focusin', ['$event'])
117+
_handleFocusin(event: FocusEvent) {
118+
this._foundation.handleFocusIn(event, this._indexForElement(event.target as HTMLElement));
119+
}
120+
121+
@HostListener('focusout', ['$event'])
122+
_handleFocusout(event: FocusEvent) {
123+
this._foundation.handleFocusOut(event, this._indexForElement(event.target as HTMLElement));
124+
}
125+
126+
@ContentChildren(MatListItemBase) _items: QueryList<MatListItemBase>;
127+
128+
protected _adapter: MDCListAdapter = {
129+
getListItemCount: () => this._items.length,
130+
listItemAtIndexHasClass:
131+
(index, className) => this._itemAtIndex(index)._element.classList.contains(className),
132+
addClassForElementIndex:
133+
(index, className) => this._itemAtIndex(index)._element.classList.add(className),
134+
removeClassForElementIndex:
135+
(index, className) => this._itemAtIndex(index)._element.classList.remove(className),
136+
getAttributeForElementIndex:
137+
(index, attr) => this._itemAtIndex(index)._element.getAttribute(attr),
138+
setAttributeForElementIndex:
139+
(index, attr, value) => this._itemAtIndex(index)._element.setAttribute(attr, value),
140+
setTabIndexForListItemChildren:
141+
(index, value) => this._itemAtIndex(index)._element.tabIndex = value as unknown as number,
142+
getFocusedElementIndex: () => this._indexForElement(this._document?.activeELement),
143+
isFocusInsideList: () => this._element.nativeElement.contains(this._document?.activeElement),
144+
isRootFocused: () => this._element.nativeElement === this._document?.activeElement,
145+
focusItemAtIndex: index => this._itemAtIndex(index)._element.focus(),
146+
147+
// The following methods have a dummy implementation in the base class because they are only
148+
// applicable to certain types of lists. They should be implemented for the concrete classes
149+
// where they are applicable.
150+
hasCheckboxAtIndex: () => false,
151+
hasRadioAtIndex: () => false,
152+
setCheckedCheckboxOrRadioAtIndex: () => {},
153+
isCheckboxCheckedAtIndex: () => false,
154+
notifyAction: () => {},
155+
156+
// TODO(mmalerba): Determine if we need to implement this.
157+
getPrimaryTextAtIndex: () => '',
158+
};
159+
160+
protected _foundation: MDCListFoundation;
161+
162+
constructor(protected _element: ElementRef<HTMLElement>,
163+
@Inject(DOCUMENT) protected _document: any) {
164+
this._foundation = new MDCListFoundation(this._adapter);
165+
}
166+
167+
ngAfterViewInit() {
168+
this._foundation.init();
169+
this._foundation.layout();
170+
}
171+
172+
ngOnDestroy() {
173+
this._foundation.destroy();
174+
}
175+
176+
private _itemAtIndex(index: number): MatListItemBase {
177+
return this._items.toArray()[index];
178+
}
179+
180+
private _indexForElement(element: HTMLElement | null) {
181+
return element ? this._items.toArray().findIndex(i => i._element.contains(element)) : -1;
182+
}
183+
}

0 commit comments

Comments
 (0)