Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autocomplete): add event when option is activated #18387

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/material/autocomplete/autocomplete.spec.ts
Expand Up @@ -2496,6 +2496,34 @@ describe('MatAutocomplete', () => {
expect(event.option.value).toBe('Puerto Rico');
}));

it('should emit an event when an option is activated', fakeAsync(() => {
const fixture = createComponent(AutocompleteWithActivatedEvent);

fixture.detectChanges();
fixture.componentInstance.trigger.openPanel();
zone.simulateZoneExit();
fixture.detectChanges();

const input = fixture.nativeElement.querySelector('input');
const spy = fixture.componentInstance.optionActivated;
const autocomplete = fixture.componentInstance.autocomplete;
const options = fixture.componentInstance.options.toArray();

expect(spy).not.toHaveBeenCalled();

dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[0]});

dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[1]});

dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[2]});
}));

it('should be able to set a custom panel connection element', () => {
const fixture = createComponent(AutocompleteWithDifferentOrigin);

Expand Down Expand Up @@ -2978,3 +3006,24 @@ class AutocompleteWithNativeAutocompleteAttribute {
})
class InputWithoutAutocompleteAndDisabled {
}


@Component({
template: `
<mat-form-field>
<input matInput [matAutocomplete]="auto">
</mat-form-field>

<mat-autocomplete #auto="matAutocomplete" (optionActivated)="optionActivated($event)">
<mat-option *ngFor="let state of states" [value]="state">{{ state }}</mat-option>
</mat-autocomplete>
`
})
class AutocompleteWithActivatedEvent {
states = ['California', 'West Virginia', 'Florida'];
optionActivated = jasmine.createSpy('optionActivated callback');

@ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
@ViewChild(MatAutocomplete) autocomplete: MatAutocomplete;
@ViewChildren(MatOption) options: QueryList<MatOption>;
}
25 changes: 24 additions & 1 deletion src/material/autocomplete/autocomplete.ts
Expand Up @@ -24,6 +24,7 @@ import {
TemplateRef,
ViewChild,
ViewEncapsulation,
OnDestroy,
} from '@angular/core';
import {
CanDisableRipple,
Expand All @@ -33,6 +34,7 @@ import {
MatOption,
mixinDisableRipple,
} from '@angular/material/core';
import {Subscription} from 'rxjs';


/**
Expand All @@ -50,6 +52,14 @@ export class MatAutocompleteSelectedEvent {
public option: MatOption) { }
}

/** Event object that is emitted when an autocomplete option is activated. */
export interface MatAutocompleteActivatedEvent {
/** Reference to the autocomplete panel that emitted the event. */
source: MatAutocomplete;

/** Option that was selected. */
option: MatOption|null;
}

// Boilerplate for applying mixins to MatAutocomplete.
/** @docs-private */
Expand Down Expand Up @@ -91,7 +101,8 @@ export function MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): MatAutocompleteDefau
]
})
export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit,
CanDisableRipple {
CanDisableRipple, OnDestroy {
private _activeOptionChanges = Subscription.EMPTY;

/** Manages active item in option list based on key events. */
_keyManager: ActiveDescendantKeyManager<MatOption>;
Expand Down Expand Up @@ -149,6 +160,10 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC
/** Event that is emitted when the autocomplete panel is closed. */
@Output() readonly closed: EventEmitter<void> = new EventEmitter<void>();

/** Emits whenever an option is activated using the keyboard. */
@Output() readonly optionActivated: EventEmitter<MatAutocompleteActivatedEvent> =
new EventEmitter<MatAutocompleteActivatedEvent>();

/**
* Takes classes set on the host mat-autocomplete element and applies them to the panel
* inside the overlay container to allow for easy styling.
Expand Down Expand Up @@ -183,10 +198,18 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC

ngAfterContentInit() {
this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options).withWrap();
this._activeOptionChanges = this._keyManager.change.subscribe(index => {
this.optionActivated.emit({source: this, option: this.options.toArray()[index] || null});
});

// Set the initial visibility state.
this._setVisibility();
}

ngOnDestroy() {
this._activeOptionChanges.unsubscribe();
}

/**
* Sets the panel scrollTop. This allows us to manually scroll to display options
* above or below the fold, as they are not actually being focused when active.
Expand Down
11 changes: 9 additions & 2 deletions tools/public_api_guard/material/autocomplete.d.ts
Expand Up @@ -20,7 +20,7 @@ export declare const MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER: {

export declare const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any;

export declare class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit, CanDisableRipple {
export declare class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit, CanDisableRipple, OnDestroy {
_classList: {
[key: string]: boolean;
};
Expand All @@ -34,6 +34,7 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement
id: string;
get isOpen(): boolean;
readonly opened: EventEmitter<void>;
readonly optionActivated: EventEmitter<MatAutocompleteActivatedEvent>;
optionGroups: QueryList<MatOptgroup>;
readonly optionSelected: EventEmitter<MatAutocompleteSelectedEvent>;
options: QueryList<MatOption>;
Expand All @@ -47,12 +48,18 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement
_setScrollTop(scrollTop: number): void;
_setVisibility(): void;
ngAfterContentInit(): void;
ngOnDestroy(): void;
static ngAcceptInputType_autoActiveFirstOption: BooleanInput;
static ngAcceptInputType_disableRipple: BooleanInput;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "disableRipple": "disableRipple"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; }, ["options", "optionGroups"]>;
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "disableRipple": "disableRipple"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, ["options", "optionGroups"]>;
static ɵfac: i0.ɵɵFactoryDef<MatAutocomplete>;
}

export interface MatAutocompleteActivatedEvent {
option: MatOption | null;
source: MatAutocomplete;
}

export interface MatAutocompleteDefaultOptions {
autoActiveFirstOption?: boolean;
}
Expand Down