Skip to content
Merged
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes for each version of this project will be documented in this file.

## 20.0.6
### General
- `IgxSimpleCombo`
- Added `disableFiltering` to the `IgxSimpleCombo`, which enables/disables the filtering in the list. The default is `false`.
- `IgxCombo`, `IgxSimpleCombo`
- Removed deprecated `filteringOptions.filterable` option.

## 20.0.2

### New Features
Expand Down Expand Up @@ -42,7 +49,6 @@ All notable changes for each version of this project will be documented in this
```

## 19.2.0

### General
- `IgxCarousel`
- Removed deprecated property `keyboardSupport`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@
"version": "20.0.2",
"description": "Updates Ignite UI for Angular from v20.0.0 to v20.0.2",
"factory": "./update-20_0_2"
},
"migration-48": {
"version": "20.0.6",
"description": "Updates Ignite UI for Angular from v20.0.2 to v20.0.6",
"factory": "./update-20_0_6"
}
}
}
106 changes: 106 additions & 0 deletions projects/igniteui-angular/migrations/update-20_0_6/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as path from 'path';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { setupTestTree } from '../common/setup.spec';

describe('Migration 20.0.6 - Replace filteringOptions.filterable', () => {
let appTree: UnitTestTree;
const runner = new SchematicTestRunner(
'ig-migrate',
path.join(__dirname, '../migration-collection.json')
);
const migrationName = 'migration-48';
const makeTemplate = (name: string) => `/testSrc/appPrefix/component/${name}.component.html`;
const makeScript = (name: string) => `/testSrc/appPrefix/component/${name}.component.ts`;
const components = ['igx-simple-combo', 'igx-combo'];



beforeEach(() => {
appTree = setupTestTree();
});

it('should replace simple inline filteringOptions.filterable true with default behavior of the simple combo', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="{ filterable: true }"></${component}>`;
appTree.create(makeTemplate(`${component}-inline-true`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-inline-true`));

expect(output).not.toContain('[disableFiltering]');
expect(output).not.toContain('filterable');
});
});

it('should handle mixed object literal correctly', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="{ filterable: false, caseSensitive: true }"></${component}>`;
appTree.create(makeTemplate(`${component}-inline2`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-inline2`));

expect(output).toContain(`[disableFiltering]="true"`);
expect(output).toContain(`[filteringOptions]="{ caseSensitive: true }"`);
});
});

it('should warn on variable reference', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="filterOpts""></${component}>`;
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeTemplate(`${component}-referenceInTsFile`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-referenceInTsFile`));

expect(output).toContain('[filteringOptions]');
expect(output).toContain(warnMsg);
});
});

it('should skip adding new [disableFiltering] if already present on igx-combo', async () => {
const input = `<igx-combo [disableFiltering]="true" [filteringOptions]="{ filterable: false }"></igx-combo>`;
appTree.create(makeTemplate('combo-has-disableFiltering'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate('combo-has-disableFiltering'));

const occurrences = (output.match(/\[disableFiltering\]/g) || []).length;

expect(occurrences).toBe(1);
expect(output).not.toContain('filterable');
});

// TS file tests

it('should insert warning comment before `.filteringOptions.filterable = ...` assignment', async () => {
const input = `this.igxCombo.filteringOptions.filterable = false;`;
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeScript('tsWarnOnDirectAssignment'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeScript('tsWarnOnDirectAssignment'));

expect(output).toContain(expectedComment);
expect(output).toContain('this.igxCombo.filteringOptions.filterable = false;');
});

it('should insert warning comment before `.filteringOptions = { ... }` assignment', async () => {
const input = `this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };`;
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeScript('tsWarnOnObjectAssignment'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeScript('tsWarnOnObjectAssignment'));

expect(output).toContain(expectedComment);
expect(output).toContain('this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };');
});
});
126 changes: 126 additions & 0 deletions projects/igniteui-angular/migrations/update-20_0_6/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Element } from '@angular/compiler';
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { UpdateChanges } from '../common/UpdateChanges';
import {
FileChange,
findElementNodes,
getSourceOffset,
parseFile
} from '../common/util';
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.js';

const version = '20.0.6';

export default (): Rule => async (host: Tree, context: SchematicContext) => {
context.logger.info(
`Applying migration for Ignite UI for Angular to version ${version}`
);

const { HtmlParser } = await nativeImport('@angular/compiler') as typeof import('@angular/compiler');

const update = new UpdateChanges(__dirname, host, context);
const changes = new Map<string, FileChange[]>();
const parser = new HtmlParser();

const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

const applyChanges = () => {
for (const [path, fileChanges] of changes.entries()) {
let content = host.read(path).toString();
fileChanges.sort((a, b) => b.position - a.position).forEach(c => {
content = c.apply(content);
});
host.overwrite(path, content);
}
};

const addChange = (path: string, change: FileChange) => {
if (!changes.has(path)) {
changes.set(path, []);
}
changes.get(path).push(change);
};

const COMBO_TAGS = ['igx-simple-combo', 'igx-combo'];

for (const path of update.templateFiles) {
const nodes = findElementNodes(parseFile(parser, host, path), COMBO_TAGS);

for (const node of nodes) {
if (!(node instanceof Element)) continue;

const hasDisableFiltering = node.attrs.some(a => a.name.includes('disableFiltering'));
const attr = node.attrs.find(a => a.name === '[filteringOptions]');
if (!attr) continue;

const attrVal = attr.value.trim();
const offset = getSourceOffset(node);
const file = offset.file;

let replacementText = '';

if (attrVal.startsWith('{')) {
// inline object literal
const normalized = attrVal
.replace(/'/g, '"')
.replace(/([{,]\s*)(\w+)\s*:/g, '$1"$2":');
const parsed = JSON.parse(normalized);
const filterable = parsed.filterable;

if (filterable === false && !hasDisableFiltering) {
replacementText += `[disableFiltering]="true"`;
}

const remaining = { ...parsed };
delete remaining.filterable;
const remainingProps = Object.entries(remaining)
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
.join(', ');

if (remainingProps.length > 0) {
replacementText += ` [filteringOptions]="{ ${remainingProps} }"`;
}

// Replace whole [filteringOptions] attribute
const match = node.sourceSpan.toString().match(/\[filteringOptions\]="([^"]+)"/);
if (match) {
const attrText = match[0];
const attrPos = file.content.indexOf(attrText, offset.startTag.start);
addChange(file.url, new FileChange(attrPos, replacementText, attrText, 'replace'));
}
} else {
// log for manual TS edit
const comment = `\n<!-- ${warnMsg} -->\n`;
addChange(file.url, new FileChange(offset.startTag.end, comment));
}
}
}

applyChanges();

for (const path of update.tsFiles) {
const content = host.read(path).toString();
const lines = content.split('\n');
const newLines: string[] = [];

let modified = false;

for (const line of lines) {
if (
/\.filteringOptions\.filterable\s*=/.test(line) ||
/\.filteringOptions\s*=/.test(line)
) {
newLines.push('// ' + warnMsg);
modified = true;
}
newLines.push(line);
}

if (modified) {
host.overwrite(path, newLines.join('\n'));
}
}

update.applyChanges();
};
17 changes: 12 additions & 5 deletions projects/igniteui-angular/src/lib/combo/combo.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,6 @@ export const enum DataTypes {
export interface IComboFilteringOptions {
/** Defines filtering case-sensitivity */
caseSensitive?: boolean;
/**
* Defines whether filtering is allowed
* @deprecated in version 18.2.0. Use the `disableFiltering` property instead.
*/
filterable?: boolean;
/** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
filteringKey?: string;
}
Expand All @@ -123,6 +118,17 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
@Input({ transform: booleanAttribute })
public showSearchCaseIcon = false;

/**
* Enables/disables filtering in the list. The default is `false`.
*/
@Input({ transform: booleanAttribute })
public get disableFiltering(): boolean {
return this._disableFiltering;
}
public set disableFiltering(value: boolean) {
this._disableFiltering = value;
}

/**
* Set custom overlay settings that control how the combo's list of items is displayed.
* Set:
Expand Down Expand Up @@ -945,6 +951,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
protected computedStyles;

private _id: string = `igx-combo-${NEXT_ID++}`;
private _disableFiltering = false;
private _type = null;
private _dataType = '';
private _itemHeight = undefined;
Expand Down
11 changes: 0 additions & 11 deletions projects/igniteui-angular/src/lib/combo/combo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
@Input({ transform: booleanAttribute })
public autoFocusSearch = true;

/**
* Enables/disables filtering in the list. The default is `false`.
*/
@Input({ transform: booleanAttribute })
public get disableFiltering(): boolean {
return this._disableFiltering || this.filteringOptions.filterable === false;
}
public set disableFiltering(value: boolean) {
this._disableFiltering = value;
}

/**
* Defines the placeholder value for the combo dropdown search field
Expand Down Expand Up @@ -184,7 +174,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
protected _prevInputValue = '';

private _displayText: string;
private _disableFiltering = false;

constructor(
elementRef: ElementRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
(focus)="dropdown.onFocus()" (keydown)="handleItemKeyDown($event)">
<igx-combo-item [role]="item?.isHeader? 'group' : 'option'" [singleMode]="true"
[itemHeight]="itemHeight" (click)="handleItemClick()" *igxFor="let item of data
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction:disableFiltering
| comboGrouping:groupKey:valueKey:groupSortingDirection:compareCollator;
index as rowIndex; initialChunkSize: 10; containerSize: itemsMaxHeight || containerSize; itemSize: itemHeight || itemSize; scrollOrientation: 'vertical';"
[value]="item" [isHeader]="item?.isHeader" [index]="rowIndex">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,30 @@ describe('IgxSimpleCombo', () => {
expect(combo.displayValue).toEqual('Wisconsin');
});

it('should not filter the data when disableFiltering is true', () => {
combo.disableFiltering = true;
fixture.detectChanges();
combo.focusSearchInput();

UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();

expect(combo.filteredData.length).toEqual(combo.data.length);
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();

combo.disableFiltering = false;
fixture.detectChanges();
combo.focusSearchInput();
combo.comboInput.value = '';
fixture.detectChanges();
UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();
expect(combo.filteredData.length).toEqual(2);
});

it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => {
fixture.componentInstance.allowCustomValues = true;
fixture.detectChanges();
Expand Down
Loading