diff --git a/.gitignore b/.gitignore
index 3999067c5c..d80f742175 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,7 +67,6 @@ Thumbs.db
.now
-
# bazel
bazel-*
.bazelrc.user
diff --git a/angular.json b/angular.json
index 9c472a6996..98a8522fb9 100644
--- a/angular.json
+++ b/angular.json
@@ -1525,6 +1525,46 @@
},
"schematics": {}
},
+ "combobox": {
+ "projectType": "library",
+ "root": "libs/barista-components/experimental/combobox",
+ "sourceRoot": "libs/barista-components/experimental/combobox/src",
+ "prefix": "dt",
+ "architect": {
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "libs/barista-components/experimental/combobox/tsconfig.lib.json",
+ "libs/barista-components/experimental/combobox/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**",
+ "!libs/barista-components/experimental/combobox/**"
+ ]
+ }
+ },
+ "lint-styles": {
+ "builder": "./dist/libs/workspace:stylelint",
+ "options": {
+ "stylelintConfig": ".stylelintrc",
+ "reportFile": "dist/stylelint/report.xml",
+ "exclude": ["**/node_modules/**"],
+ "files": ["libs/barista-components/experimental/combobox/**/*.scss"]
+ }
+ },
+ "test": {
+ "builder": "@nrwl/jest:jest",
+ "options": {
+ "jestConfig": "libs/barista-components/experimental/combobox/jest.config.js",
+ "tsConfig": "libs/barista-components/experimental/combobox/tsconfig.spec.json",
+ "setupFile": "libs/barista-components/experimental/combobox/src/test-setup.ts",
+ "passWithNoTests": true
+ }
+ }
+ },
+ "schematics": {}
+ },
"confirmation-dialog": {
"projectType": "library",
"root": "libs/barista-components/confirmation-dialog",
diff --git a/apps/components-e2e/src/app/app.routing.module.ts b/apps/components-e2e/src/app/app.routing.module.ts
index 321a2cd56b..026b1d70fd 100644
--- a/apps/components-e2e/src/app/app.routing.module.ts
+++ b/apps/components-e2e/src/app/app.routing.module.ts
@@ -54,6 +54,13 @@ export const routes: Routes = [
(module) => module.DtE2ECheckboxModule,
),
},
+ {
+ path: 'combobox',
+ loadChildren: () =>
+ import('../components/combobox/combobox.module').then(
+ (module) => module.DtE2EComboboxModule,
+ ),
+ },
{
path: 'consumption',
loadChildren: () =>
diff --git a/apps/components-e2e/src/components/combobox/combobox.e2e.ts b/apps/components-e2e/src/components/combobox/combobox.e2e.ts
new file mode 100644
index 0000000000..eea87e2227
--- /dev/null
+++ b/apps/components-e2e/src/components/combobox/combobox.e2e.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+fixture('Combobox').page('http://localhost:4200/combobox');
+
+// test('should execute click handlers when not disabled', async (testController: TestController) => {
+// await testController.click(button);
+//
+// const count = await clickCounter.textContent;
+// await testController.expect(count).eql('1');
+// });
+//
+// test('should not execute click handlers when disabled', async (testController: TestController) => {
+// await testController.click(disableButton);
+//
+// await testController.expect(button.hasAttribute('disabled')).ok();
+//
+// await testController.click(button);
+// await testController.expect(await clickCounter.textContent).eql('0');
+// });
diff --git a/apps/components-e2e/src/components/combobox/combobox.html b/apps/components-e2e/src/components/combobox/combobox.html
new file mode 100644
index 0000000000..7df6303ba7
--- /dev/null
+++ b/apps/components-e2e/src/components/combobox/combobox.html
@@ -0,0 +1,3 @@
+
diff --git a/apps/components-e2e/src/components/combobox/combobox.module.ts b/apps/components-e2e/src/components/combobox/combobox.module.ts
new file mode 100644
index 0000000000..81278706fa
--- /dev/null
+++ b/apps/components-e2e/src/components/combobox/combobox.module.ts
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { Route, RouterModule } from '@angular/router';
+import { DtE2ECombobox } from './combobox';
+import { DtComboboxModule } from '@dynatrace/barista-components/experimental/combobox';
+
+const routes: Route[] = [{ path: '', component: DtE2ECombobox }];
+
+@NgModule({
+ declarations: [DtE2ECombobox],
+ imports: [CommonModule, RouterModule.forChild(routes), DtComboboxModule],
+ exports: [],
+ providers: [],
+})
+export class DtE2EComboboxModule {}
diff --git a/apps/components-e2e/src/components/combobox/combobox.po.ts b/apps/components-e2e/src/components/combobox/combobox.po.ts
new file mode 100644
index 0000000000..5d82cf8d51
--- /dev/null
+++ b/apps/components-e2e/src/components/combobox/combobox.po.ts
@@ -0,0 +1,19 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Selector } from 'testcafe';
+
+export const combobox = Selector('#test-combobox');
diff --git a/apps/components-e2e/src/components/combobox/combobox.ts b/apps/components-e2e/src/components/combobox/combobox.ts
new file mode 100644
index 0000000000..047c85515d
--- /dev/null
+++ b/apps/components-e2e/src/components/combobox/combobox.ts
@@ -0,0 +1,23 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'dt-e2e-combobox',
+ templateUrl: 'combobox.html',
+})
+export class DtE2ECombobox {}
diff --git a/apps/dev/src/app.module.ts b/apps/dev/src/app.module.ts
index f75fde9a87..94b28eb1b0 100644
--- a/apps/dev/src/app.module.ts
+++ b/apps/dev/src/app.module.ts
@@ -91,9 +91,10 @@ import { TopBarNavigationDemo } from './top-bar-navigation/top-bar-navigation-de
import { TreeTableDemo } from './tree-table/tree-table-demo.component';
import { DtIconModule } from '@dynatrace/barista-components/icon';
import {
- DT_UI_TEST_CONFIG,
DT_DEFAULT_UI_TEST_CONFIG,
+ DT_UI_TEST_CONFIG,
} from '@dynatrace/barista-components/core';
+import { ComboboxDemo } from './combobox/combobox-demo.component';
// tslint:disable-next-line: use-component-selector
@Component({ template: '' })
@@ -121,6 +122,7 @@ export class NoopRouteComponent {}
CardDemo,
ChartDemo,
CheckboxDemo,
+ ComboboxDemo,
ConfirmationDialogDemo,
ContextDialogDemo,
CopyToClipboardDemo,
diff --git a/apps/dev/src/combobox/combobox-demo.component.html b/apps/dev/src/combobox/combobox-demo.component.html
new file mode 100644
index 0000000000..de2500585f
--- /dev/null
+++ b/apps/dev/src/combobox/combobox-demo.component.html
@@ -0,0 +1,37 @@
+
+
+ {{ option.name }}
+
+
+
+
+ Value 1
+
+
+ Value 2
+
+
+
+
+ Selected Value:
+ {{ selectedValue || 'No value selected' }}
+
+
+
+ {{ coffee.viewValue }}
+
+
diff --git a/apps/dev/src/combobox/combobox-demo.component.ts b/apps/dev/src/combobox/combobox-demo.component.ts
new file mode 100644
index 0000000000..bacb14bdf7
--- /dev/null
+++ b/apps/dev/src/combobox/combobox-demo.component.ts
@@ -0,0 +1,88 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ViewChild,
+ AfterViewInit,
+} from '@angular/core';
+import { take } from 'rxjs/operators';
+import { timer } from 'rxjs';
+import { DtCombobox } from '@dynatrace/barista-components/experimental/combobox';
+
+const allOptions: { name: string; value: string }[] = [
+ { name: 'Value 1', value: '[value: Value 1]' },
+ { name: 'Value 2', value: '[value: Value 2]' },
+ { name: 'Value 3', value: '[value: Value 3]' },
+ { name: 'Value 4', value: '[value: Value 4]' },
+];
+
+@Component({
+ selector: 'combobox-dev-app-demo',
+ templateUrl: 'combobox-demo.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ComboboxDemo implements AfterViewInit {
+ @ViewChild(DtCombobox) combobox: DtCombobox;
+
+ _initialValue = allOptions[0];
+ _options = [...allOptions];
+ _loading = false;
+ _displayWith = (option: { name: string; value: string }) => option.name;
+
+ constructor(private _changeDetectorRef: ChangeDetectorRef) {}
+
+ ngAfterViewInit(): void {
+ this.combobox.selectionChange.subscribe((val) => {
+ console.log(val);
+ });
+ }
+
+ openedChanged(event: boolean): void {
+ console.log(`openedChanged: '${event}'`);
+ }
+
+ valueChanged(event: string): void {
+ console.log(`valueChanged: '${event}'`);
+ }
+
+ filterChanged(event: string): void {
+ console.log(`filterChanged: '${event}'`);
+
+ this._loading = true;
+ this._changeDetectorRef.markForCheck();
+
+ timer(1500)
+ .pipe(take(1))
+ .subscribe(() => {
+ this._options = allOptions.filter(
+ (option) =>
+ option.value.toLowerCase().indexOf(event.toLowerCase()) >= 0,
+ );
+ this._loading = false;
+ this._changeDetectorRef.markForCheck();
+ });
+ }
+
+ selectedValue: string;
+ coffees = [
+ { value: 'ThePerfectPour', viewValue: 'ThePerfectPour' },
+ { value: 'Affogato', viewValue: 'Affogato' },
+ { value: 'Americano', viewValue: 'Americano' },
+ ];
+}
diff --git a/apps/dev/src/devapp-routing.module.ts b/apps/dev/src/devapp-routing.module.ts
index 6c91d8cc8a..42b965ee7d 100644
--- a/apps/dev/src/devapp-routing.module.ts
+++ b/apps/dev/src/devapp-routing.module.ts
@@ -77,6 +77,7 @@ import { ToastDemo } from './toast/toast-demo.component';
import { ToggleButtonGroupDemo } from './toggle-button-group/toggle-button-group-demo.component';
import { TopBarNavigationDemo } from './top-bar-navigation/top-bar-navigation-demo.component';
import { TreeTableDemo } from './tree-table/tree-table-demo.component';
+import { ComboboxDemo } from './combobox/combobox-demo.component';
const routes: Routes = [
{ path: 'alert', component: AlertDemo },
@@ -88,6 +89,7 @@ const routes: Routes = [
{ path: 'card', component: CardDemo },
{ path: 'chart', component: ChartDemo },
{ path: 'checkbox', component: CheckboxDemo },
+ { path: 'combobox', component: ComboboxDemo },
{ path: 'consumption', component: ConsumptionDemo },
{ path: 'context-dialog', component: ContextDialogDemo },
{ path: 'confirmation-dialog', component: ConfirmationDialogDemo },
diff --git a/apps/dev/src/devapp.component.ts b/apps/dev/src/devapp.component.ts
index 060b8f70b0..620ff6da17 100644
--- a/apps/dev/src/devapp.component.ts
+++ b/apps/dev/src/devapp.component.ts
@@ -49,6 +49,7 @@ export class DevApp implements AfterContentInit, OnDestroy {
{ name: 'Card', route: '/card' },
{ name: 'Chart', route: '/chart' },
{ name: 'Checkbox', route: '/checkbox' },
+ { name: 'Combobox', route: '/combobox' },
{ name: 'Confirmation-dialog', route: '/confirmation-dialog' },
{ name: 'Consumption', route: '/consumption' },
{
diff --git a/apps/dev/src/dt-components.module.ts b/apps/dev/src/dt-components.module.ts
index 75c7a3715c..1a510c2645 100644
--- a/apps/dev/src/dt-components.module.ts
+++ b/apps/dev/src/dt-components.module.ts
@@ -74,6 +74,7 @@ import { DtToastModule } from '@dynatrace/barista-components/toast';
import { DtToggleButtonGroupModule } from '@dynatrace/barista-components/toggle-button-group';
import { DtTopBarNavigationModule } from '@dynatrace/barista-components/top-bar-navigation';
import { DtTreeTableModule } from '@dynatrace/barista-components/tree-table';
+import { DtComboboxModule } from '@dynatrace/barista-components/experimental/combobox';
/**
* NgModule that includes all Dynatrace angular components modules that are required to serve the examples.
@@ -89,6 +90,7 @@ import { DtTreeTableModule } from '@dynatrace/barista-components/tree-table';
DtCardModule,
DtChartModule,
DtCheckboxModule,
+ DtComboboxModule,
DtConfirmationDialogModule,
DtContextDialogModule,
DtCopyToClipboardModule,
diff --git a/apps/universal/src/app/barista.module.ts b/apps/universal/src/app/barista.module.ts
index a31ef04a6f..f177dfbf68 100644
--- a/apps/universal/src/app/barista.module.ts
+++ b/apps/universal/src/app/barista.module.ts
@@ -58,6 +58,7 @@ import { DtTimelineChartModule } from '@dynatrace/barista-components/timeline-ch
import { DtToggleButtonGroupModule } from '@dynatrace/barista-components/toggle-button-group';
import { DtTopBarNavigationModule } from '@dynatrace/barista-components/top-bar-navigation';
import { DtTreeTableModule } from '@dynatrace/barista-components/tree-table';
+import { DtComboboxModule } from '@dynatrace/barista-components/experimental/combobox';
@NgModule({
imports: [
@@ -71,6 +72,7 @@ import { DtTreeTableModule } from '@dynatrace/barista-components/tree-table';
DtButtonModule,
DtCardModule,
DtCheckboxModule,
+ DtComboboxModule,
DtConsumptionModule,
DtContainerBreakpointObserverModule,
DtContainerBreakpointObserverModule,
diff --git a/apps/universal/src/app/kitchen-sink/kitchen-sink.html b/apps/universal/src/app/kitchen-sink/kitchen-sink.html
index b3d3444a64..484773429c 100644
--- a/apps/universal/src/app/kitchen-sink/kitchen-sink.html
+++ b/apps/universal/src/app/kitchen-sink/kitchen-sink.html
@@ -370,6 +370,24 @@ Packets
my content
+
+ option 1
+ option 2
+ option 3
+
+
+
+
+ Value 1
+ Value 2
+ Value 3
+
+
+ Value 1
+ Value 2
+ Value 3
+
+
{{ tooltip.label }}: {{ tooltip.value }}
diff --git a/libs/barista-components/autocomplete/src/autocomplete-module.ts b/libs/barista-components/autocomplete/src/autocomplete-module.ts
index 30246faf8c..2ca5cf6b2a 100644
--- a/libs/barista-components/autocomplete/src/autocomplete-module.ts
+++ b/libs/barista-components/autocomplete/src/autocomplete-module.ts
@@ -23,9 +23,10 @@ import { DtOptionModule } from '@dynatrace/barista-components/core';
import { DtAutocomplete } from './autocomplete';
import { DtAutocompleteOrigin } from './autocomplete-origin';
import { DtAutocompleteTrigger } from './autocomplete-trigger';
+import { PortalModule } from '@angular/cdk/portal';
@NgModule({
- imports: [CommonModule, OverlayModule, DtOptionModule],
+ imports: [CommonModule, OverlayModule, DtOptionModule, PortalModule],
exports: [
DtAutocompleteTrigger,
DtAutocomplete,
diff --git a/libs/barista-components/autocomplete/src/autocomplete-trigger.ts b/libs/barista-components/autocomplete/src/autocomplete-trigger.ts
index 25928a0f28..48c92adbd4 100644
--- a/libs/barista-components/autocomplete/src/autocomplete-trigger.ts
+++ b/libs/barista-components/autocomplete/src/autocomplete-trigger.ts
@@ -25,16 +25,17 @@ import {
import {
Overlay,
OverlayConfig,
+ OverlayContainer,
OverlayRef,
PositionStrategy,
ViewportRuler,
- OverlayContainer,
} from '@angular/cdk/overlay';
import { DOCUMENT } from '@angular/common';
import {
ChangeDetectorRef,
Directive,
ElementRef,
+ forwardRef,
Host,
Inject,
Input,
@@ -42,43 +43,42 @@ import {
OnDestroy,
Optional,
Provider,
- forwardRef,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
- EMPTY,
- Observable,
- Subject,
- Subscription,
defer,
+ EMPTY,
fromEvent,
merge,
+ Observable,
of as observableOf,
+ Subject,
+ Subscription,
} from 'rxjs';
import {
delay,
filter,
map,
- startWith,
switchMap,
take,
takeUntil,
tap,
+ startWith,
} from 'rxjs/operators';
import {
- DtOption,
- DtOptionSelectionChange,
- DtViewportResizer,
_countGroupLabelsBeforeOption,
_getOptionScrollPosition,
- isDefined,
_readKeyCode,
- stringify,
+ DT_UI_TEST_CONFIG,
DtFlexibleConnectedPositionStrategy,
+ DtOption,
+ DtOptionSelectionChange,
dtSetUiTestAttribute,
- DT_UI_TEST_CONFIG,
DtUiTestConfiguration,
+ DtViewportResizer,
+ isDefined,
+ stringify,
} from '@dynatrace/barista-components/core';
import { DtFormField } from '@dynatrace/barista-components/form-field';
@@ -96,7 +96,7 @@ export const DT_AUTOCOMPLETE_VALUE_ACCESSOR: Provider = {
};
/** The height of the select items. */
-export const AUTOCOMPLETE_OPTION_HEIGHT = 32;
+export const AUTOCOMPLETE_OPTION_HEIGHT = 28;
/** The max height of the select's overlay panel */
export const AUTOCOMPLETE_PANEL_MAX_HEIGHT = 256;
@@ -205,16 +205,18 @@ export class DtAutocompleteTrigger
/** Stream of changes to the selection state of the autocomplete options. */
readonly optionSelections: Observable> = defer(
() => {
- const options = this.autocomplete ? this.autocomplete._options : null;
-
- if (options) {
- return options.changes.pipe(
- startWith(options),
- switchMap(() =>
- merge>(
- ...options.map((option) => option.selectionChange),
- ),
- ),
+ const optionsChanged = this.autocomplete
+ ? this.autocomplete._options
+ : null;
+
+ if (optionsChanged) {
+ return optionsChanged.changes.pipe(
+ startWith(optionsChanged),
+ switchMap(() => {
+ return merge>(
+ ...optionsChanged.map((option) => option.selectionChange),
+ );
+ }),
);
}
@@ -690,8 +692,7 @@ export class DtAutocompleteTrigger
const index = this.autocomplete._keyManager.activeItemIndex || 0;
const labelCount = _countGroupLabelsBeforeOption(
index,
- this.autocomplete._options,
- this.autocomplete._optionGroups,
+ this.autocomplete._options.toArray(),
);
const newScrollPosition = _getOptionScrollPosition(
diff --git a/libs/barista-components/autocomplete/src/autocomplete.html b/libs/barista-components/autocomplete/src/autocomplete.html
index ba9d97016f..2a86fe6cd7 100644
--- a/libs/barista-components/autocomplete/src/autocomplete.html
+++ b/libs/barista-components/autocomplete/src/autocomplete.html
@@ -7,5 +7,6 @@
#panel
>
+
diff --git a/libs/barista-components/autocomplete/src/autocomplete.spec.ts b/libs/barista-components/autocomplete/src/autocomplete.spec.ts
index dc0070742f..471e453283 100644
--- a/libs/barista-components/autocomplete/src/autocomplete.spec.ts
+++ b/libs/barista-components/autocomplete/src/autocomplete.spec.ts
@@ -35,6 +35,9 @@ import {
Type,
ViewChild,
ViewChildren,
+ TemplateRef,
+ ViewContainerRef,
+ AfterViewInit,
} from '@angular/core';
import {
ComponentFixture,
@@ -78,6 +81,7 @@ import {
MockNgZone,
typeInElement,
} from '@dynatrace/testing/browser';
+import { TemplatePortal } from '@angular/cdk/portal';
describe('DtAutocomplete', () => {
let overlayContainer: OverlayContainer;
@@ -727,7 +731,7 @@ describe('DtAutocomplete', () => {
let UP_ARROW_EVENT: KeyboardEvent;
let ENTER_EVENT: KeyboardEvent;
- beforeEach(fakeAsync(() => {
+ beforeEach(() => {
fixture = createComponent(SimpleAutocomplete);
fixture.detectChanges();
@@ -739,18 +743,19 @@ describe('DtAutocomplete', () => {
fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
zone.simulateZoneExit();
- }));
+ });
- it('should not focus the option when DOWN key is pressed', () => {
+ it('should not focus the option when DOWN key is pressed', fakeAsync(() => {
jest
.spyOn(fixture.componentInstance.options.first, 'focus')
.mockImplementation(() => {});
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
+
expect(
fixture.componentInstance.options.first.focus,
).not.toHaveBeenCalled();
- });
+ }));
it('should not close the panel when DOWN key is pressed', () => {
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
@@ -1594,6 +1599,19 @@ describe('DtAutocomplete', () => {
);
});
});
+ describe('additional programmatic options', () => {
+ it('should add the additional option at the end of the normal content projected options', () => {
+ const fixture: ComponentFixture = createComponent(
+ ProgrammaticOptions,
+ );
+ fixture.detectChanges();
+ const trigger = fixture.componentInstance.trigger;
+ trigger.openPanel();
+ fixture.detectChanges();
+ expect(overlayContainerElement.textContent).toContain('First');
+ expect(overlayContainerElement.textContent).toContain('Second');
+ });
+ });
});
@Component({
@@ -1934,3 +1952,45 @@ class PropagateAttribute {
@ViewChild(DtAutocompleteTrigger, { static: false })
trigger: DtAutocompleteTrigger;
}
+
+@Component({
+ template: `
+
+
+ First
+
+
+
+ Second
+
+ `,
+})
+class ProgrammaticOptions implements AfterViewInit {
+ @ViewChild(DtAutocompleteTrigger, { static: false })
+ trigger: DtAutocompleteTrigger;
+
+ @ViewChild(TemplateRef) templateRef: TemplateRef;
+
+ @ViewChild(DtAutocomplete, { static: true }) autocomplete: DtAutocomplete<
+ number
+ >;
+
+ @ViewChildren(DtOption) options: QueryList>;
+
+ constructor(private _viewContainerRef: ViewContainerRef) {}
+
+ ngAfterViewInit(): void {
+ this.autocomplete._additionalPortal = new TemplatePortal(
+ this.templateRef,
+ this._viewContainerRef,
+ );
+ this.autocomplete._additionalOptions = [this.options.last];
+ }
+}
diff --git a/libs/barista-components/autocomplete/src/autocomplete.ts b/libs/barista-components/autocomplete/src/autocomplete.ts
index 4c0e2afabe..6c59defc72 100644
--- a/libs/barista-components/autocomplete/src/autocomplete.ts
+++ b/libs/barista-components/autocomplete/src/autocomplete.ts
@@ -35,9 +35,11 @@ import {
ViewChild,
ViewContainerRef,
ViewEncapsulation,
+ OnDestroy,
} from '@angular/core';
-import { DtOptgroup, DtOption } from '@dynatrace/barista-components/core';
+import { DtOption } from '@dynatrace/barista-components/core';
+import { Subscription } from 'rxjs';
let _uniqueIdCounter = 0;
@@ -82,7 +84,8 @@ export function DT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): DtAutocompleteDefault
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class DtAutocomplete implements AfterContentInit, AfterViewInit {
+export class DtAutocomplete
+ implements AfterContentInit, AfterViewInit, OnDestroy {
/**
* Whether the first option should be highlighted when the autocomplete panel is opened.
* Can be configured globally through the `DT_AUTOCOMPLETE_DEFAULT_OPTIONS` token.
@@ -159,6 +162,9 @@ export class DtAutocomplete implements AfterContentInit, AfterViewInit {
/** @internal */
_portal: TemplatePortal;
+ /** @internal Additional portal used when additional programmatic options need to be added */
+ _additionalPortal: TemplatePortal;
+
/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
id = `dt-autocomplete-${_uniqueIdCounter++}`;
@@ -176,12 +182,31 @@ export class DtAutocomplete implements AfterContentInit, AfterViewInit {
* @internal References to all the options that are currently applied.
*/
@ContentChildren(DtOption, { descendants: true })
- _options: QueryList>;
+ private _projectedOptions: QueryList>;
- /**
- * @interal References to all the option groups that are currently applied.
- */
- @ContentChildren(DtOptgroup) _optionGroups: QueryList;
+ /** @internal The options that are added programmatically and not part of the content children */
+ get _additionalOptions(): DtOption[] {
+ return this._additionalOptionsInternal;
+ }
+ set _additionalOptions(val: DtOption[]) {
+ this._additionalOptionsInternal = val;
+ this._combineOptions();
+ // only emit a changes event when content init is already done
+ // similar to the querylist changes event
+ if (this._initialized) {
+ this._options.notifyOnChanges();
+ }
+ }
+ private _additionalOptionsInternal: DtOption[] = [];
+
+ /** @internal Querylist that combines projected & programmatic changes */
+ _options = new QueryList>();
+
+ /** Property to trace whether initialization is already done */
+ private _initialized = false;
+
+ private _projectedOptionsChangeSubscription: Subscription =
+ Subscription.EMPTY;
constructor(
private _changeDetectorRef: ChangeDetectorRef,
@@ -198,11 +223,27 @@ export class DtAutocomplete implements AfterContentInit, AfterViewInit {
}
ngAfterContentInit(): void {
+ // Combine the changes first
+ this._combineOptions();
+ // init keymanager with custom querylist that combines projected and programmatic options
this._keyManager = new ActiveDescendantKeyManager>(
this._options,
).withWrap();
// Set the initial visibility state.
this._setVisibility();
+ this._projectedOptionsChangeSubscription = this._projectedOptions.changes.subscribe(
+ () => {
+ this._combineOptions();
+ this._options.notifyOnChanges();
+ },
+ );
+ // We need this property here so we don't emit a change event on the first changes for programmatic actions
+ // similar to what the querylist does
+ this._initialized = true;
+ }
+
+ ngOnDestroy(): void {
+ this._projectedOptionsChangeSubscription.unsubscribe();
}
/**
@@ -243,6 +284,14 @@ export class DtAutocomplete implements AfterContentInit, AfterViewInit {
this.optionSelected.emit(event);
}
+ /** Combines the projected and the programmatic options and updates the querylist */
+ private _combineOptions(): void {
+ const options = this._projectedOptions
+ .toArray()
+ .concat(this._additionalOptionsInternal);
+ this._options.reset(options);
+ }
+
/** Sets the autocomplete visibility classes on a classlist based on the panel is visible. */
private _setVisibilityClasses(classList: { [key: string]: boolean }): void {
classList['dt-autocomplete-visible'] = this.showPanel;
diff --git a/libs/barista-components/core/src/error/errors.ts b/libs/barista-components/core/src/error/errors.ts
new file mode 100644
index 0000000000..c95bec082d
--- /dev/null
+++ b/libs/barista-components/core/src/error/errors.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const DT_COMPARE_WITH_NON_FUNCTION_VALUE_ERROR_MSG =
+ '`compareWith` must be a function.';
diff --git a/libs/barista-components/core/src/error/index.ts b/libs/barista-components/core/src/error/index.ts
index b954b1c85c..008195f954 100644
--- a/libs/barista-components/core/src/error/index.ts
+++ b/libs/barista-components/core/src/error/index.ts
@@ -15,3 +15,4 @@
*/
export * from './error-matcher';
+export * from './errors';
diff --git a/libs/barista-components/core/src/option/option.ts b/libs/barista-components/core/src/option/option.ts
index 6ac0b58909..bd2b6a6eda 100644
--- a/libs/barista-components/core/src/option/option.ts
+++ b/libs/barista-components/core/src/option/option.ts
@@ -27,13 +27,13 @@ import {
OnDestroy,
Optional,
Output,
- QueryList,
ViewEncapsulation,
} from '@angular/core';
import { Subject } from 'rxjs';
import { _readKeyCode } from '../util/index';
import { DtOptgroup } from './optgroup';
+import { Highlightable } from '@angular/cdk/a11y';
let _uniqueId = 0;
@@ -69,7 +69,7 @@ export class DtOptionSelectionChange {
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class DtOption implements AfterViewChecked, OnDestroy {
+export class DtOption implements Highlightable, AfterViewChecked, OnDestroy {
private _selected = false;
private _active = false;
private _disabled = false;
@@ -257,24 +257,18 @@ export class DtOption implements AfterViewChecked, OnDestroy {
/** Counts the amount of option group labels that precede the specified option. */
export function _countGroupLabelsBeforeOption(
optionIndex: number,
- options: QueryList>,
- optionGroups: QueryList,
+ options: DtOption[],
): number {
- if (optionGroups.length) {
- const optionsArray = options.toArray();
- const groups = optionGroups.toArray();
- let groupCounter = 0;
+ if (options.some((option) => !!option.group)) {
+ const optionsArray = options;
+ const groups = new Set();
for (let i = 0; i < optionIndex + 1; i++) {
- if (
- optionsArray[i].group &&
- optionsArray[i].group === groups[groupCounter]
- ) {
- groupCounter++;
+ if (optionsArray[i].group && !groups.has(optionsArray[i].group!)) {
+ groups.add(optionsArray[i].group!);
}
}
-
- return groupCounter;
+ return groups.size;
}
return 0;
diff --git a/libs/barista-components/experimental/combobox/README.md b/libs/barista-components/experimental/combobox/README.md
new file mode 100644
index 0000000000..f94bb41597
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/README.md
@@ -0,0 +1,68 @@
+# Combobox (experimental)
+
+The `` is similar to the `` component in the sense that
+it is a form control for selecting a value from a list of options. The major
+differences between the two components is that the `` allows the
+user to freely filter for options before selecting one. This makes it much more
+suitable for large amounts of data that couldn't be handled well by the
+`` component. It is also designed to work with Angular forms. By
+using the `` element, which is also provided in the select module,
+you can add values to the select. Also, the use of `` is supported
+for grouping options.
+
+
+
+## Imports
+
+You have to import the `DtComboboxModule` when you want to use the
+``. The `` component also requires Angular's
+`BrowserAnimationsModule` for animations. For more details on this see
+[_Step 2: Animations_](/components/get-started/#step-2-animations) in the
+getting started guide.
+
+```typescript
+@NgModule({
+ imports: [DtComboboxModule],
+})
+class MyModule {}
+```
+
+## Initialization
+
+The API of the `` is very similar to the native `` element,
+but has some additional useful functions, like a placeholder property. It is
+possible to disable the entire select or individual options in the select by
+using the disabled property on the `` or ``
+
+The `` also supports all of the form directives from the core
+FormsModule (NgModel) and ReactiveFormsModule (FormControl, FormGroup, etc.).
+
+## DtCombobox Inputs
+
+| Name | Type | Default Value | Description |
+| ----------------- | ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
+| `id` | `string` | `''` | The ID for the combobox. |
+| `value` | `T | null` | `null` | The currently selected value in the combobox. |
+| `loading` | `boolean` | `false` | When set to true, a loading indicator is shown to show to the user that data is currently being loaded/filtered. |
+| `required` | `boolean` | `false` | Whether the control is required. |
+| `panelClass` | `string` | `''` | An arbitrary class name that is added to the combobox dropdown. |
+| `placeholder` | `string | undefined` | `undefined` | A placeholder text for the input field. |
+| `displayWith` | `(value: T) => string` | (value: T) =>`\${value}` | A function returning a display name for a given object that represents an option from the combobox. |
+| `aria-label` | `string` | `undefined` | Aria label of the select. |
+| `aria-labelledby` | `string` | `undefined` | Input that can be used to specify the `aria-labelledby` attribute. |
+| `focused` | `boolean` | `false` | Whether the control is focused. |
+
+## DtOption inputs
+
+| Name | Type | Description |
+| ---------- | --------- | ------------------------------- |
+| `value` | `T` | The form value of the option. |
+| `disabled` | `boolean` | Whether the option is disabled. |
+
+## DtCombobox Outputs
+
+| Name | Type | Description |
+| ------------ | ----------------------- | ----------------------------------------------------- |
+| valueChange | `EventEmitter` | Event emitted when a new value has been selected. |
+| filterChange | `EventEmitter` | Event emitted when the filter changes. |
+| openedChange | `EventEmitter` | Event emitted when the select panel has been toggled. |
diff --git a/libs/barista-components/experimental/combobox/barista.json b/libs/barista-components/experimental/combobox/barista.json
new file mode 100644
index 0000000000..d943ba5a4d
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/barista.json
@@ -0,0 +1,29 @@
+{
+ "title": "Combobox",
+ "description": "The combobox component can be used to provide typeahead filtering for a list of options.",
+ "postid": "combobox",
+ "identifier": "Co",
+ "category": "components",
+ "public": true,
+ "contributors": {
+ "dev": [
+ {
+ "name": "Christoph Matscheko",
+ "gitHubUser": "heartdisease"
+ },
+ {
+ "name": "Fabian Friedl",
+ "gitHubUser": "ffriedl89"
+ }
+ ],
+ "ux": [
+ {
+ "name": "Andreas Mayr",
+ "gitHubUser": "moarliman"
+ }
+ ]
+ },
+ "properties": ["work in progress", "experimental"],
+ "related": ["input"],
+ "tags": ["angular", "component"]
+}
diff --git a/libs/barista-components/experimental/combobox/index.ts b/libs/barista-components/experimental/combobox/index.ts
new file mode 100644
index 0000000000..c77286c25f
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/index.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DtCombobox } from './src/combobox';
+export * from './src/combobox-module';
diff --git a/libs/barista-components/experimental/combobox/jest.config.js b/libs/barista-components/experimental/combobox/jest.config.js
new file mode 100644
index 0000000000..3cad0572ad
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/jest.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ name: 'combobox',
+ preset: '../../../../jest.config.js',
+ coverageDirectory: '../../../../coverage/components/combobox',
+ snapshotSerializers: [
+ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
+ 'jest-preset-angular/build/AngularSnapshotSerializer.js',
+ 'jest-preset-angular/build/HTMLCommentSerializer.js',
+ ],
+};
diff --git a/libs/barista-components/experimental/combobox/package.json b/libs/barista-components/experimental/combobox/package.json
new file mode 100644
index 0000000000..dedb72ce9c
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/package.json
@@ -0,0 +1,7 @@
+{
+ "ngPackage": {
+ "lib": {
+ "entryFile": "index.ts"
+ }
+ }
+}
diff --git a/libs/barista-components/experimental/combobox/src/combobox-module.ts b/libs/barista-components/experimental/combobox/src/combobox-module.ts
new file mode 100644
index 0000000000..c3398a77e0
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/src/combobox-module.ts
@@ -0,0 +1,43 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { NgModule } from '@angular/core';
+
+import { FormsModule } from '@angular/forms';
+
+import { CommonModule } from '@angular/common';
+import { DtInputModule } from '@dynatrace/barista-components/input';
+import { DtCombobox } from './combobox';
+import { DtIconModule } from '@dynatrace/barista-components/icon';
+import { DtAutocompleteModule } from '@dynatrace/barista-components/autocomplete';
+import { DtLoadingDistractorModule } from '@dynatrace/barista-components/loading-distractor';
+import { PortalModule } from '@angular/cdk/portal';
+
+@NgModule({
+ exports: [DtCombobox],
+ declarations: [DtCombobox],
+ imports: [
+ CommonModule,
+ PortalModule,
+ FormsModule,
+ DtIconModule,
+ DtInputModule,
+ DtAutocompleteModule,
+ DtLoadingDistractorModule,
+ PortalModule,
+ ],
+})
+export class DtComboboxModule {}
diff --git a/libs/barista-components/experimental/combobox/src/combobox.html b/libs/barista-components/experimental/combobox/src/combobox.html
new file mode 100644
index 0000000000..20f31c006e
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/src/combobox.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/barista-components/experimental/combobox/src/combobox.scss b/libs/barista-components/experimental/combobox/src/combobox.scss
new file mode 100644
index 0000000000..3164303e81
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/src/combobox.scss
@@ -0,0 +1,96 @@
+@import '../../../style/font-mixins';
+@import '../../../core/src/style/variables';
+@import '../../../core/src/style/form-control';
+@import '../../../core/src/style/interactive-common';
+
+:host {
+ display: inline-block;
+ box-sizing: border-box;
+ outline: none;
+ @include dt-main-font();
+ @include dt-form-control();
+ @include dt-cdkmonitor-focus-style();
+
+ // Do not allow it to grow outside its wrapper
+ max-width: 100%;
+
+ .dt-combobox-spinner {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ fill: $gray-500;
+ }
+
+ &.dt-combobox-open .dt-combobox-arrow {
+ transform: rotate(180deg);
+ }
+
+ &:hover:not(.dt-select-disabled) {
+ border-color: $gray-500;
+ cursor: pointer;
+ }
+}
+
+:host.dt-combobox-disabled {
+ background-color: $gray-100;
+ color: $disabledcolor;
+
+ .dt-combobox-trigger {
+ pointer-events: none;
+ }
+
+ .dt-combobox-input,
+ .dt-combobox-input::placeholder {
+ color: $disabledcolor;
+ }
+}
+
+:host.dt-combobox-disabled .dt-combobox-arrow ::ng-deep svg {
+ fill: $disabledcolor;
+}
+
+.dt-combobox-trigger {
+ display: grid;
+ grid-template-columns: 1fr 30px;
+ align-items: center;
+ border-radius: 3px;
+}
+
+.dt-combobox-input {
+ @include dt-main-font();
+ appearance: none;
+ position: relative;
+ display: inline-block;
+ box-sizing: border-box;
+ text-decoration: none;
+ padding: 0 0 0 12px;
+ line-height: -moz-block-height;
+ vertical-align: middle;
+ white-space: nowrap;
+ text-align: left;
+ border: none;
+ width: 100%;
+ outline: none;
+ min-height: 30px;
+ border-radius: 3px;
+}
+
+.dt-combobox-input::placeholder {
+ @include dt-main-font();
+}
+
+.dt-combobox-postfix {
+ justify-self: center;
+}
+
+.dt-combobox-arrow {
+ width: 16px;
+ transform: rotate(0);
+ transition: transform 150ms ease-out;
+ fill: $turquoise-600;
+
+ ::ng-deep svg {
+ width: 100%;
+ height: 100%;
+ }
+}
diff --git a/libs/barista-components/experimental/combobox/src/combobox.ts b/libs/barista-components/experimental/combobox/src/combobox.ts
new file mode 100644
index 0000000000..9624f87eb0
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/src/combobox.ts
@@ -0,0 +1,541 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ContentChildren,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ OnInit,
+ Optional,
+ Output,
+ QueryList,
+ TemplateRef,
+ ViewChild,
+ ViewContainerRef,
+ ViewEncapsulation,
+ Self,
+ Attribute,
+ AfterContentInit,
+ NgZone,
+} from '@angular/core';
+import {
+ DtAutocomplete,
+ DtAutocompleteSelectedEvent,
+ DtAutocompleteTrigger,
+} from '@dynatrace/barista-components/autocomplete';
+import { fromEvent, Subject, Observable, defer, merge } from 'rxjs';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ map,
+ takeUntil,
+ take,
+ switchMap,
+} from 'rxjs/operators';
+import {
+ CanDisable,
+ DtOption,
+ ErrorStateMatcher,
+ HasTabIndex,
+ mixinDisabled,
+ mixinErrorState,
+ mixinTabIndex,
+ DtOptionSelectionChange,
+ isDefined,
+ DtLogger,
+ DtLoggerFactory,
+ DT_COMPARE_WITH_NON_FUNCTION_VALUE_ERROR_MSG,
+} from '@dynatrace/barista-components/core';
+import { DtFormFieldControl } from '@dynatrace/barista-components/form-field';
+import {
+ FormGroupDirective,
+ NgControl,
+ NgForm,
+ ControlValueAccessor,
+} from '@angular/forms';
+import { TemplatePortal } from '@angular/cdk/portal';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { SelectionModel } from '@angular/cdk/collections';
+
+const LOG: DtLogger = DtLoggerFactory.create('DtCombobox');
+
+/** Change event object that is emitted when the combobox value has changed. */
+export class DtComboboxChange {
+ constructor(
+ /** Reference to the combobox that emitted the change event. */
+ public source: DtCombobox,
+ /** Current value of the combobox that emitted the event. */
+ public value: T,
+ ) {}
+}
+
+export class DtComboboxBase {
+ constructor(
+ public _elementRef: ElementRef,
+ public _defaultErrorStateMatcher: ErrorStateMatcher,
+ public _parentForm: NgForm,
+ public _parentFormGroup: FormGroupDirective,
+ public ngControl: NgControl,
+ ) {}
+}
+export const _DtComboboxMixinBase = mixinTabIndex(
+ mixinDisabled(mixinErrorState(DtComboboxBase)),
+);
+
+@Component({
+ selector: 'dt-combobox',
+ exportAs: 'dtCombobox',
+ templateUrl: 'combobox.html',
+ styleUrls: ['combobox.scss'],
+ host: {
+ class: 'dt-combobox',
+ // role: 'listbox', // TODO ChMa: a11y build still fails with "Certain ARIA roles must contain particular children"
+ '[class.dt-combobox-loading]': '_loading',
+ '[class.dt-combobox-disabled]': 'disabled',
+ '[class.dt-combobox-invalid]': 'errorState',
+ '[class.dt-combobox-required]': 'required',
+ '[class.dt-combobox-open]': '_panelOpen',
+ '[attr.id]': 'id',
+ '[attr.tabindex]': 'tabIndex',
+ '[attr.aria-required]': 'required.toString()',
+ '[attr.aria-disabled]': 'disabled.toString()',
+ '[attr.aria-invalid]': 'errorState',
+ },
+ inputs: ['disabled', 'tabIndex'],
+ providers: [{ provide: DtFormFieldControl, useExisting: DtCombobox }],
+ encapsulation: ViewEncapsulation.Emulated,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DtCombobox extends _DtComboboxMixinBase
+ implements
+ OnInit,
+ AfterContentInit,
+ AfterViewInit,
+ OnDestroy,
+ CanDisable,
+ HasTabIndex,
+ ControlValueAccessor,
+ DtFormFieldControl {
+ /** The ID for the combobox. */
+ @Input() id: string;
+ /** The currently selected value in the combobox. */
+ @Input()
+ get value(): T {
+ return this._value;
+ }
+ set value(newValue: T) {
+ if (newValue !== this._value) {
+ this.writeValue(newValue);
+ this._value = newValue;
+ }
+ }
+ private _value: T;
+
+ /** When set to true, a loading indicator is shown to show to the user that data is currently being loaded/filtered. */
+ @Input()
+ get loading(): boolean {
+ return this._loading;
+ }
+ set loading(value: boolean) {
+ const coercedValue = coerceBooleanProperty(value);
+
+ if (coercedValue !== this._loading) {
+ this._loading = coercedValue;
+
+ if (this._loading) {
+ this._reopenAutocomplete = true;
+ this._autocompleteTrigger.closePanel();
+ } else if (this._reopenAutocomplete) {
+ this._reopenAutocomplete = false;
+ this._autocompleteTrigger.openPanel();
+ }
+ this._changeDetectorRef.markForCheck();
+ }
+ }
+ _loading = false;
+
+ /** Whether the control is required. */
+ @Input() required: boolean = false;
+ /** An arbitrary class name that is added to the combobox dropdown. */
+ @Input() panelClass: string = '';
+ /** A placeholder text for the input field. */
+ @Input() placeholder: string | undefined;
+ /** A function returning a display name for a given object that represents an option from the combobox. */
+ @Input() displayWith: (value: T) => string = (value: T) => `${value}`;
+ /** Aria label of the combobox. */
+ @Input('aria-label') ariaLabel: string;
+ /** Input that can be used to specify the `aria-labelledby` attribute. */
+ @Input('aria-labelledby') ariaLabelledBy: string;
+ /** Whether the control is focused. (TODO ChMa: implement!) */
+ @Input() focused: boolean;
+
+ /**
+ * Function to compare the option values with the selected values. The first argument
+ * is a value from an option. The second is a value from the selection. A boolean
+ * should be returned.
+ * Defaults to value equality.
+ */
+ @Input()
+ get compareWith(): (v1: T, v2: T) => boolean {
+ return this._compareWith;
+ }
+ set compareWith(fn: (v1: T, v2: T) => boolean) {
+ // tslint:disable-next-line:strict-type-predicates
+ if (typeof fn !== 'function') {
+ LOG.error(DT_COMPARE_WITH_NON_FUNCTION_VALUE_ERROR_MSG);
+ }
+ this._compareWith = fn;
+ if (this._selectionModel) {
+ // A different comparator means the selection could change.
+ this._initializeSelection();
+ }
+ }
+ private _compareWith = (v1: T, v2: T) => v1 === v2;
+
+ /** Event emitted when a new value has been selected. */
+ @Output() valueChange = new EventEmitter();
+ /** Event emitted when the selected value has been changed by the user. */
+ @Output() readonly selectionChange = new EventEmitter>();
+ /** Event emitted when the filter changes. */
+ @Output() filterChange = new EventEmitter();
+ /** Event emitted when the combobox panel has been toggled. */
+ @Output() openedChange = new EventEmitter();
+
+ /** Combined stream of all of the child options' change events. */
+ readonly optionSelectionChanges: Observable<
+ DtOptionSelectionChange
+ > = defer(() => {
+ if (this._options) {
+ return merge>(
+ ...this._options.map((option) => option.selectionChange),
+ );
+ }
+
+ return this._ngZone.onStable.asObservable().pipe(
+ take(1),
+ switchMap(() => this.optionSelectionChanges),
+ );
+ });
+
+ /** @internal The trigger of the internal autocomplete trigger */
+ @ViewChild('autocompleteTrigger', { static: true })
+ _autocompleteTrigger: DtAutocompleteTrigger;
+ /** @internal The elementRef of the input used internally */
+ @ViewChild('searchInput', { static: true }) _searchInput: ElementRef;
+ /**
+ * @internal The templateRef used to capture the options passed via ng-content
+ * to pass through to the autocomplete
+ */
+ @ViewChild('autocompleteContent') _templatePortalContent: TemplateRef;
+ /** @internal The autocomplete instance that holds all options */
+ @ViewChild(DtAutocomplete) _autocomplete: DtAutocomplete;
+
+ /** @internal The options received via ng-content */
+ @ContentChildren(DtOption, { descendants: true })
+ _options: QueryList>;
+
+ /** @return false
if no value is currently selected. */
+ get empty(): boolean {
+ return !this._selectionModel || this._selectionModel.isEmpty();
+ }
+
+ /** The currently selected option. */
+ get selected(): DtOption {
+ return this._selectionModel.selected[0];
+ }
+
+ /** The value displayed in the trigger. */
+ get triggerValue(): string {
+ return !this.empty ? this._selectionModel.selected[0].viewValue : '';
+ }
+
+ /** @internal is false
if the autocomplete panel is not shown */
+ _panelOpen = false;
+
+ /** @internal `View -> model callback called when value changes` */
+ _onChange: (value: any) => void = () => {};
+
+ /** @internal `View -> model callback called when combobox has been touched` */
+ _onTouched = () => {};
+
+ /** @internal Deals with the selection logic. */
+ _selectionModel: SelectionModel>;
+
+ private _reopenAutocomplete = false;
+ /** Emits whenever the component is destroyed. */
+ private readonly _destroy = new Subject();
+
+ constructor(
+ public _elementRef: ElementRef,
+ @Optional() public _defaultErrorStateMatcher: ErrorStateMatcher,
+ @Optional() public _parentForm: NgForm,
+ @Optional() public _parentFormGroup: FormGroupDirective,
+ @Self() @Optional() public ngControl: NgControl,
+ @Attribute('tabindex') tabIndex: string,
+ private _viewContainerRef: ViewContainerRef,
+ private _changeDetectorRef: ChangeDetectorRef,
+ private _ngZone: NgZone,
+ ) {
+ super(
+ _elementRef,
+ _defaultErrorStateMatcher,
+ _parentForm,
+ _parentFormGroup,
+ ngControl,
+ );
+
+ if (this.ngControl) {
+ // Note: we provide the value accessor through here, instead of
+ // the `providers` to avoid running into a circular import.
+ this.ngControl.valueAccessor = this;
+ }
+
+ this.tabIndex = parseInt(tabIndex, 10) || 0;
+
+ // Force setter to be called in case id was not specified.
+ this.id = this.id;
+ }
+
+ ngOnInit(): void {
+ this._selectionModel = new SelectionModel>();
+ this.stateChanges.next();
+
+ fromEvent(this._searchInput.nativeElement, 'input')
+ .pipe(
+ map((event: KeyboardEvent): string => {
+ event.stopPropagation();
+ return this._searchInput.nativeElement.value;
+ }),
+ distinctUntilChanged(),
+ debounceTime(150),
+ takeUntil(this._destroy),
+ )
+ .subscribe((query) => this.filterChange.emit(query));
+ }
+
+ ngAfterContentInit(): void {
+ this._selectionModel.changed
+ .pipe(takeUntil(this._destroy))
+ .subscribe((event) => {
+ event.added.forEach((option) => {
+ option.select();
+ });
+ event.removed.forEach((option) => {
+ option.deselect();
+ });
+ });
+ }
+
+ ngAfterViewInit(): void {
+ this._autocomplete._additionalPortal = new TemplatePortal(
+ this._templatePortalContent,
+ this._viewContainerRef,
+ );
+ this._autocomplete._additionalOptions = this._options.toArray();
+ this._autocompleteTrigger.registerOnChange(() => this._onChange);
+ this._autocompleteTrigger.registerOnTouched(() => this._onTouched);
+ }
+
+ ngOnDestroy(): void {
+ this._destroy.next();
+ this._destroy.complete();
+ }
+
+ /** Toggles the panel containing the options of the combobox */
+ toggle(): void {
+ // TODO: note that currently if the toggle method is called inside an event handler
+ // e.g. onclick of a button and the event is not stopped from bubbling to the document
+ // the click outside event stream in the autocomplete fires and immediately closes the overlay after opening
+ if (this._panelOpen) {
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+
+ /** Opens the panel containing the options of the combobox */
+ open(): void {
+ this._panelOpen = true;
+ this._autocompleteTrigger.openPanel(); // implicitly triggers _opened()
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** Closes the panel containing the options of the combobox */
+ close(): void {
+ this._panelOpen = false;
+ this._autocompleteTrigger.closePanel(); // implicitly triggers _closed()
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** Sets the list of element IDs that currently describe this control. */
+ setDescribedByIds(_: string[]): void {
+ // TODO ChMa: implement (what does this even do?)
+ }
+
+ /** Handles a click on the control's container. */
+ onContainerClick(_: MouseEvent): void {
+ // TODO ChMa: implement (do we even need this handler?)
+ }
+
+ /** Sets the combobox's value. Part of the ControlValueAccessor. */
+ writeValue(value: T): void {
+ if (this._options) {
+ this._setSelectionByValue(value);
+ if (this._autocompleteTrigger) {
+ this._autocompleteTrigger.writeValue(value);
+ }
+ }
+ }
+
+ /**
+ * Saves a callback function to be invoked when the select's value
+ * changes from user input. Part of the ControlValueAccessor.
+ */
+ registerOnChange(fn: (value: any) => {}): void {
+ this._onChange = fn;
+ this._autocompleteTrigger.registerOnChange(fn);
+ }
+
+ /**
+ * Saves a callback function to be invoked when the combobox is blurred
+ * by the user. Part of the ControlValueAccessor.
+ */
+ registerOnTouched(fn: () => {}): void {
+ this._onTouched = fn;
+ this._autocompleteTrigger.registerOnTouched(fn);
+ }
+
+ /** Disables the combobox. Part of the ControlValueAccessor. */
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ this._changeDetectorRef.markForCheck();
+ this.stateChanges.next();
+ }
+
+ /** @internal called when the user selects a different option */
+ _optionSelected(event: DtAutocompleteSelectedEvent): void {
+ const option = event.option;
+ const wasSelected = this._selectionModel.isSelected(option);
+
+ if (isDefined(option.value)) {
+ if (option.selected) {
+ this._selectionModel.select(option);
+ } else {
+ this._selectionModel.deselect(option);
+ }
+ } else {
+ option.deselect();
+ this._selectionModel.clear();
+ this._propagateChanges(option.value);
+ }
+
+ if (wasSelected !== this._selectionModel.isSelected(option)) {
+ this._propagateChanges();
+ }
+
+ this.stateChanges.next();
+
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /**
+ * @internal called when user clicks on trigger,
+ * stops event propagation to handle toggling correctly on the trigger
+ */
+ _toggle(event: MouseEvent): void {
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.toggle();
+ }
+
+ /** @internal called when dt-autocomplete emits an open event */
+ _opened(): void {
+ this._panelOpen = true;
+ this.openedChange.emit(true);
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** @internal called when dt-autocomplete emits a close event */
+ _closed(): void {
+ this._panelOpen = false;
+ this.openedChange.emit(false);
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** Emits change event to set the model value. */
+ private _propagateChanges(fallbackValue?: T): void {
+ const valueToEmit = this.selected ? this.selected.value : fallbackValue;
+ this._value = valueToEmit!;
+ this.valueChange.emit(valueToEmit);
+ this._onChange(valueToEmit!);
+ this.selectionChange.emit(new DtComboboxChange(this, valueToEmit!));
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** Handles the initial value selection */
+ private _initializeSelection(): void {
+ // Defer setting the value in order to avoid the "Expression
+ // has changed after it was checked" errors from Angular.
+ Promise.resolve().then(() => {
+ this._setSelectionByValue(
+ this.ngControl ? this.ngControl.value : this._value,
+ );
+ });
+ }
+
+ /** Updates the selection by value using selection model and keymanager to handle the active item */
+ private _setSelectionByValue(value: T): void {
+ this._selectionModel.clear();
+ const correspondingOption = this._selectValue(value);
+
+ // Shift focus to the active item. Note that we shouldn't do this in multiple
+ // mode, because we don't know what option the user interacted with last.
+ if (correspondingOption && this._autocomplete?._keyManager) {
+ this._autocomplete._keyManager.setActiveItem(correspondingOption);
+ }
+
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** Searches for an option matching the value and selects it if found */
+ private _selectValue(value: T): DtOption | undefined {
+ const correspondingOption = this._options.find((option: DtOption) => {
+ try {
+ // Treat null as a special reset value.
+ return (
+ isDefined(option.value) && this._compareWith(option.value, value)
+ );
+ } catch (error) {
+ // Notify developers of errors in their comparator.
+ LOG.warn(error);
+ return false;
+ }
+ });
+
+ if (correspondingOption) {
+ this._selectionModel.select(correspondingOption);
+ }
+
+ return correspondingOption;
+ }
+}
diff --git a/libs/barista-components/experimental/combobox/src/test-setup.ts b/libs/barista-components/experimental/combobox/src/test-setup.ts
new file mode 100644
index 0000000000..3c66e43d72
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/src/test-setup.ts
@@ -0,0 +1,17 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'jest-preset-angular';
diff --git a/libs/barista-components/experimental/combobox/tsconfig.json b/libs/barista-components/experimental/combobox/tsconfig.json
new file mode 100644
index 0000000000..a6233e13a0
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["node", "jest"]
+ },
+ "include": ["**/*.ts"]
+}
diff --git a/libs/barista-components/experimental/combobox/tsconfig.lib.json b/libs/barista-components/experimental/combobox/tsconfig.lib.json
new file mode 100644
index 0000000000..b639cbf40a
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/tsconfig.lib.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": ["dom", "es2018"]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": ["src/test-setup.ts", "**/*.spec.ts"]
+}
diff --git a/libs/barista-components/experimental/combobox/tsconfig.spec.json b/libs/barista-components/experimental/combobox/tsconfig.spec.json
new file mode 100644
index 0000000000..aed68bc6bf
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "files": ["src/test-setup.ts"],
+ "include": ["**/*.spec.ts", "**/*.d.ts"]
+}
diff --git a/libs/barista-components/experimental/combobox/tslint.json b/libs/barista-components/experimental/combobox/tslint.json
new file mode 100644
index 0000000000..b20a356378
--- /dev/null
+++ b/libs/barista-components/experimental/combobox/tslint.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../../../tslint.json",
+ "rules": {}
+}
diff --git a/libs/barista-components/select/src/select.ts b/libs/barista-components/select/src/select.ts
index 13be13ba2d..e61f07a95a 100644
--- a/libs/barista-components/select/src/select.ts
+++ b/libs/barista-components/select/src/select.ts
@@ -97,6 +97,7 @@ import {
DT_UI_TEST_CONFIG,
DtUiTestConfiguration,
dtSetUiTestAttribute,
+ DT_COMPARE_WITH_NON_FUNCTION_VALUE_ERROR_MSG,
} from '@dynatrace/barista-components/core';
import {
DtFormField,
@@ -108,7 +109,7 @@ let uniqueId = 0;
const LOG: DtLogger = DtLoggerFactory.create('DtSelect');
/** The height of the select items. */
-export const SELECT_ITEM_HEIGHT = 32;
+export const SELECT_ITEM_HEIGHT = 28;
/** The max height of the select's overlay panel */
export const SELECT_PANEL_MAX_HEIGHT = 256;
@@ -137,9 +138,12 @@ export const _DtSelectMixinBase = mixinTabIndex(
mixinDisabled(mixinErrorState(DtSelectBase)),
);
-export function getDtSelectNonFunctionValueError(): Error {
- return Error('`compareWith` must be a function.');
-}
+/**
+ * @deprecated Will be removed with v8.0.0 - will not throw an Error after 8.0.0 but instead use the Logger
+ * see combobox compareWith
+ */
+export const getDtSelectNonFunctionValueError = () =>
+ new Error(DT_COMPARE_WITH_NON_FUNCTION_VALUE_ERROR_MSG);
@Component({
selector: 'dt-select',
@@ -882,8 +886,7 @@ export class DtSelect extends _DtSelectMixinBase
: this._getOptionIndex(this._selectionModel.selected[0])!;
selectedOptionOffset += _countGroupLabelsBeforeOption(
selectedOptionOffset,
- this.options,
- this.optionGroups,
+ this.options.toArray(),
);
// We must maintain a scroll buffer so the selected option will be scrolled to the
@@ -920,8 +923,7 @@ export class DtSelect extends _DtSelectMixinBase
const activeOptionIndex = this._keyManager.activeItemIndex || 0;
const labelCount = _countGroupLabelsBeforeOption(
activeOptionIndex,
- this.options,
- this.optionGroups,
+ this.options.toArray(),
);
this.panel.nativeElement.scrollTop = _getOptionScrollPosition(
diff --git a/libs/examples/src/combobox/combobox-examples.module.ts b/libs/examples/src/combobox/combobox-examples.module.ts
new file mode 100644
index 0000000000..71f9f6c30a
--- /dev/null
+++ b/libs/examples/src/combobox/combobox-examples.module.ts
@@ -0,0 +1,29 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { NgModule } from '@angular/core';
+import { DtComboboxModule } from '@dynatrace/barista-components/experimental/combobox';
+import { DtExampleComboboxSimple } from './combobox-simple-example/combobox-simple-example';
+import { DtOptionModule } from '@dynatrace/barista-components/core';
+import { CommonModule } from '@angular/common';
+
+export const DT_COMBOBOX_EXAMPLES = [DtExampleComboboxSimple];
+
+@NgModule({
+ imports: [DtComboboxModule, DtOptionModule, CommonModule],
+ declarations: [...DT_COMBOBOX_EXAMPLES],
+ entryComponents: [...DT_COMBOBOX_EXAMPLES],
+})
+export class DtComboboxExamplesModule {}
diff --git a/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.html b/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.html
new file mode 100644
index 0000000000..173752ed4f
--- /dev/null
+++ b/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.html
@@ -0,0 +1,15 @@
+
+
+ {{ option.name }}
+
+
diff --git a/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.ts b/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.ts
new file mode 100644
index 0000000000..ee2b49f4a8
--- /dev/null
+++ b/libs/examples/src/combobox/combobox-simple-example/combobox-simple-example.ts
@@ -0,0 +1,65 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ChangeDetectorRef, Component } from '@angular/core';
+import { take } from 'rxjs/operators';
+import { timer } from 'rxjs';
+
+const allOptions: { name: string; value: string }[] = [
+ { name: 'Value 1', value: '[value: Value 1]' },
+ { name: 'Value 2', value: '[value: Value 2]' },
+ { name: 'Value 3', value: '[value: Value 3]' },
+ { name: 'Value 4', value: '[value: Value 4]' },
+];
+
+@Component({
+ selector: 'dt-example-simple-combobox',
+ templateUrl: './combobox-simple-example.html',
+})
+export class DtExampleComboboxSimple {
+ _initialValue = allOptions[0];
+ _options = [...allOptions];
+ _loading = false;
+ _displayWith = (option: { name: string; value: string }) => option.name;
+
+ constructor(private _changeDetectorRef: ChangeDetectorRef) {}
+
+ openedChanged(event: boolean): void {
+ console.log(`openedChanged: '${event}'`);
+ }
+
+ valueChanged(event: string): void {
+ console.log(`valueChanged: '${event}'`);
+ }
+
+ filterChanged(event: string): void {
+ console.log(`filterChanged: '${event}'`);
+
+ this._loading = true;
+ this._changeDetectorRef.markForCheck();
+
+ timer(1500)
+ .pipe(take(1))
+ .subscribe(() => {
+ this._options = allOptions.filter(
+ (option) =>
+ option.value.toLowerCase().indexOf(event.toLowerCase()) >= 0,
+ );
+ this._loading = false;
+ this._changeDetectorRef.markForCheck();
+ });
+ }
+}
diff --git a/libs/examples/src/combobox/index.ts b/libs/examples/src/combobox/index.ts
new file mode 100644
index 0000000000..ee7cdf7c8e
--- /dev/null
+++ b/libs/examples/src/combobox/index.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 Dynatrace LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './combobox-simple-example/combobox-simple-example';
+export * from './combobox-examples.module';
diff --git a/libs/examples/src/examples.module.ts b/libs/examples/src/examples.module.ts
index 8109555aa7..96585bff5f 100644
--- a/libs/examples/src/examples.module.ts
+++ b/libs/examples/src/examples.module.ts
@@ -30,6 +30,7 @@ import { DtButtonGroupExamplesModule } from './button-group/button-group-example
import { DtCardExamplesModule } from './card/card-examples.module';
import { DtChartExamplesModule } from './chart/chart-examples.module';
import { DtCheckboxExamplesModule } from './checkbox/checkbox-examples.module';
+import { DtComboboxExamplesModule } from './combobox/combobox-examples.module';
import { DtConfirmationDialogExamplesModule } from './confirmation-dialog/confirmation-dialog-examples.module';
import { DtConsumptionExamplesModule } from './consumption/consumption-examples.module';
import { DtContainerBreakpointObserverExamplesModule } from './container-breakpoint-observer/container-breakpoint-observer-examples.module';
@@ -96,6 +97,7 @@ import { DtExamplesTreeTableModule } from './tree-table/tree-table-examples.modu
DtCardExamplesModule,
DtChartExamplesModule,
DtCheckboxExamplesModule,
+ DtComboboxExamplesModule,
DtConfirmationDialogExamplesModule,
DtConsumptionExamplesModule,
DtContainerBreakpointObserverExamplesModule,
diff --git a/libs/examples/src/index.ts b/libs/examples/src/index.ts
index 2342e31e6c..3e0decc7bc 100644
--- a/libs/examples/src/index.ts
+++ b/libs/examples/src/index.ts
@@ -84,6 +84,7 @@ import { DtExampleCheckboxDark } from './checkbox/checkbox-dark-example/checkbox
import { DtExampleCheckboxDefault } from './checkbox/checkbox-default-example/checkbox-default-example';
import { DtExampleCheckboxIndeterminate } from './checkbox/checkbox-indeterminate-example/checkbox-indeterminate-example';
import { DtExampleCheckboxResponsive } from './checkbox/checkbox-responsive-example/checkbox-responsive-example';
+import { DtExampleComboboxSimple } from './combobox/combobox-simple-example/combobox-simple-example';
import { DtExampleConfirmationDialogDefault } from './confirmation-dialog/confirmation-dialog-default-example/confirmation-dialog-default-example';
import { DtExampleConfirmationDialogShowBackdrop } from './confirmation-dialog/confirmation-dialog-show-backdrop-example/confirmation-dialog-show-backdrop-example';
import { DtExampleConsumptionDefault } from './consumption/consumption-default-example/consumption-default-example';
@@ -331,6 +332,7 @@ export { DtButtonGroupExamplesModule } from './button-group/button-group-example
export { DtCardExamplesModule } from './card/card-examples.module';
export { DtChartExamplesModule } from './chart/chart-examples.module';
export { DtCheckboxExamplesModule } from './checkbox/checkbox-examples.module';
+export { DtComboboxExamplesModule } from './combobox/combobox-examples.module';
export { DtConfirmationDialogExamplesModule } from './confirmation-dialog/confirmation-dialog-examples.module';
export { DtConsumptionExamplesModule } from './consumption/consumption-examples.module';
export { DtContainerBreakpointObserverExamplesModule } from './container-breakpoint-observer/container-breakpoint-observer-examples.module';
@@ -445,6 +447,7 @@ export {
DtExampleCheckboxDefault,
DtExampleCheckboxIndeterminate,
DtExampleCheckboxResponsive,
+ DtExampleComboboxSimple,
DtExampleConfirmationDialogDefault,
DtExampleConfirmationDialogShowBackdrop,
DtExampleConsumptionDefault,
@@ -750,6 +753,7 @@ export const EXAMPLES_MAP = new Map>([
['DtExampleCheckboxDefault', DtExampleCheckboxDefault],
['DtExampleCheckboxIndeterminate', DtExampleCheckboxIndeterminate],
['DtExampleCheckboxResponsive', DtExampleCheckboxResponsive],
+ ['DtExampleComboboxSimple', DtExampleComboboxSimple],
['DtExampleConfirmationDialogDefault', DtExampleConfirmationDialogDefault],
[
'DtExampleConfirmationDialogShowBackdrop',
diff --git a/nx.json b/nx.json
index d8d2f4f9f9..339704ce71 100644
--- a/nx.json
+++ b/nx.json
@@ -75,6 +75,7 @@
"card",
"chart",
"checkbox",
+ "combobox",
"confirmation-dialog",
"consumption",
"container-breakpoint-observer",
@@ -158,6 +159,9 @@
"checkbox": {
"tags": ["scope:components", "type:library"]
},
+ "combobox": {
+ "tags": ["scope:components", "type:library"]
+ },
"confirmation-dialog": {
"tags": ["scope:components", "type:library"]
},
diff --git a/tsconfig.json b/tsconfig.json
index ba5463e847..8ffdccdf49 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -47,6 +47,9 @@
"@dynatrace/barista-components/checkbox": [
"libs/barista-components/checkbox/index.ts"
],
+ "@dynatrace/barista-components/experimental/combobox": [
+ "libs/barista-components/experimental/combobox/index.ts"
+ ],
"@dynatrace/barista-components/confirmation-dialog": [
"libs/barista-components/confirmation-dialog/index.ts"
],