Skip to content

Commit 5fab515

Browse files
committed
feat: handle group and item renderring dynamically based on search
1 parent 9de269e commit 5fab515

19 files changed

+201
-96
lines changed

projects/ngneat/cmdk/src/lib/cmdk.module.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InputDirective } from './directives/input/input.directive';
33
import { EmptyDirective } from './directives/empty/empty.directive';
44
import { CommandComponent } from './components/command/command.component';
55
import { DynamicViewModule } from '@ngneat/overview';
6-
import { ListComponent } from './components/list/list.component';
6+
// import { ListComponent } from './components/list/list.component';
77
import { GroupComponent } from './components/group/group.component';
88
import { SeparatorComponent } from './components/separator/separator.component';
99
import { CommonModule } from '@angular/common';
@@ -14,7 +14,7 @@ import { ItemComponent } from './components/item/item.component';
1414
CommandComponent,
1515
InputDirective,
1616
EmptyDirective,
17-
ListComponent,
17+
// ListComponent,
1818
GroupComponent,
1919
SeparatorComponent,
2020
ItemComponent,
@@ -24,7 +24,7 @@ import { ItemComponent } from './components/item/item.component';
2424
CommandComponent,
2525
InputDirective,
2626
EmptyDirective,
27-
ListComponent,
27+
// ListComponent,
2828
GroupComponent,
2929
SeparatorComponent,
3030
ItemComponent,

