Skip to content

Commit 8ce5e86

Browse files
authored
feat(typeahead): add animation (#5240)
1 parent f4aebbe commit 8ce5e86

File tree

14 files changed

+257
-65
lines changed

14 files changed

+257
-65
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<pre class="card card-block card-header mb-3">Model: {{selected | json}}</pre>
2+
<input [(ngModel)]="selected"
3+
[typeahead]="states"
4+
[isAnimated]="true"
5+
class="form-control">
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'demo-typeahead-animated',
5+
templateUrl: './animated.html'
6+
})
7+
export class DemoTypeaheadAnimatedComponent {
8+
selected: string;
9+
states: string[] = [
10+
'Alabama',
11+
'Alaska',
12+
'Arizona',
13+
'Arkansas',
14+
'California',
15+
'Colorado',
16+
'Connecticut',
17+
'Delaware',
18+
'Florida',
19+
'Georgia',
20+
'Hawaii',
21+
'Idaho',
22+
'Illinois',
23+
'Indiana',
24+
'Iowa',
25+
'Kansas',
26+
'Kentucky',
27+
'Louisiana',
28+
'Maine',
29+
'Maryland',
30+
'Massachusetts',
31+
'Michigan',
32+
'Minnesota',
33+
'Mississippi',
34+
'Missouri',
35+
'Montana',
36+
'Nebraska',
37+
'Nevada',
38+
'New Hampshire',
39+
'New Jersey',
40+
'New Mexico',
41+
'New York',
42+
'North Dakota',
43+
'North Carolina',
44+
'Ohio',
45+
'Oklahoma',
46+
'Oregon',
47+
'Pennsylvania',
48+
'Rhode Island',
49+
'South Carolina',
50+
'South Dakota',
51+
'Tennessee',
52+
'Texas',
53+
'Utah',
54+
'Vermont',
55+
'Virginia',
56+
'Washington',
57+
'West Virginia',
58+
'Wisconsin',
59+
'Wyoming'
60+
];
61+
}

demo/src/app/components/+typeahead/demos/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DemoTypeaheadAdaptivePositionComponent } from './adaptive-position/adap
22
import { DemoTypeaheadAsyncComponent } from './async/async';
33
import { DemoTypeaheadBasicComponent } from './basic/basic';
44
import { DemoTypeaheadConfigComponent } from './config/config';
5+
import { DemoTypeaheadAnimatedComponent } from './animated/animated';
56
import { DemoTypeaheadContainerComponent } from './container/container';
67
import { DemoTypeaheadDelayComponent } from './delay/delay';
78
import { DemoTypeaheadDropupComponent } from './dropup/dropup';
@@ -24,6 +25,7 @@ import { DemoTypeaheadSingleWorldComponent } from './single-world/single-world';
2425

