Skip to content

Commit dd3f70d

Browse files
ChronosSFdesig9stein
authored andcommitted
feat(combo): Exported filter strategy for non-diacritic filter (#13071) (#13298)
1 parent d9953a1 commit dd3f70d

File tree

4 files changed

+89
-32
lines changed

4 files changed

+89
-32
lines changed

CHANGELOG.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ All notable changes for each version of this project will be documented in this
3434
```html
3535
<igx-badge icon="heart-monitor" iconSet="imx-icons"></igx-badge>
3636
```
37+
- `IgxCombo`:
38+
- Exposed `comboIgnoreDiacriticsFilter` filter function which normalizes diacritics to their base representation.
39+
When the combo components are configured with it, filtering for **"resume"** will match both **"resume"** and **"résumé"**.
3740

3841
### General
3942
- `IgxStepper`:
@@ -48,11 +51,11 @@ All notable changes for each version of this project will be documented in this
4851
- All Ignite UI for Angular components are now exported as `standalone` components. The library still exports `NgModules`, which have been preserved for backward compatibility, but they no longer declare any of the Ignite UI for Angular components, instead they just import and export the `standalone` components. The `standalone` components are still in a preview stage. Some utility directive exports may change in the future and may be missing from the documentation in the initial release, hence the `preview` state of the feature.
4952

5053
Now you can do:
51-
54+
5255
```typescript
5356
// IGX_GRID_DIRECTIVES exports all grid related components and directives
5457
import { IGX_GRID_DIRECTIVES } from 'igniteui-angular';
55-
58+
5659
@Component({
5760
selector: 'app-grid-sample',
5861
styleUrls: ['grid.sample.scss'],
@@ -61,13 +64,13 @@ All notable changes for each version of this project will be documented in this
6164
imports: [IGX_GRID_DIRECTIVES, AsyncPipe]
6265
})
6366
```
64-
67+
6568
or
66-
69+
6770
```typescript
6871
// Single import of only the <igx-grid> component.
6972
import { IgxGridComponent } from 'igniteui-angular';
70-
73+
7174
@Component({
7275
selector: 'app-grid-sample',
7376
styleUrls: ['grid.sample.scss'],
@@ -76,13 +79,13 @@ All notable changes for each version of this project will be documented in this
7679
imports: [IgxGridComponent, AsyncPipe]
7780
})
7881
```
79-
82+
8083
or still
81-
84+
8285
```typescript
8386
// `NgModule` import of the `IgxGridModule` module, which is equivalent to IGX_GRID_DIRECTIVES in terms of exported components and directives.
8487
import { IgxGridModule } from 'igniteui-angular';
85-
88+
8689
@Component({
8790
selector: 'app-grid-sample',
8891
styleUrls: ['grid.sample.scss'],

projects/igniteui-angular/src/lib/combo/combo.component.spec.ts

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
IComboItemAdditionEvent, IComboSearchInputEventArgs, IComboSelectionChangingEventArgs, IgxComboComponent
2828
} from './combo.component';
2929
import { IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboItemDirective } from './combo.directives';
30-
import { IgxComboFilteringPipe } from './combo.pipes';
30+
import { IgxComboFilteringPipe, comboIgnoreDiacriticsFilter } from './combo.pipes';
3131
import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base';
3232

3333
const CSS_CLASS_COMBO = 'igx-combo';
@@ -307,7 +307,7 @@ describe('igxCombo', () => {
307307
};
308308
combo.comboInput = {
309309
nativeElement: {
310-
focus: () => {}
310+
focus: () => { }
311311
}
312312
} as any;
313313
combo.handleOpening(inputEvent);
@@ -1195,9 +1195,9 @@ describe('igxCombo', () => {
11951195
expect(comboData).toEqual(data);
11961196
});
11971197
it('should remove undefined from array of primitive data', () => {
1198-
combo.data = ['New York', 'Sofia', undefined, 'Istanbul','Paris'];
1198+
combo.data = ['New York', 'Sofia', undefined, 'Istanbul', 'Paris'];
11991199

1200-
expect(combo.data).toEqual(['New York', 'Sofia', 'Istanbul','Paris']);
1200+
expect(combo.data).toEqual(['New York', 'Sofia', 'Istanbul', 'Paris']);
12011201
});
12021202
it('should render empty template when combo data source is not set', () => {
12031203
combo.data = [];
@@ -2031,7 +2031,7 @@ describe('igxCombo', () => {
20312031
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20322032
fixture.detectChanges();
20332033
expect(combo.value).toBe('0');
2034-
expect(combo.selection).toEqual([ 0 ]);
2034+
expect(combo.selection).toEqual([0]);
20352035

20362036
combo.open();
20372037
fixture.detectChanges();
@@ -2041,7 +2041,7 @@ describe('igxCombo', () => {
20412041
item2.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20422042
fixture.detectChanges();
20432043
expect(combo.value).toBe('0, false');
2044-
expect(combo.selection).toEqual([ 0, false ]);
2044+
expect(combo.selection).toEqual([0, false]);
20452045

20462046
combo.open();
20472047
fixture.detectChanges();
@@ -2051,7 +2051,7 @@ describe('igxCombo', () => {
20512051
item3.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20522052
fixture.detectChanges();
20532053
expect(combo.value).toBe('0, false, ');
2054-
expect(combo.selection).toEqual([ 0, false, '' ]);
2054+
expect(combo.selection).toEqual([0, false, '']);
20552055

20562056
combo.open();
20572057
fixture.detectChanges();
@@ -2061,7 +2061,7 @@ describe('igxCombo', () => {
20612061
item4.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20622062
fixture.detectChanges();
20632063
expect(combo.value).toBe('0, false, , null');
2064-
expect(combo.selection).toEqual([ 0, false, '', null ]);
2064+
expect(combo.selection).toEqual([0, false, '', null]);
20652065

20662066
combo.open();
20672067
fixture.detectChanges();
@@ -2071,7 +2071,7 @@ describe('igxCombo', () => {
20712071
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20722072
fixture.detectChanges();
20732073
expect(combo.value).toBe('0, false, , null, NaN');
2074-
expect(combo.selection).toEqual([ 0, false, '', null, NaN ]);
2074+
expect(combo.selection).toEqual([0, false, '', null, NaN]);
20752075

20762076
combo.open();
20772077
fixture.detectChanges();
@@ -2081,7 +2081,7 @@ describe('igxCombo', () => {
20812081
item6.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
20822082
fixture.detectChanges();
20832083
expect(combo.value).toBe('0, false, , null, NaN');
2084-
expect(combo.selection).toEqual([ 0, false, '', null, NaN ]);
2084+
expect(combo.selection).toEqual([0, false, '', null, NaN]);
20852085
});
20862086
it('should select falsy values except "undefined" with "writeValue" method', () => {
20872087
combo.valueKey = 'value';
@@ -2429,6 +2429,32 @@ describe('igxCombo', () => {
24292429
expect(combo.addition.emit).toHaveBeenCalledTimes(1);
24302430
expect(combo.data[combo.data.length - 1]).toEqual('myItem');
24312431
});
2432+
2433+
it('should support filtering strings containing diacritic characters', fakeAsync(() => {
2434+
combo.filterFunction = comboIgnoreDiacriticsFilter;
2435+
combo.displayKey = null;
2436+
combo.valueKey = null;
2437+
combo.filteringOptions = { caseSensitive: false, filterable: true, filteringKey: undefined };
2438+
combo.data = ['José', 'Óscar', 'Ángel', 'Germán', 'Niño', 'México', 'Méxícó', 'Mexico', 'Köln', 'München'];
2439+
combo.toggle();
2440+
fixture.detectChanges();
2441+
2442+
const searchInput = fixture.debugElement.query(By.css(`input[name="searchInput"]`));
2443+
2444+
const verifyFilteredItems = (term: string, expected: number) => {
2445+
UIInteractions.triggerInputEvent(searchInput, term);
2446+
fixture.detectChanges();
2447+
const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement;
2448+
const items = list.querySelectorAll(`.${CSS_CLASS_DROPDOWNLISTITEM}`);
2449+
expect(items.length).toEqual(expected);
2450+
};
2451+
2452+
verifyFilteredItems('jose', 1);
2453+
verifyFilteredItems('mexico', 3);
2454+
verifyFilteredItems('o', 7);
2455+
verifyFilteredItems('é', 7);
2456+
}));
2457+
24322458
it('should filter the dropdown items when typing in the search input', fakeAsync(() => {
24332459
let dropdownList;
24342460
let dropdownItems;
@@ -2513,8 +2539,8 @@ describe('igxCombo', () => {
25132539
const searchInput = fixture.debugElement.query(By.css(CSS_CLASS_SEARCHINPUT));
25142540

25152541
const verifyFilteredItems = (inputValue: string,
2516-
expectedDropdownItemsNumber: number,
2517-
expectedFilteredItemsNumber: number) => {
2542+
expectedDropdownItemsNumber: number,
2543+
expectedFilteredItemsNumber: number) => {
25182544
UIInteractions.triggerInputEvent(searchInput, inputValue);
25192545
fixture.detectChanges();
25202546
dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement;
@@ -2768,7 +2794,7 @@ describe('igxCombo', () => {
27682794
return collection.filter(i => filteringOptions.caseSensitive ?
27692795
i[filteringOptions.filteringKey]?.includes(searchTerm) :
27702796
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
2771-
}
2797+
}
27722798
combo.open();
27732799
tick();
27742800
fixture.detectChanges();
@@ -2806,7 +2832,7 @@ describe('igxCombo', () => {
28062832
return collection.filter(i => filteringOptions.caseSensitive ?
28072833
i[filteringOptions.filteringKey]?.includes(searchTerm) :
28082834
i[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm))
2809-
}
2835+
}
28102836
combo.open();
28112837
tick();
28122838
fixture.detectChanges();

projects/igniteui-angular/src/lib/combo/combo.pipes.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,45 @@ export class IgxComboGroupingPipe implements PipeTransform {
7070
}
7171
}
7272

73-
function defaultFilterFunction (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions): any[] {
73+
function defaultFilterFunction<T>(collection: T[], searchValue: string, filteringOptions: IComboFilteringOptions): T[] {
7474
if (!searchValue) {
7575
return collection;
7676
}
77-
const searchTerm = filteringOptions.caseSensitive ? searchValue : searchValue.toLowerCase();
78-
if (filteringOptions.filteringKey != null) {
79-
return collection.filter(e => filteringOptions.caseSensitive ?
80-
e[filteringOptions.filteringKey]?.includes(searchTerm) :
81-
e[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm));
82-
} else {
83-
return collection.filter(e => filteringOptions.caseSensitive ?
84-
e?.includes(searchTerm) :
85-
e?.toString().toLowerCase().includes(searchTerm));
77+
78+
const { caseSensitive, filteringKey } = filteringOptions;
79+
const term = caseSensitive ? searchValue : searchValue.toLowerCase();
80+
81+
return collection.filter(item => {
82+
const str = filteringKey ? `${item[filteringKey]}` : `${item}`;
83+
return (caseSensitive ? str : str.toLowerCase()).includes(term);
84+
});
85+
}
86+
87+
function normalizeString(str: string, caseSensitive = false): string {
88+
return (caseSensitive ? str : str.toLocaleLowerCase())
89+
.normalize('NFKD')
90+
.replace(/\p{M}/gu, '');
91+
}
92+
93+
/**
94+
* Combo filter function which does not distinguish between accented letters and their base letters.
95+
* For example, when filtering for "resume", this function will match both "resume" and "résumé".
96+
*
97+
* @example
98+
* ```html
99+
* <igx-combo [filterFunction]="comboIgnoreDiacriticFilterFunction"></igx-combo>
100+
* ```
101+
*/
102+
export function comboIgnoreDiacriticsFilter<T>(collection: T[], searchValue: string, filteringOptions: IComboFilteringOptions): T[] {
103+
if (!searchValue) {
104+
return collection;
86105
}
106+
107+
const { caseSensitive, filteringKey } = filteringOptions;
108+
const term = normalizeString(searchValue, caseSensitive);
109+
110+
return collection.filter(item => {
111+
const str = filteringKey ? `${item[filteringKey]}` : `${item}`;
112+
return normalizeString(str, caseSensitive).includes(term);
113+
});
87114
}

projects/igniteui-angular/src/lib/combo/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
export { IComboFilteringOptions } from './combo.common';
1818
export * from './combo.component';
1919
export * from './combo.directives';
20+
export { comboIgnoreDiacriticsFilter } from './combo.pipes';
2021

2122
/* NOTE: Combo directives collection for ease-of-use import in standalone components scenario */
2223
export const IGX_COMBO_DIRECTIVES = [

0 commit comments

Comments
 (0)