projects/ngneat/cmdk/src/lib/cmdk.service.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,8 @@ import { Subject } from 'rxjs';
55
export class CmdkService {
66
private _searchSub = new Subject<string | undefined>();
77
search$ = this._searchSub.asObservable();
8-
private _isEmptySub = new Subject<boolean>();
9-
isEmpty$ = this._isEmptySub.asObservable();
108

119
setSearch(value: string | undefined) {
1210
this._searchSub.next(value);
1311
}
14-
15-
setIsEmpty(value: boolean) {
16-
this._isEmptySub.next(value);
17-
}
1812
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
<!-- <ng-template #cmdkContent> -->
2-
<div class="cmdk-command" role="tree" [attr.aria-label]="ariaLabel" [id]="panelId" #panel>
1+
<ng-template #cmdkContent>
2+
<div class="cmdk-command" role="listbox" [attr.aria-label]="ariaLabel" [id]="panelId" #panel>
33
<ng-content></ng-content>
4+
<ng-container *ngFor="let cmdkItem of filteredItems" [ngTemplateOutlet]="cmdkItem.templateRef"></ng-container>
45
</div>
5-
<!-- </ng-template> -->
6+
</ng-template>
67

7-
<!-- <ng-container *dynamicView="cmdkContent"></ng-container> -->
8+
<ng-container *dynamicView="cmdkContent"></ng-container>

projects/ngneat/cmdk/src/lib/components/command/command.component.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import {
1313
AfterContentInit,
1414
ElementRef,
1515
inject,
16+
ChangeDetectorRef,
1617
} from '@angular/core';
1718
import { Content } from '@ngneat/overview';
1819
import { UntilDestroy } from '@ngneat/until-destroy';
1920
import { filter } from 'rxjs';
2021
import { CmdkService } from '../../cmdk.service';
22+
import { EmptyDirective } from '../../directives/empty/empty.directive';
2123
import { CmdkCommandProps } from '../../types';
2224
import { ItemComponent } from '../item/item.component';
2325
import { ListComponent } from '../list/list.component';
@@ -33,7 +35,9 @@ let commandId = 0;
3335
encapsulation: ViewEncapsulation.None,
3436
exportAs: 'cmdkCommand',
3537
})
36-
export class CommandComponent implements CmdkCommandProps, OnInit {
38+
export class CommandComponent
39+
implements CmdkCommandProps, OnInit, AfterContentInit
40+
{
3741
@Input() label?: Content;
3842
@Input() ariaLabel?: string;
3943
@Input() shouldFilter?: boolean;
@@ -53,31 +57,36 @@ export class CommandComponent implements CmdkCommandProps, OnInit {
5357
| QueryList<ListComponent>
5458
| undefined;
5559
/** Reference to all list-items within the cmdk-command. */
56-
@ContentChildren(ItemComponent, { descendants: true }) items:
57-
| QueryList<ItemComponent>
60+
@ContentChildren(ItemComponent) items!: QueryList<ItemComponent>;
61+
/** Reference to all empty-items within the cmdk-command. */
62+
@ContentChildren(EmptyDirective) emptyList:
63+
| QueryList<EmptyDirective>
5864
| undefined;
5965

60-
allItems = new Set<string>(); // [...itemIds];
61-
allGroups = new Map<string, Set<string>>(); // groupId → [...itemIds]
62-
ids = new Map<string, string>(); // id → value
66+
filteredItems: ItemComponent[] = [];
6367

6468
private _cmdkService = inject(CmdkService);
6569

6670
readonly panelId = `cmdk-command-${commandId++}`;
6771

72+
constructor(private _cdr: ChangeDetectorRef) {}
73+
6874
ngOnInit() {
69-
this._cmdkService.search$
70-
.pipe(filter((s) => s !== undefined))
71-
.subscribe((s) => this.handleSearch(s!));
75+
this._cmdkService.search$.subscribe((s) => this.handleSearch(s));
76+
}
77+
78+
ngAfterContentInit(): void {
79+
this.handleSearch('');
7280
}
7381

74-
handleSearch(search: string) {
75-
this.items?.forEach((item) => {
76-
if (item.value && item.value.search(search) >= 0) {
77-
item.visible = true;
78-
} else {
79-
item.visible = false;
82+
handleSearch(search = '') {
83+
this.filteredItems = this.items.filter((item) => {
84+
if (!search) {
85+
return true;
8086
}
87+
const filterValue = search.toLowerCase();
88+
return item.value.toLowerCase().includes(filterValue);
8189
});
90+
this._cdr.markForCheck();
8291
}
8392
}
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
<div role="presentation" *ngIf="label">{{ label }}</div>
2-
<div class="cmdk-group" role="group" [attr.aria-label]="ariaLabel">
3-
<ng-content></ng-content>
4-
</div>
1+
<ng-template #cmdkGroup>
2+
<ng-container *ngIf="filteredItems.length">
3+
<div role="presentation" *ngIf="label" class="cmdk-group-label">{{ label }}</div>
4+
<div class="cmdk-group" role="group" [attr.aria-label]="ariaLabel">
5+
<ng-container *ngFor="let cmdkItem of filteredItems" [ngTemplateOutlet]="cmdkItem.templateRef"></ng-container>
6+
</div>
7+
</ng-container>
8+
</ng-template>
9+
10+
<ng-container *dynamicView="cmdkGroup"></ng-container>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.cmdk-group-label {
2+
font-weight: bold;
3+
}
4+
Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,53 @@
1-
import { Component, Input } from '@angular/core';
1+
import {
2+
AfterContentInit,
3+
AfterViewInit,
4+
ChangeDetectionStrategy,
5+
ChangeDetectorRef,
6+
Component,
7+
ContentChildren,
8+
inject,
9+
Input,
10+
OnInit,
11+
QueryList,
12+
} from '@angular/core';
213
import { Content } from '@ngneat/overview';
14+
import { CmdkService } from '../../cmdk.service';
315
import { CmdkGroupProps } from '../../types';
16+
import { ItemComponent } from '../item/item.component';
417

518
@Component({
619
selector: 'cmdk-group',
720
templateUrl: './group.component.html',
821
styleUrls: ['./group.component.scss'],
22+
changeDetection: ChangeDetectionStrategy.OnPush,
923
})
10-
export class GroupComponent implements CmdkGroupProps {
24+
export class GroupComponent
25+
implements CmdkGroupProps, OnInit, AfterContentInit
26+
{
1127
@Input() label?: Content;
1228
@Input() ariaLabel?: string;
29+
@ContentChildren(ItemComponent) items!: QueryList<ItemComponent>;
30+
private _cmdkService = inject(CmdkService);
31+
filteredItems: ItemComponent[] = [];
32+
33+
constructor(private _cdr: ChangeDetectorRef) {}
34+
35+
ngOnInit() {
36+
this._cmdkService.search$.subscribe((s) => this.handleSearch(s));
37+
}
38+
39+
ngAfterContentInit(): void {
40+
this.handleSearch('');
41+
}
42+
43+
handleSearch(search = '') {
44+
this.filteredItems = this.items.filter((item) => {
45+
if (!search) {
46+
return true;
47+
}
48+
const filterValue = search.toLowerCase();
49+
return item.value.toLowerCase().includes(filterValue);
50+
});
51+
this._cdr.markForCheck();
52+
}
1353
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
<ng-container *ngIf="visible">
2-
<div class="cmdk-item" role="button" #item>
3-
<ng-content></ng-content>
4-
</div>
5-
</ng-container>
1+
<ng-template #cmdkItem>
2+
<div class="cmdk-item" role="option" #item>
3+
<ng-content></ng-content>
4+
</div>
5+
</ng-template>
6+
7+
<!-- <ng-container *dynamicView="cmdkItem"></ng-container> -->
Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
1-
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
1+
import {
2+
AfterViewInit,
3+
ChangeDetectionStrategy,
4+
ChangeDetectorRef,
5+
Component,
6+
ElementRef,
7+
HostBinding,
8+
inject,
9+
Input,
10+
OnInit,
11+
TemplateRef,
12+
ViewChild,
13+
} from '@angular/core';
14+
import { CmdkService } from '../../cmdk.service';
215

316
@Component({
417
selector: 'cmdk-item',
518
templateUrl: './item.component.html',
619
styleUrls: ['./item.component.scss'],
20+
changeDetection: ChangeDetectionStrategy.OnPush,
721
})
8-
export class ItemComponent {
9-
@ViewChild('item') item: ElementRef | undefined;
22+
export class ItemComponent implements OnInit {
23+
@ViewChild('item') item!: ElementRef;
24+
@ViewChild(TemplateRef, { static: true }) templateRef!: TemplateRef<any>;
1025

11-
private _value: string | undefined;
26+
private _value: string = '';
1227
@Input()
13-
set value(value: string | undefined) {
28+
set value(value: string) {
1429
this._value = value;
1530
}
1631
get value() {
17-
return this._value ?? this.item?.nativeElement.textContent;
32+
return this._value ?? this.item.nativeElement.textContent;
1833
}
1934

20-
private _visible = true;
21-
@Input()
22-
set visible(value: boolean) {
23-
this._visible = value;
24-
}
25-
get visible() {
26-
return this._visible;
35+
private _cmdkService = inject(CmdkService);
36+
37+
constructor(public cdr: ChangeDetectorRef) {}
38+
39+
ngOnInit() {
40+
this._cmdkService.search$.subscribe((s) => this.handleSearch(s));
2741
}
42+
43+
handleSearch(search = '') {}
2844
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
<div class="cmdk-list" role="group" [attr.aria-label]="ariaLabel" *ngIf="hasContent">
2-
<ng-content></ng-content>
3-
</div>
1+
<ng-template #cmdkList>
2+
<div class="cmdk-list" role="group" [attr.aria-label]="ariaLabel">
3+
<ng-content [select]="parentGroup ? 'cmdk-group' : 'cmdk-item'"></ng-content>
4+
</div>
5+
</ng-template>
6+
7+
<ng-container *dynamicView="cmdkList"></ng-container>

0 commit comments

Comments
 (0)