2526
export const DEMO_COMPONENTS = [
2627
DemoTypeaheadAdaptivePositionComponent,
28+
DemoTypeaheadAnimatedComponent,
2729
DemoTypeaheadAsyncComponent,
2830
DemoTypeaheadBasicComponent,
2931
DemoTypeaheadConfigComponent,
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
2+
13
// RECOMMENDED
24
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
35
// or
46
import { TypeaheadModule } from 'ngx-bootstrap';
57

68
@NgModule({
7-
imports: [TypeaheadModule.forRoot(),...]
9+
imports: [
10+
BrowserAnimationsModule,
11+
TypeaheadModule.forRoot(),
12+
...
13+
]
814
})
915
export class AppModule(){}

demo/src/app/components/+typeahead/typeahead-section.list.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ApiSectionsComponent } from '../../docs/demo-section-components/demo-ap
22
import { ContentSection } from '../../docs/models/content-section.model';
33
import { DemoTopSectionComponent } from '../../docs/demo-section-components/demo-top-section';
44
import { DemoTypeaheadAdaptivePositionComponent } from './demos/adaptive-position/adaptive-position';
5+
import { DemoTypeaheadAnimatedComponent } from './demos/animated/animated';
56
import { DemoTypeaheadAsyncComponent } from './demos/async/async';
67
import { DemoTypeaheadBasicComponent } from './demos/basic/basic';
78
import { DemoTypeaheadConfigComponent } from './demos/config/config';
@@ -49,6 +50,14 @@ export const demoComponentContent: ContentSection[] = [
4950
html: require('!!raw-loader!./demos/basic/basic.html'),
5051
outlet: DemoTypeaheadBasicComponent
5152
},
53+
{
54+
title: 'With animation',
55+
anchor: 'animated',
56+
component: require('!!raw-loader!./demos/animated/animated'),
57+
html: require('!!raw-loader!./demos/animated/animated.html'),
58+
description: `You can enable animation via <code>isAnimated</code> input or config option`,
59+
outlet: DemoTypeaheadAnimatedComponent
60+
},
5261
{
5362
title: 'Adaptive position',
5463
anchor: 'adaptive-position',

schematics/src/ng-add/index.ts

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,27 @@ import { hasNgModuleImport } from '../utils/ng-module-imports';
2727
const bootstrapStylePath = `./node_modules/bootstrap/dist/css/bootstrap.min.css`;
2828
const datePickerStylePath = `./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css`;
2929
const datepickerComponentName = 'datepicker';
30-
const accordionComponentName = 'accordion';
31-
const collapseComponentName = 'collapse';
30+
const bsName = 'ngx-bootstrap';
31+
32+
const components: { [key: string]: { moduleName: string; link: string; animated?: boolean } } = {
33+
accordion: { moduleName: 'AccordionModule', link: `${bsName}/accordion`, animated: true },
34+
alerts: { moduleName: 'AlertModule', link: `${bsName}/alert` },
35+
buttons: { moduleName: 'ButtonsModule', link: `${bsName}/buttons` },
36+
carousel: { moduleName: 'CarouselModule', link: `${bsName}/carousel` },
37+
collapse: { moduleName: 'CollapseModule', link: `${bsName}/collapse`, animated: true },
38+
datepicker: { moduleName: 'BsDatepickerModule', link: `${bsName}/datepicker`, animated: true },
39+
dropdowns: { moduleName: 'BsDropdownModule', link: `${bsName}/dropdown` },
40+
modals: { moduleName: 'ModalModule', link: `${bsName}/modal` },
41+
pagination: { moduleName: 'PaginationModule', link: `${bsName}/pagination` },
42+
popover: { moduleName: 'PopoverModule', link: `${bsName}/popover` },
43+
progressbar: { moduleName: 'ProgressbarModule', link: `${bsName}/progressbar` },
44+
rating: { moduleName: 'RatingModule', link: `${bsName}/rating` },
45+
sortable: { moduleName: 'SortableModule', link: `${bsName}/sortable` },
46+
tabs: { moduleName: 'TabsModule', link: `${bsName}/tabs` },
47+
timepicker: { moduleName: 'TimepickerModule', link: `${bsName}/timepicker` },
48+
tooltip: { moduleName: 'TooltipModule', link: `${bsName}/tooltip` },
49+
typeahead: { moduleName: 'TypeaheadModule', link: `${bsName}/typeahead`, animated: true }
50+
};
3251

3352
/* tslint:disable-next-line: no-default-export */
3453
export default function (options: Schema): Rule {
@@ -50,27 +69,6 @@ export default function (options: Schema): Rule {
5069
}
5170

5271
function addModuleOfComponent(projectName: string | undefined, componentName: string) {
53-
const bsName = 'ngx-bootstrap';
54-
55-
const components: { [key: string]: { moduleName: string; link: string } } = {
56-
accordion: { moduleName: 'AccordionModule', link: `${bsName}/accordion` },
57-
alerts: { moduleName: 'AlertModule', link: `${bsName}/alert` },
58-
buttons: { moduleName: 'ButtonsModule', link: `${bsName}/buttons` },
59-
carousel: { moduleName: 'CarouselModule', link: `${bsName}/carousel` },
60-
collapse: { moduleName: 'CollapseModule', link: `${bsName}/collapse` },
61-
datepicker: { moduleName: 'BsDatepickerModule', link: `${bsName}/datepicker` },
62-
dropdowns: { moduleName: 'BsDropdownModule', link: `${bsName}/dropdown` },
63-
modals: { moduleName: 'ModalModule', link: `${bsName}/modal` },
64-
pagination: { moduleName: 'PaginationModule', link: `${bsName}/pagination` },
65-
popover: { moduleName: 'PopoverModule', link: `${bsName}/popover` },
66-
progressbar: { moduleName: 'ProgressbarModule', link: `${bsName}/progressbar` },
67-
rating: { moduleName: 'RatingModule', link: `${bsName}/rating` },
68-
sortable: { moduleName: 'SortableModule', link: `${bsName}/sortable` },
69-
tabs: { moduleName: 'TabsModule', link: `${bsName}/tabs` },
70-
timepicker: { moduleName: 'TimepickerModule', link: `${bsName}/timepicker` },
71-
tooltip: { moduleName: 'TooltipModule', link: `${bsName}/tooltip` },
72-
typeahead: { moduleName: 'TypeaheadModule', link: `${bsName}/typeahead` }
73-
};
7472

7573
return (host: Tree) => {
7674
const workspace = getWorkspace(host);
@@ -133,7 +131,7 @@ function insertCommonStyles(project: WorkspaceProject, host: Tree, workspace: Wo
133131

134132
function addAnimationModule(projectName: string | undefined, componentName: string) {
135133
return (host: Tree) => {
136-
if (!(!componentName || componentName === accordionComponentName || componentName === collapseComponentName)) {
134+
if (!(!componentName || components[componentName].animated)) {
137135
return host;
138136
}
139137

src/positioning/positioning.service.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { isPlatformBrowser } from '@angular/common';
33

44
import { positionElements } from './ng-positioning';
55

6-
import { fromEvent, merge, of, animationFrameScheduler, Subject } from 'rxjs';
6+
import { fromEvent, merge, of, animationFrameScheduler, Subject, Observable } from 'rxjs';
77
import { Options } from './models';
88

99

@@ -43,25 +43,33 @@ export interface PositioningOptions {
4343

4444
@Injectable()
4545
export class PositioningService {
46-
options: Options;
46+
private options: Options;
4747
private update$$ = new Subject<null>();
4848
private positionElements = new Map();
49+
private triggerEvent$: Observable<number|Event>;
50+
private isDisabled = false;
4951

5052
constructor(
5153
rendererFactory: RendererFactory2,
5254
@Inject(PLATFORM_ID) platformId: number
5355
) {
5456
if (isPlatformBrowser(platformId)) {
55-
merge(
57+
this.triggerEvent$ = merge(
5658
fromEvent(window, 'scroll'),
5759
fromEvent(window, 'resize'),
5860
/* tslint:disable-next-line: deprecation */
5961
of(0, animationFrameScheduler),
6062
this.update$$
61-
)
62-
.subscribe(() => {
63+
);
64+
65+
this.triggerEvent$.subscribe(() => {
66+
if (this.isDisabled) {
67+
return;
68+
}
69+
6370
this.positionElements
64-
.forEach((positionElement: PositioningOptions) => {
71+
/* tslint:disable-next-line: no-any */
72+
.forEach((positionElement: any) => {
6573
positionElements(
6674
_getHtmlElement(positionElement.target),
6775
_getHtmlElement(positionElement.element),
@@ -79,6 +87,18 @@ export class PositioningService {
7987
this.addPositionElement(options);
8088
}
8189

90+
get event$(): Observable<number|Event> {
91+
return this.triggerEvent$;
92+
}
93+
94+
disable(): void {
95+
this.isDisabled = true;
96+
}
97+
98+
enable(): void {
99+
this.isDisabled = false;
100+
}
101+
82102
addPositionElement(options: PositioningOptions): void {
83103
this.positionElements.set(_getHtmlElement(options.element), options);
84104
}

src/spec/typeahead-container.component.spec.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/* tslint:disable: max-file-line-count */
2-
import { TestBed, ComponentFixture, tick, fakeAsync } from '@angular/core/testing';
32
import { asNativeElements } from '@angular/core';
3+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
44
import { By } from '@angular/platform-browser';
5+
import { TestBed, ComponentFixture, tick, fakeAsync } from '@angular/core/testing';
6+
7+
import { PositioningService } from 'ngx-bootstrap/positioning';
8+
59
import {
610
TypeaheadConfig,
711
TypeaheadContainerComponent,
@@ -10,6 +14,7 @@ import {
1014
TypeaheadOptions
1115
} from '../typeahead';
1216

17+
1318
describe('Component: TypeaheadContainer', () => {
1419
let fixture: ComponentFixture<TypeaheadContainerComponent>;
1520
/* tslint:disable-next-line: no-any */
@@ -19,14 +24,18 @@ describe('Component: TypeaheadContainer', () => {
1924
beforeEach(fakeAsync(() => {
2025
testModule = TestBed.configureTestingModule({
2126
declarations: [TypeaheadContainerComponent],
22-
providers: [{
23-
provide: TypeaheadOptions,
24-
useValue: new TypeaheadOptions({ animation: false, placement: 'bottom-left', typeaheadRef: undefined })
25-
},
27+
imports: [BrowserAnimationsModule],
28+
providers: [
29+
{
30+
provide: TypeaheadOptions,
31+
useValue: new TypeaheadOptions({ animation: false, placement: 'bottom start', typeaheadRef: undefined })
32+
},
2633
{
2734
provide: TypeaheadConfig,
2835
useValue: new TypeaheadConfig()
29-
}]
36+
},
37+
PositioningService
38+
]
3039
});
3140
fixture = testModule.createComponent(TypeaheadContainerComponent);
3241

src/spec/typeahead.directive.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* tslint:disable:max-file-line-count */
2+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
23
import { By } from '@angular/platform-browser';
34
import { Component, DebugElement } from '@angular/core';
45
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
@@ -42,7 +43,7 @@ describe('Directive: Typeahead', () => {
4243
beforeEach(() => {
4344
fixture = TestBed.configureTestingModule({
4445
declarations: [ TestTypeaheadComponent],
45-
imports: [TypeaheadModule.forRoot(), FormsModule]
46+
imports: [TypeaheadModule.forRoot(), BrowserAnimationsModule, FormsModule]
4647
}).createComponent(TestTypeaheadComponent);
4748

4849
fixture.detectChanges();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
animate,
3+
style,
4+
AnimationTriggerMetadata,
5+
state,
6+
transition,
7+
trigger
8+
} from '@angular/animations';
9+
10+
export const TYPEAHEAD_ANIMATION_TIMING = '220ms cubic-bezier(0, 0, 0.2, 1)';
11+
12+
export const typeaheadAnimation: AnimationTriggerMetadata =
13+
trigger('typeaheadAnimation', [
14+
state('animated-down', style({ height: '*', overflow: 'hidden'})),
15+
transition('* => animated-down', [
16+
style({ height: 0, overflow: 'hidden' }),
17+
animate(TYPEAHEAD_ANIMATION_TIMING)
18+
]),
19+
state('animated-up', style({ height: '*', overflow: 'hidden'})),
20+
transition('* => animated-up', [
21+
style({ height: '*', overflow: 'hidden' }),
22+
animate(TYPEAHEAD_ANIMATION_TIMING)
23+
]),
24+
transition('* => unanimated', animate('0s'))
25+
]);

0 commit comments

Comments
 (0)