Commit b5875232 authored by Ed Carroll's avatar Ed Carroll
Browse files

feat(select): Options now manually rendered at specified location

parent 89c76317
......@@ -52,7 +52,7 @@
"raw-loader": "0.5.1",
"resolve": "1.3.3",
"rsvp": "3.6.0",
"rxjs": "5.4.1",
"rxjs": "5.4.2",
"sass-loader": "6.0.6",
"script-loader": "0.7.0",
"semver": "5.3.0",
......@@ -427,7 +427,7 @@
"aproba": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz",
"integrity": "sha1-RcZikJTeTpb2k+9+q3SuB5wkD8E=",
"integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==",
"dev": true
},
"are-we-there-yet": {
......@@ -4170,7 +4170,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
......@@ -4203,7 +4203,7 @@
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
"integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=",
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
"dev": true
},
"globby": {
......@@ -5395,7 +5395,7 @@
"istanbul-lib-coverage": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz",
"integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=",
"integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==",
"dev": true
},
"istanbul-lib-hook": {
......@@ -6249,7 +6249,7 @@
"lru-cache": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
"integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=",
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
"dev": true,
"optional": true,
"requires": {
......@@ -6426,7 +6426,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
......@@ -8025,7 +8025,7 @@
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
"integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=",
"integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
"dev": true,
"requires": {
"is-number": "3.0.0",
......@@ -8066,7 +8066,7 @@
"randombytes": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
"integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=",
"integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
......@@ -8758,9 +8758,9 @@
}
},
"rxjs": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.1.tgz",
"integrity": "sha1-ti91fyeURdJloYpY+wpw3JDpFiY=",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz",
"integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=",
"requires": {
"symbol-observable": "1.0.4"
}
......@@ -9470,7 +9470,7 @@
"stream-http": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz",
"integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=",
"integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==",
"dev": true,
"requires": {
"builtin-status-codes": "3.0.0",
......@@ -10059,7 +10059,7 @@
"url-loader": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.5.9.tgz",
"integrity": "sha1-zI/qgse5Bud3cBklCGnlaemVwpU=",
"integrity": "sha512-B7QYFyvv+fOBqBVeefsxv6koWWtjmHaMFT6KZWti4KRw8YUD/hOU+3AECvXuzyVawIBx3z7zQRejXCDSO5kk1Q==",
"dev": true,
"requires": {
"loader-utils": "1.1.0",
......@@ -10269,7 +10269,7 @@
"walk-sync": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.2.tgz",
"integrity": "sha1-SCcoCvxC0OA1NnxKTjHurA0Tb3U=",
"integrity": "sha512-FMB5VqpLqOCcqrzA9okZFc0wq0Qbmdm396qJxvQZhDpyu0W95G9JCmp74tx7iyYnyOcBtUuKJsgIKAqjozvmmQ==",
"dev": true,
"requires": {
"ensure-posix-path": "1.0.2",
......@@ -10634,7 +10634,7 @@
"webpack-sources": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz",
"integrity": "sha1-xzVkNqTRMSO+LiQmoF0drZy+Zc8=",
"integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==",
"dev": true,
"requires": {
"source-list-map": "2.0.0",
......@@ -10644,7 +10644,7 @@
"source-list-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
"integrity": "sha1-qqR0A/eyRakvvJfqCPJQ1gh+0IU=",
"integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
"dev": true
}
}
......@@ -10694,7 +10694,7 @@
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
"integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
"requires": {
"string-width": "1.0.2"
......
......@@ -55,7 +55,7 @@
"element-closest": "^2.0.2",
"extend": "^3.0.1",
"popper.js": "~1.10.6",
"rxjs": "^5.0.1"
"rxjs": "^5.4.2"
},
"devDependencies": {
"@angular/common": "^4.3.1",
......
......@@ -34,8 +34,8 @@ export class SuiComponentFactory {
}
// Inserts the component into the specified view container.
public attachToView<T>(componentRef:ComponentRef<T>, viewContainer:ViewContainerRef):void {
viewContainer.insert(componentRef.hostView, 0);
public attachToView<T>(componentRef:ComponentRef<T>, viewContainer:ViewContainerRef, index:number = 0):void {
viewContainer.insert(componentRef.hostView, index);
}
// Inserts the component in the root application node.
......
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Util } from "../../../misc/util";
import { LookupFn, LookupFnResult, FilterFn } from "../helpers/lookup-fn";
......@@ -59,6 +60,9 @@ export class SearchService<T, U> {
return this._results;
}
// Results in observable form.
public results$:BehaviorSubject<T[]>;
private _query:string;
// Allows the empty query to produce results.
public allowEmptyQuery:boolean;
......@@ -97,6 +101,8 @@ export class SearchService<T, U> {
return false;
};
this.results$ = new BehaviorSubject<T[]>([]);
// Set default values and reset.
this.allowEmptyQuery = allowEmptyQuery;
this.searchDelay = 0;
......@@ -128,7 +134,7 @@ export class SearchService<T, U> {
if (this._resultsCache.hasOwnProperty(this._query)) {
// If the query is already cached, make use of it.
this._results = this._resultsCache[this._query];
this.updateResults(this._resultsCache[this._query], false);
return callback();
}
......@@ -163,9 +169,12 @@ export class SearchService<T, U> {
}
// Updates & caches the new set of results.
private updateResults(results:T[]):void {
this._resultsCache[this._query] = results;
private updateResults(results:T[], cache:boolean = true):void {
if (cache) {
this._resultsCache[this._query] = results;
}
this._results = results;
this.results$.next(results);
}
// tslint:disable-next-line:promise-function-async
......@@ -201,6 +210,7 @@ export class SearchService<T, U> {
// Resets the search back to a pristine state.
private reset():void {
this._results = [];
this.results$.next([]);
this._resultsCache = {};
this._isSearching = false;
this.updateQuery("");
......
import {
ViewChild, HostBinding, ElementRef, HostListener, Input, ContentChildren, QueryList, AfterViewInit,
AfterContentInit, TemplateRef, ViewContainerRef, ContentChild, EventEmitter, Output, ViewChildren
AfterContentInit, TemplateRef, ViewContainerRef, ContentChild, EventEmitter, Output, ViewChildren, ComponentRef
} from "@angular/core";
import { Subscription } from "rxjs/Subscription";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/merge";
import { DropdownService, SuiDropdownMenu } from "../../dropdown";
import { SearchService, LookupFn, FilterFn } from "../../search";
import { Util, ITemplateRefContext, HandledEvent, KeyCode } from "../../../misc/util";
import { Util, ITemplateRefContext, HandledEvent, KeyCode, SuiComponentFactory } from "../../../misc/util";
import { ISelectLocaleValues, RecursivePartial, SuiLocalizationService } from "../../../behaviors/localization";
import { SuiSelectOption, ISelectRenderedOption } from "../components/select-option";
import { SuiSelectOption } from "../components/select-option";
import { SuiSelectSearch } from "../directives/select-search";
import { SuiSelectOptions } from "../components/select-options";
......@@ -26,19 +29,16 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
// Keep track of all of the rendered select options. (Rendered by the user using *ngFor).
@ContentChildren(SuiSelectOption)
protected _renderedOptions:QueryList<SuiSelectOption<T>>;
protected _manualOptions:QueryList<SuiSelectOption<T>>;
// Keep track of all of the subscriptions to the selected events on the rendered options.
private _renderedSubscriptions:Subscription[];
@ViewChild(SuiSelectOptions, { read: ViewContainerRef })
private _internalOptionsContainer:ViewContainerRef;
@ViewChild(SuiSelectOptions)
private _internalOptions?:SuiSelectOptions<T>;
@ContentChild(SuiSelectOptions, { read: ViewContainerRef })
private _manualOptionsContainer?:ViewContainerRef;
@ContentChild(SuiSelectOptions)
private _manualOptions?:SuiSelectOptions<T>;
public get optionsContainer():SuiSelectOptions<T> | undefined {
return this._manualOptions || this._internalOptions;
public get optionsContainer():ViewContainerRef {
return this._manualOptionsContainer || this._internalOptionsContainer;
}
// Sets the Semantic UI classes on the host element.
......@@ -143,14 +143,12 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
}
}
public get filteredOptions():T[] {
return this.searchService.results;
public get filteredOptions$():BehaviorSubject<T[]> {
return this.searchService.results$;
}
// Deprecated
public get availableOptions():T[] {
return this.filteredOptions;
}
private _renderedOptions:ComponentRef<SuiSelectOption<T>>[];
private _renderedSubscription:Subscription;
public get query():string | undefined {
return this.isSearchable ? this.searchService.query : undefined;
......@@ -161,7 +159,7 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
this.queryUpdateHook();
this.updateQuery(query);
// Update the rendered text as query has changed.
this._renderedOptions.forEach(ro => ro.formatter = this.configuredFormatter);
this._manualOptions.forEach(ro => ro.formatter = this.configuredFormatter);
if (this.searchInput) {
this.searchInput.query = query;
......@@ -236,16 +234,22 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
@Output("touched")
public onTouched:EventEmitter<void>;
constructor(private _element:ElementRef, protected _localizationService:SuiLocalizationService) {
constructor(private _element:ElementRef,
private _componentFactory:SuiComponentFactory,
protected _localizationService:SuiLocalizationService) {
this.dropdownService = new DropdownService();
// We do want an empty query to return all results.
this.searchService = new SearchService<T, U>(true);
this._renderedOptions = [];
this.isSearchable = false;
this.onLocaleUpdate();
this._localizationService.onLanguageUpdate.subscribe(() => this.onLocaleUpdate());
this._renderedSubscriptions = [];
this.searchService.results$.subscribe(rs => this.renderOptions(rs));
this.icon = "dropdown";
this.transition = "slide down";
......@@ -259,7 +263,7 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
public ngAfterContentInit():void {
this._menu.service = this.dropdownService;
// We manually specify the menu items to the menu because the @ContentChildren doesn't pick up our dynamically rendered items.
this._menu.items = this._renderedOptions;
this._menu.items = this._manualOptions;
}
public ngAfterViewInit():void {
......@@ -273,13 +277,9 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
this.searchInput.onQueryKeyDown.subscribe((e:KeyboardEvent) => this.onQueryInputKeydown(e));
}
if (this.optionsContainer) {
this.optionsContainer.options = this.availableOptions;
}
// We must call this immediately as changes doesn't fire when you subscribe.
this.onAvailableOptionsRendered();
this._renderedOptions.changes.subscribe(() => this.onAvailableOptionsRendered());
this.onManualOptionsRendered();
this._manualOptions.changes.subscribe(() => this.onManualOptionsRendered());
}
private onLocaleUpdate():void {
......@@ -313,31 +313,53 @@ export abstract class SuiSelectBase<T, U> implements AfterContentInit, AfterView
}
}
protected onAvailableOptionsRendered():void {
console.log(this._renderedOptions);
// Unsubscribe from all previous subscriptions to avoid memory leaks on large selects.
this._renderedSubscriptions.forEach(rs => rs.unsubscribe());
this._renderedSubscriptions = [];
private renderOptions(options:T[]):void {
if (this.optionsContainer) {
this.optionsContainer.clear();
}
this._renderedOptions.forEach(ro => {
// Slightly delay initialisation to avoid change after checked errors. TODO - look into avoiding this!
setTimeout(() => this.initialiseRenderedOption(ro));
if (this._renderedSubscription) {
this._renderedSubscription.unsubscribe();
}
this._renderedSubscriptions.push(ro.onSelected.subscribe(() => this.selectOption(ro.option)));
});
this._renderedOptions.forEach(ro => ro.destroy());
this._renderedOptions = [];
// If no options have been provided, autogenerate them from the rendered ones.
if (this.searchService.options.length === 0 && !this.searchService.optionsLookup) {
this.options = this._renderedOptions.map(ro => ro.option);
}
options
.slice()
.reverse()
.forEach(option => {
const component = this._componentFactory.createComponent(SuiSelectOption);
component.instance.option = option;
this._componentFactory.attachToView(component, this.optionsContainer);
this._renderedOptions.push(component);
});
this._renderedSubscription = Observable
.merge(...this._renderedOptions.map(ro => ro.instance.onSelected))
.subscribe((o:T) => {
this.selectOption(o);
this.updateRenderedOptions();
});
this.updateRenderedOptions();
}
protected initialiseRenderedOption(option:ISelectRenderedOption<T>):void {
option.usesTemplate = !!this.optionTemplate;
private updateRenderedOptions():void {
this._renderedOptions.forEach(ro => this.updateRenderedOption(ro.instance));
}
protected updateRenderedOption(option:SuiSelectOption<T>):void {
option.query = this.query;
option.formatter = this.configuredFormatter;
option.template = this.optionTemplate;
}
if (option.usesTemplate) {
this.drawTemplate(option.templateSibling, option.option);
protected onManualOptionsRendered():void {
// If no options have been provided, autogenerate them from the rendered ones.
if (this.searchService.options.length === 0 && !this.searchService.optionsLookup) {
this.options = this._manualOptions.map(ro => ro.option);
}
}
......
......@@ -36,7 +36,7 @@ export class SuiMultiSelectLabel<T> extends SuiTransition {
public onDeselected:EventEmitter<T>;
@Input()
public formatter:(obj:T) => string;
public formatter?:(obj:T) => string;
private _template?:TemplateRef<IOptionContext<T>>;
......
import { Component, HostBinding, ElementRef, EventEmitter, Output, Input, Directive } from "@angular/core";
import { ICustomValueAccessorHost, KeyCode, customValueAccessorFactory, CustomValueAccessor } from "../../../misc/util";
import {
ICustomValueAccessorHost, KeyCode, customValueAccessorFactory,
CustomValueAccessor, SuiComponentFactory
} from "../../../misc/util";
import { SuiLocalizationService } from "../../../behaviors/localization";
import { SuiSelectBase } from "../classes/select-base";
import { ISelectRenderedOption } from "./select-option";
import { SuiSelectOption } from "./select-option";
@Component({
selector: "sui-multi-select",
......@@ -46,7 +49,7 @@ import { ISelectRenderedOption } from "./select-option";
<ng-content></ng-content>
<sui-select-options></sui-select-options>
<ng-container *ngIf="availableOptions.length == 0 ">
<ng-container *ngIf="(filteredOptions$ | async).length == 0 ">
<div *ngIf="!maxSelectedReached" class="message">{{ localeValues.noResultsMessage }}</div>
<div *ngIf="maxSelectedReached" class="message">{{ maxSelectedMessage }}</div>
</ng-container>
......@@ -135,8 +138,11 @@ export class SuiMultiSelect<T, U> extends SuiSelectBase<T, U> implements ICustom
@HostBinding("class.multiple")
private _multiSelectClasses:boolean;
constructor(element:ElementRef, localizationService:SuiLocalizationService) {
super(element, localizationService);
constructor(element:ElementRef,
componentFactory:SuiComponentFactory,
localizationService:SuiLocalizationService) {
super(element, componentFactory, localizationService);
this.selectedOptions = [];
this.selectedOptionsChange = new EventEmitter<U[]>();
......@@ -159,8 +165,8 @@ export class SuiMultiSelect<T, U> extends SuiSelectBase<T, U> implements ICustom
}
}
protected initialiseRenderedOption(rendered:ISelectRenderedOption<T>):void {
super.initialiseRenderedOption(rendered);
protected updateRenderedOption(rendered:SuiSelectOption<T>):void {
super.updateRenderedOption(rendered);
// Boldens the item so it appears selected in the dropdown.
rendered.isActive = !this.hasLabels && this.selectedOptions.indexOf(rendered.option) !== -1;
......@@ -178,10 +184,6 @@ export class SuiMultiSelect<T, U> extends SuiSelectBase<T, U> implements ICustom
// Automatically refocus the search input for better keyboard accessibility.
this.focus();
if (!this.hasLabels) {
this.onAvailableOptionsRendered();
}
}
public writeValue(values:U[]):void {
......@@ -217,10 +219,6 @@ export class SuiMultiSelect<T, U> extends SuiSelectBase<T, U> implements ICustom
// Automatically refocus the search input for better keyboard accessibility.
this.focus();
if (!this.hasLabels) {
this.onAvailableOptionsRendered();
}
}
public onQueryInputKeydown(event:KeyboardEvent):void {
......
import {
Component, Input, HostBinding, HostListener, EventEmitter, ViewContainerRef,
ViewChild, Renderer2, ElementRef, Output, ChangeDetectorRef
ViewChild, Renderer2, ElementRef, Output, ChangeDetectorRef, TemplateRef
} from "@angular/core";
import { SuiDropdownMenuItem } from "../../dropdown";
import { HandledEvent } from "../../../misc/util";
export interface ISelectRenderedOption<T> {
option:T;
isActive?:boolean;
formatter:(o:T) => string;
usesTemplate:boolean;
templateSibling:ViewContainerRef;
}
import { HandledEvent, SuiComponentFactory } from "../../../misc/util";
import { IOptionContext } from "../classes/select-base";
@Component({
selector: "sui-select-option",
template: `
<span #templateSibling></span>
<span [innerHTML]="renderedText"></span>
`,
styles: [`
:host {
display: none !important;
}
:host-context(sui-select-options) {
display: block;
}
`]
<span *ngIf="!template" [innerHTML]="formatter(option)"></span>
`
})
export class SuiSelectOption<T> extends SuiDropdownMenuItem implements ISelectRenderedOption<T> {
export class SuiSelectOption<T> extends SuiDropdownMenuItem {
// Sets the Semantic UI classes on the host element.
@HostBinding("class.item")
private _optionClasses:boolean;
......@@ -37,48 +21,55 @@ export class SuiSelectOption<T> extends SuiDropdownMenuItem implements ISelectRe
@Input()
public option:T;
// Fires when the option is selected, whether by clicking or by keyboard.
@Output()
public onSelected:EventEmitter<T>;
@Input()
public query?:string;
@HostBinding("class.active")
public isActive:boolean;
public renderedText?:string;
@Input()
public formatter?:(obj:T) => string;
public set formatter(formatter:(obj:T) => string) {
if (!this.usesTemplate) {
this.renderedText = formatter(this.option);
} else {
this.renderedText = undefined;
}
private _template?:TemplateRef<IOptionContext<T>>;
@Input()
public get template():TemplateRef<IOptionContext<T>> | undefined {
return this._template;
}
public usesTemplate:boolean;
public set template(template:TemplateRef<IOptionContext<T>> | undefined) {
this._template = template;
if (this.template) {
this.componentFactory.createView(this.templateSibling, this.template, {
$implicit: this.option,
query: this.query
});
}
}
// Placeholder to draw template beside.
@ViewChild("templateSibling", { read: ViewContainerRef })
public templateSibling:ViewContainerRef;
constructor(renderer:Renderer2, element:ElementRef) {
// Fires when the option is selected, whether by clicking or by keyboard.
@Output()
public onSelected:EventEmitter<T>;
constructor(renderer:Renderer2, element:ElementRef, public componentFactory:SuiComponentFactory) {
// We inherit SuiDropdownMenuItem to automatically gain all keyboard navigation functionality.
// This is not done via adding the .item class because it isn't supported by Angular.
super(renderer, element);
this._optionClasses = true;
this.isActive = false;
this.onSelected = new EventEmitter<T>();
// By default we make this function return an empty string, for the brief moment when it isn't displaying the correct label.
this.formatter = o => "";
this.usesTemplate = false;
this._optionClasses = true;
}
@HostListener("click", ["$event"])