7
7
*/
8
8
9
9
import { Platform } from '@angular/cdk/platform' ;
10
+ import { DOCUMENT } from '@angular/common' ;
10
11
import {
11
12
AfterContentInit ,
13
+ AfterViewInit ,
14
+ ContentChildren ,
12
15
Directive ,
13
16
ElementRef ,
14
17
HostBinding ,
18
+ HostListener ,
19
+ Inject ,
15
20
NgZone ,
16
21
OnDestroy ,
17
22
QueryList
18
23
} from '@angular/core' ;
19
24
import { RippleConfig , RippleRenderer , RippleTarget , setLines } from '@angular/material/core' ;
25
+ import { MDCListAdapter , MDCListFoundation } from '@material/list' ;
20
26
import { Subscription } from 'rxjs' ;
21
27
import { startWith } from 'rxjs/operators' ;
22
28
@@ -28,17 +34,6 @@ function toggleClass(el: Element, className: string, on: boolean) {
28
34
}
29
35
}
30
36
31
- @Directive ( )
32
- /** @docs -private */
33
- export abstract class MatListBase {
34
- // @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
35
- // metadata is not inherited by child classes, instead the host binding data is defined in a way
36
- // that can be inherited.
37
- // tslint:disable-next-line:no-host-decorator-in-concrete
38
- @HostBinding ( 'class.mdc-list--non-interactive' )
39
- _isNonInteractive : boolean = false ;
40
- }
41
-
42
37
@Directive ( )
43
38
/** @docs -private */
44
39
export abstract class MatListItemBase implements AfterContentInit , OnDestroy , RippleTarget {
@@ -53,22 +48,37 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
53
48
54
49
private _rippleRenderer : RippleRenderer ;
55
50
56
- constructor ( protected _element : ElementRef , protected _ngZone : NgZone , listBase : MatListBase ,
57
- platform : Platform ) {
58
- const el = this . _element . nativeElement ;
59
- this . rippleDisabled = listBase . _isNonInteractive ;
60
- if ( ! listBase . _isNonInteractive ) {
61
- el . classList . add ( 'mat-mdc-list-item-interactive' ) ;
62
- }
63
- this . _rippleRenderer =
64
- new RippleRenderer ( this , this . _ngZone , el , platform ) ;
65
- this . _rippleRenderer . setupTriggerEvents ( el ) ;
51
+ protected constructor ( public _elementRef : ElementRef < HTMLElement > , protected _ngZone : NgZone ,
52
+ private _listBase : MatListBase , private _platform : Platform ) {
53
+ this . _initRipple ( ) ;
66
54
}
67
55
68
56
ngAfterContentInit ( ) {
69
57
this . _monitorLines ( ) ;
70
58
}
71
59
60
+ ngOnDestroy ( ) {
61
+ this . _subscriptions . unsubscribe ( ) ;
62
+ this . _rippleRenderer . _removeTriggerEvents ( ) ;
63
+ }
64
+
65
+ _initDefaultTabIndex ( tabIndex : number ) {
66
+ const el = this . _elementRef . nativeElement ;
67
+ if ( ! el . hasAttribute ( 'tabIndex' ) ) {
68
+ el . tabIndex = tabIndex ;
69
+ }
70
+ }
71
+
72
+ private _initRipple ( ) {
73
+ this . rippleDisabled = this . _listBase . _isNonInteractive ;
74
+ if ( ! this . _listBase . _isNonInteractive ) {
75
+ this . _elementRef . nativeElement . classList . add ( 'mat-mdc-list-item-interactive' ) ;
76
+ }
77
+ this . _rippleRenderer =
78
+ new RippleRenderer ( this , this . _ngZone , this . _elementRef . nativeElement , this . _platform ) ;
79
+ this . _rippleRenderer . setupTriggerEvents ( this . _elementRef . nativeElement ) ;
80
+ }
81
+
72
82
/**
73
83
* Subscribes to changes in `MatLine` content children and annotates them appropriately when they
74
84
* change.
@@ -77,20 +87,137 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
77
87
this . _ngZone . runOutsideAngular ( ( ) => {
78
88
this . _subscriptions . add ( this . lines . changes . pipe ( startWith ( this . lines ) )
79
89
. subscribe ( ( lines : QueryList < ElementRef < Element > > ) => {
80
- this . _element . nativeElement . classList
90
+ this . _elementRef . nativeElement . classList
81
91
. toggle ( 'mat-mdc-list-item-single-line' , lines . length <= 1 ) ;
82
92
lines . forEach ( ( line : ElementRef < Element > , index : number ) => {
83
93
toggleClass ( line . nativeElement ,
84
94
'mdc-list-item__primary-text' , index === 0 && lines . length > 1 ) ;
85
95
toggleClass ( line . nativeElement , 'mdc-list-item__secondary-text' , index !== 0 ) ;
86
96
} ) ;
87
- setLines ( lines , this . _element , 'mat-mdc' ) ;
97
+ setLines ( lines , this . _elementRef , 'mat-mdc' ) ;
88
98
} ) ) ;
89
99
} ) ;
90
100
}
101
+ }
102
+
103
+ @Directive ( )
104
+ /** @docs -private */
105
+ export abstract class MatListBase {
106
+ @HostBinding ( 'class.mdc-list--non-interactive' )
107
+ _isNonInteractive : boolean = true ;
108
+ }
109
+
110
+ @Directive ( )
111
+ export abstract class MatInteractiveListBase extends MatListBase
112
+ implements AfterViewInit , OnDestroy {
113
+ @HostListener ( 'keydown' , [ '$event' ] )
114
+ _handleKeydown ( event : KeyboardEvent ) {
115
+ const index = this . _indexForElement ( event . target as HTMLElement ) ;
116
+ this . _foundation . handleKeydown (
117
+ event , this . _elementAtIndex ( index ) === event . target , index ) ;
118
+ }
119
+
120
+ @HostListener ( 'click' , [ '$event' ] )
121
+ _handleClick ( event : MouseEvent ) {
122
+ this . _foundation . handleClick ( this . _indexForElement ( event . target as HTMLElement ) , false ) ;
123
+ }
124
+
125
+ @HostListener ( 'focusin' , [ '$event' ] )
126
+ _handleFocusin ( event : FocusEvent ) {
127
+ this . _foundation . handleFocusIn ( event , this . _indexForElement ( event . target as HTMLElement ) ) ;
128
+ }
129
+
130
+ @HostListener ( 'focusout' , [ '$event' ] )
131
+ _handleFocusout ( event : FocusEvent ) {
132
+ this . _foundation . handleFocusOut ( event , this . _indexForElement ( event . target as HTMLElement ) ) ;
133
+ }
134
+
135
+ @ContentChildren ( MatListItemBase , { descendants : true } ) _items : QueryList < MatListItemBase > ;
136
+
137
+ protected _adapter : MDCListAdapter = {
138
+ getListItemCount : ( ) => this . _items . length ,
139
+ listItemAtIndexHasClass :
140
+ ( index , className ) => this . _elementAtIndex ( index ) . classList . contains ( className ) ,
141
+ addClassForElementIndex :
142
+ ( index , className ) => this . _elementAtIndex ( index ) . classList . add ( className ) ,
143
+ removeClassForElementIndex :
144
+ ( index , className ) => this . _elementAtIndex ( index ) . classList . remove ( className ) ,
145
+ getAttributeForElementIndex : ( index , attr ) => this . _elementAtIndex ( index ) . getAttribute ( attr ) ,
146
+ setAttributeForElementIndex :
147
+ ( index , attr , value ) => this . _elementAtIndex ( index ) . setAttribute ( attr , value ) ,
148
+ getFocusedElementIndex : ( ) => this . _indexForElement ( this . _document ?. activeElement ) ,
149
+ isFocusInsideList : ( ) => this . _element . nativeElement . contains ( this . _document ?. activeElement ) ,
150
+ isRootFocused : ( ) => this . _element . nativeElement === this . _document ?. activeElement ,
151
+ focusItemAtIndex : index => this . _elementAtIndex ( index ) . focus ( ) ,
152
+
153
+ // MDC uses this method to disable focusable children of list items. However, we believe that
154
+ // this is not an accessible pattern and should be avoided, therefore we intentionally do not
155
+ // implement this method. In addition, implementing this would require violating Angular
156
+ // Material's general principle of not having components modify DOM elements they do not own.
157
+ // A user who feels they really need this feature can simply listen to the `(focus)` and
158
+ // `(blur)` events on the list item and enable/disable focus on the children themselves as
159
+ // appropriate.
160
+ setTabIndexForListItemChildren : ( ) => { } ,
161
+
162
+ // The following methods have a dummy implementation in the base class because they are only
163
+ // applicable to certain types of lists. They should be implemented for the concrete classes
164
+ // where they are applicable.
165
+ hasCheckboxAtIndex : ( ) => false ,
166
+ hasRadioAtIndex : ( ) => false ,
167
+ setCheckedCheckboxOrRadioAtIndex : ( ) => { } ,
168
+ isCheckboxCheckedAtIndex : ( ) => false ,
169
+
170
+ // TODO(mmalerba): Determine if we need to implement these.
171
+ getPrimaryTextAtIndex : ( ) => '' ,
172
+ notifyAction : ( ) => { } ,
173
+ } ;
174
+
175
+ protected _foundation : MDCListFoundation ;
176
+
177
+ protected _document : Document ;
178
+
179
+ private _itemsArr : MatListItemBase [ ] = [ ] ;
180
+
181
+ private _subscriptions = new Subscription ( ) ;
182
+
183
+ constructor ( protected _element : ElementRef < HTMLElement > , @Inject ( DOCUMENT ) document : any ) {
184
+ super ( ) ;
185
+ this . _document = document ;
186
+ this . _isNonInteractive = false ;
187
+ this . _foundation = new MDCListFoundation ( this . _adapter ) ;
188
+ }
189
+
190
+ ngAfterViewInit ( ) {
191
+ this . _initItems ( ) ;
192
+ this . _foundation . init ( ) ;
193
+ this . _foundation . layout ( ) ;
194
+ }
91
195
92
196
ngOnDestroy ( ) {
197
+ this . _foundation . destroy ( ) ;
93
198
this . _subscriptions . unsubscribe ( ) ;
94
- this . _rippleRenderer . _removeTriggerEvents ( ) ;
199
+ }
200
+
201
+ private _initItems ( ) {
202
+ this . _subscriptions . add (
203
+ this . _items . changes . pipe ( startWith ( null ) )
204
+ . subscribe ( ( ) => this . _itemsArr = this . _items . toArray ( ) ) ) ;
205
+ for ( let i = 0 ; this . _itemsArr . length ; i ++ ) {
206
+ this . _itemsArr [ i ] . _initDefaultTabIndex ( i === 0 ? 0 : - 1 ) ;
207
+ }
208
+ }
209
+
210
+ private _itemAtIndex ( index : number ) : MatListItemBase {
211
+ return this . _itemsArr [ index ] ;
212
+ }
213
+
214
+ private _elementAtIndex ( index : number ) : HTMLElement {
215
+ return this . _itemAtIndex ( index ) . _elementRef . nativeElement ;
216
+ }
217
+
218
+ private _indexForElement ( element : Element | null ) {
219
+ return element ?
220
+ this . _itemsArr . findIndex ( i => i . _elementRef . nativeElement . contains ( element ) ) : - 1 ;
95
221
}
96
222
}
223
+
0 commit comments