@@ -3,63 +3,201 @@ import {
3
3
EventEmitter ,
4
4
Input ,
5
5
Output ,
6
- ViewEncapsulation ,
7
6
ChangeDetectionStrategy ,
8
7
ContentChildren ,
9
8
QueryList ,
10
9
inject ,
11
10
ContentChild ,
12
- AfterContentInit ,
11
+ HostListener ,
12
+ AfterViewInit ,
13
+ OnChanges ,
14
+ SimpleChanges ,
13
15
} from '@angular/core' ;
14
16
import { Content } from '@ngneat/overview' ;
15
17
import { UntilDestroy , untilDestroyed } from '@ngneat/until-destroy' ;
18
+ import { first } from 'rxjs' ;
16
19
import { CmdkService } from '../../cmdk.service' ;
17
20
import { EmptyDirective } from '../../directives/empty/empty.directive' ;
18
21
import { ItemDirective } from '../../directives/item/item.directive' ;
19
22
import { CmdkCommandProps } from '../../types' ;
23
+ import { GroupComponent } from '../group/group.component' ;
24
+ import { SeparatorComponent } from '../separator/separator.component' ;
20
25
21
26
let commandId = 0 ;
22
- @UntilDestroy ( { checkProperties : true } )
27
+ @UntilDestroy ( )
23
28
@Component ( {
24
29
selector : 'cmdk-command' ,
25
30
templateUrl : './command.component.html' ,
26
31
styleUrls : [ './command.component.scss' ] ,
27
32
providers : [ CmdkService ] ,
28
33
changeDetection : ChangeDetectionStrategy . OnPush ,
29
- encapsulation : ViewEncapsulation . None ,
30
34
exportAs : 'cmdkCommand' ,
31
35
} )
32
- export class CommandComponent implements CmdkCommandProps , AfterContentInit {
36
+ export class CommandComponent
37
+ implements CmdkCommandProps , AfterViewInit , OnChanges
38
+ {
39
+ @Output ( ) valueChanged = new EventEmitter < string > ( ) ;
40
+ @Input ( ) value ?: string ;
33
41
@Input ( ) label ?: Content ;
34
42
@Input ( ) ariaLabel ?: string ;
35
- @Input ( ) shouldFilter = true ;
36
- @Input ( ) filter ?: ( value : string , search : string ) => boolean ;
37
- @Input ( ) value ?: string ;
38
- @Output ( ) valueChanged = new EventEmitter < string > ( ) ;
43
+ @Input ( ) filter : ( ( value : string , search : string ) => boolean ) | null = (
44
+ value ,
45
+ search
46
+ ) => {
47
+ const searchValue = search . toLowerCase ( ) ;
48
+ return value . toLowerCase ( ) . includes ( searchValue ) ;
49
+ } ;
39
50
40
51
@ContentChildren ( ItemDirective , { descendants : true } )
41
52
items : QueryList < ItemDirective > | undefined ;
53
+ @ContentChildren ( GroupComponent , { descendants : true } )
54
+ groups : QueryList < GroupComponent > | undefined ;
55
+ @ContentChildren ( SeparatorComponent , { descendants : true } )
56
+ separators : QueryList < SeparatorComponent > | undefined ;
42
57
@ContentChild ( EmptyDirective ) empty ! : EmptyDirective ;
43
58
44
59
readonly panelId = `cmdk-command-${ commandId ++ } ` ;
45
60
46
61
private cmdkService = inject ( CmdkService ) ;
47
62
48
- ngAfterContentInit ( ) {
49
- if ( this . shouldFilter ) {
63
+ ngOnChanges ( changes : SimpleChanges ) {
64
+ if (
65
+ changes [ 'value' ] &&
66
+ changes [ 'value' ] . previousValue !== changes [ 'value' ] . currentValue &&
67
+ this . value
68
+ ) {
69
+ this . setValue ( this . value ) ;
70
+ }
71
+ }
72
+
73
+ ngAfterViewInit ( ) {
74
+ if ( this . filter ) {
50
75
this . cmdkService . search$
51
76
. pipe ( untilDestroyed ( this ) )
52
- . subscribe ( ( ) => this . handleSearch ( ) ) ;
77
+ . subscribe ( ( s ) => this . handleSearch ( s ) ) ;
53
78
}
54
- if ( this . items ) {
55
- this . items . first . active = true ;
79
+
80
+ if ( ! this . value ) {
81
+ this . makeFirstItemActive ( ) ;
82
+ this . makeFirstGroupActive ( ) ;
56
83
}
84
+
85
+ this . cmdkService . value$
86
+ . pipe ( untilDestroyed ( this ) )
87
+ . subscribe ( ( value ) => this . valueChanged . emit ( value ) ) ;
88
+
89
+ this . cmdkService . activeItem$
90
+ . pipe ( untilDestroyed ( this ) )
91
+ . subscribe ( ( itemId ) => {
92
+ this . setActiveGroupForActiveItem ( itemId ) ;
93
+ } ) ;
94
+ }
95
+
96
+ get filteredItems ( ) {
97
+ return this . items ?. filter ( ( item ) => item . filtered ) ;
98
+ }
99
+
100
+ get filteredGroups ( ) {
101
+ return this . groups ?. filter ( ( group ) => group . filtered ) ;
57
102
}
58
103
59
- handleSearch ( ) {
60
- if ( this . items ) {
61
- this . empty . cmdkEmpty = ! this . items . some ( ( item ) => item . filtered ) ;
62
- this . items . first . active = true ;
104
+ handleSearch ( search : string ) {
105
+ if ( this . items ?. length ) {
106
+ // filter items
107
+ this . items ?. forEach ( ( item ) => {
108
+ item . filtered = this . filter ? this . filter ( item . value , search ) : true ;
109
+ } ) ;
110
+
111
+ // show/hide empty directive
112
+ this . empty . cmdkEmpty = this . filteredItems ?. length === 0 ;
113
+
114
+ // make first item active and in-turn it will also make first group active, if available
115
+ this . makeFirstItemActive ( ) ;
116
+
117
+ // show/hide group
118
+ this . groups ?. forEach ( ( group ) => {
119
+ group . showGroup = group . filteredItems ?. length > 0 ;
120
+ group . _cdr . markForCheck ( ) ;
121
+ } ) ;
122
+
123
+ // hide separator if search and filter both are present, else show
124
+ this . separators ?. forEach ( ( seperator ) => {
125
+ seperator . showSeparator = ! ( this . filter && search ) ;
126
+ seperator . cdr . markForCheck ( ) ;
127
+ } ) ;
128
+ }
129
+ }
130
+
131
+ @HostListener ( 'keyup' , [ '$event' ] )
132
+ onKeyUp ( ev : KeyboardEvent ) {
133
+ if ( ev . key === 'ArrowDown' ) {
134
+ this . makeNextItemActive ( ) ;
135
+ } else if ( ev . key === 'ArrowUp' ) {
136
+ this . makePreviousItemActive ( ) ;
137
+ }
138
+ }
139
+
140
+ private makeFirstItemActive ( ) {
141
+ setTimeout ( ( ) => {
142
+ const firstItem = this . filteredItems ?. [ 0 ] ;
143
+ if ( firstItem ) {
144
+ this . cmdkService . setActiveItem ( firstItem . itemId ) ;
145
+ }
146
+ } ) ;
147
+ }
148
+
149
+ private makeFirstGroupActive ( ) {
150
+ setTimeout ( ( ) => {
151
+ const firstGroup = this . filteredGroups ?. [ 0 ] ;
152
+ if ( firstGroup ) {
153
+ this . cmdkService . setActiveGroup ( firstGroup . groupId ) ;
154
+ }
155
+ } ) ;
156
+ }
157
+ private makePreviousItemActive ( ) {
158
+ this . cmdkService . activeItem$
159
+ . pipe ( first ( ) , untilDestroyed ( this ) )
160
+ . subscribe ( ( activeItemId ) => {
161
+ if ( this . filteredItems ?. length ) {
162
+ const activeItemIndex = this . filteredItems . findIndex (
163
+ ( item ) => item . itemId === activeItemId
164
+ ) ;
165
+ const nextActiveItem = this . filteredItems [ activeItemIndex - 1 ] ;
166
+ if ( nextActiveItem ) {
167
+ const nextActiveItemId = nextActiveItem . itemId ;
168
+ this . cmdkService . setActiveItem ( nextActiveItemId ) ;
169
+ }
170
+ }
171
+ } ) ;
172
+ }
173
+
174
+ private setActiveGroupForActiveItem ( nextActiveItemId : string ) {
175
+ const nextActiveGroupId = this . filteredGroups ?. find ( ( group ) =>
176
+ group . filteredItems . some ( ( item ) => item . itemId === nextActiveItemId )
177
+ ) ?. groupId ;
178
+ if ( nextActiveGroupId ) {
179
+ this . cmdkService . setActiveGroup ( nextActiveGroupId ) ;
63
180
}
64
181
}
182
+
183
+ private makeNextItemActive ( ) {
184
+ this . cmdkService . activeItem$
185
+ . pipe ( first ( ) , untilDestroyed ( this ) )
186
+ . subscribe ( ( activeItemId ) => {
187
+ if ( this . filteredItems ?. length ) {
188
+ const activeItemIndex = this . filteredItems . findIndex (
189
+ ( item ) => item . itemId === activeItemId
190
+ ) ;
191
+ const nextActiveItem = this . filteredItems [ activeItemIndex + 1 ] ;
192
+ if ( nextActiveItem ) {
193
+ const nextActiveItemId = nextActiveItem . itemId ;
194
+ this . cmdkService . setActiveItem ( nextActiveItemId ) ;
195
+ }
196
+ }
197
+ } ) ;
198
+ }
199
+
200
+ private setValue ( value : string ) {
201
+ this . cmdkService . setValue ( value ) ;
202
+ }
65
203
}
0 commit comments