diff --git a/docs/content/api-docs/index.md b/docs/content/api-docs/index.md
index 49560b9b9..20eec0158 100644
--- a/docs/content/api-docs/index.md
+++ b/docs/content/api-docs/index.md
@@ -6,4 +6,7 @@ title = "API Docs for Angular Google Maps"
**Here you find the API docs for the @agm Packages:**
-* [@agm/core - Provides components and services for the official Google Maps API v3](./agm-core/modules/AgmCoreModule.html)
\ No newline at end of file
+* [@agm/core](./agm-core/modules/AgmCoreModule.html)
+ Provides Angular integration solutions for the official Google Maps Core API v3
+* [@agm/snazzy-info-window](./agm-snazzy-info-window/modules/AgmSnazzyInfoWindowModule.html)
+ Styled Info Windows with [Snazzy Info Window](https://github.com/atmist/snazzy-info-window#html-structure)
\ No newline at end of file
diff --git a/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md b/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md
new file mode 100644
index 000000000..0b82e41fa
--- /dev/null
+++ b/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md
@@ -0,0 +1,106 @@
++++
+date = "2017-06-20T20:11:15+02:00"
+draft = false
+title = "Styled Info Windows with Snazzy Info Window & Angular Google Maps"
++++
+
+Angular Google Maps provides a package that allows you to use [Snazzy Info Window](https://github.com/atmist/snazzy-info-window) together with @agm/core. 'Snazzy Info Window' allows you to create custom info window that are styleable via CSS or Angular inputs.
+
+Please note: The @agm/snazzy-info-window package currently supports Angular 4.x only.
+
+## Install the needed packages
+First make sure that you install the following NPM packages:
+
+```bash
+npm install @agm/core @agm/snazzy-info-window snazzy-info-window@^1.1.0
+```
+
+Make sure you have a Google Maps API Key - [you can get one here](https://developers.google.com/maps/documentation/javascript/get-api-key?hl=de).
+
+## Loading the modules
+
+Update your root component (e.g. src/app/app.module.ts) and import the following modules:
+
+```typescript
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { AppComponent } from './app.component';
+
+// add these imports
+import { AgmCoreModule } from '@agm/core';
+import { AgmSnazzyInfoWindowModule } from '@agm/snazzy-info-window';
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule,
+ AgmCoreModule.forRoot({
+ apiKey: ['YOUR_API_KEY_HERE']
+ }),
+ AgmSnazzyInfoWindowModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
+```
+
+## Load the CSS file
+
+There are some basic style that get shipped with the `snazzy-info-window` NPM package. The CSS file is located here:
+
+```
+node_modules/snazzy-info-window/dist/snazzy-info-window.css
+```
+
+If you are using Angular CLI, you can simply add this file to your `.angular-cli.json` file like this:
+
+```json
+"styles": [
+ "styles.css",
+ "../node_modules/snazzy-info-window/dist/snazzy-info-window.css"
+]
+```
+
+## Using the directive with a marker (AgmMarker)
+
+When you import the `AgmSnazzyInfoWindowModule`, you can use the `agmSnazzyInfoWindow` directive in your template.
+
+
+```html
+
+
+
+
+ My first Snazzy Info Window!
+
+
+
+
+```
+
+This creates a basic stylable info window that opens when the user clicks on the marker and closes when another snazzy info window opens.
+
+## Using the directive as a standalone info winodw
+
+
+```html
+
+
+
+ My first Snazzy Info Window!
+
+
+
+```
+
+## Styling
+There a two ways to style the snazzy info window:
+1. Via CSS - [simply use these CSS classes shown in this HTML](https://github.com/atmist/snazzy-info-window#html-structure)
+1. Via Angular inputs
+
+### Styling via Angular Inputs
+
+There a sevarel inputs that you can use for styling. [Check out the docs of the `agmSnazzyInfoWindow` directive here](/api-docs/agm-snazzy-info-window/directives/AgmSnazzyInfoWindow.html).
\ No newline at end of file
diff --git a/karma-test-shim.js b/karma-test-shim.js
index 169dc1abb..7ad118e40 100644
--- a/karma-test-shim.js
+++ b/karma-test-shim.js
@@ -1,7 +1,7 @@
// Turn on full stack traces in errors to help debugging
Error.stackTraceLimit=Infinity;
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
require('core-js/client/shim');
require('reflect-metadata');
diff --git a/karma.conf.js b/karma.conf.js
index ada2e07c3..f5d5aa601 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -91,5 +91,4 @@ module.exports = function (config) {
}
config.set(_config);
-
};
\ No newline at end of file
diff --git a/packages/snazzy-info-window/directives/snazzy-info-window.ts b/packages/snazzy-info-window/directives/snazzy-info-window.ts
new file mode 100644
index 000000000..db0e2ba72
--- /dev/null
+++ b/packages/snazzy-info-window/directives/snazzy-info-window.ts
@@ -0,0 +1,283 @@
+import { Host, SkipSelf, AfterViewInit, EventEmitter, Input, SimpleChanges, ViewContainerRef, TemplateRef, Output, Optional, OnDestroy, ElementRef, Component, ViewChild, ContentChild } from '@angular/core';
+import { AgmMarker, GoogleMapsAPIWrapper, MarkerManager, MapsAPILoader } from '@agm/core';
+
+declare var System: any;
+
+@Component({
+ // tslint:disable-next-line:component-selector
+ selector: 'agm-snazzy-info-window',
+ template: '
'
+})
+export class AgmSnazzyInfoWindow implements AfterViewInit, OnDestroy {
+ /**
+ * The latitude and longitude where the info window is anchored.
+ * The offset will default to 0px when using this option. Only required/used if you are not using a agm-marker.
+ */
+ @Input() latitude: number;
+
+ /**
+ * The longitude where the info window is anchored.
+ * The offset will default to 0px when using this option. Only required/used if you are not using a agm-marker.
+ */
+ @Input() longitude: number;
+
+ /**
+ * Changes the open status of the snazzy info window.
+ */
+ @Input() isOpen: boolean = false;
+
+ /**
+ * Emits when the open status changes.
+ */
+ @Output() isOpenChange: EventEmitter = new EventEmitter();
+
+ /**
+ * Choose where you want the info window to be displayed, relative to the marker.
+ */
+ @Input() placement: 'top'|'bottom'|'left'|'right' = 'top';
+
+ /**
+ * The max width in pixels of the info window.
+ */
+ @Input() maxWidth: number|string = 200;
+
+ /**
+ * The max height in pixels of the info window.
+ */
+ @Input() maxHeight: number|string = 200;
+
+ /**
+ * The color to use for the background of the info window.
+ */
+ @Input() backgroundColor: string;
+
+ /**
+ * A custom padding size around the content of the info window.
+ */
+ @Input() padding: string;
+
+ /**
+ * A custom border around the info window. Set to false to completely remove the border.
+ * The units used for border should be the same as pointer.
+ */
+ @Input() border: {width: string; color: string}|boolean;
+
+ /**
+ * A custom CSS border radius property to specify the rounded corners of the info window.
+ */
+ @Input() borderRadius: string;
+
+ /**
+ * The font color to use for the content inside the body of the info window.
+ */
+ @Input() fontColor: string;
+
+ /**
+ * The font size to use for the content inside the body of the info window.
+ */
+ @Input() fontSize: string;
+
+ /**
+ * The height of the pointer from the info window to the marker.
+ * Set to false to completely remove the pointer.
+ * The units used for pointer should be the same as border.
+ */
+ @Input() pointer: string|boolean;
+
+ /**
+ * The CSS properties for the shadow of the info window.
+ * Set to false to completely remove the shadow.
+ */
+ @Input() shadow: boolean|{h?: string, v?: string, blur: string, spread: string, opacity: number, color: string};
+
+ /**
+ * Determines if the info window will open when the marker is clicked.
+ * An internal listener is added to the Google Maps click event which calls the open() method.
+ */
+ @Input() openOnMarkerClick: boolean = true;
+
+ /**
+ * Determines if the info window will close when the map is clicked. An internal listener is added to the Google Maps click event which calls the close() method.
+ * This will not activate on the Google Maps drag event when the user is panning the map.
+ */
+ @Input() closeOnMapClick: boolean = true;
+
+ /**
+ * An optional CSS class to assign to the wrapper container of the info window.
+ * Can be used for applying custom CSS to the info window.
+ */
+ @Input() wrapperClass: string;
+
+ /**
+ * Determines if the info window will close when any other Snazzy Info Window is opened.
+ */
+ @Input() closeWhenOthersOpen: boolean = false;
+
+ /**
+ * Determines if the info window will show a close button.
+ */
+ @Input() showCloseButton: boolean = true;
+
+ /**
+ * Determines if the info window will be panned into view when opened.
+ */
+ @Input() panOnOpen: boolean = true;
+
+ /**
+ * Emits before the info window opens.
+ */
+ @Output() beforeOpen: EventEmitter = new EventEmitter();
+
+ /**
+ * Emits before the info window closes.
+ */
+ @Output() afterClose: EventEmitter = new EventEmitter();
+
+ /**
+ * @internal
+ */
+ @ViewChild('outerWrapper', {read: ElementRef}) _outerWrapper: ElementRef;
+
+ /**
+ * @internal
+ */
+ @ViewChild('viewContainer', {read: ViewContainerRef}) _viewContainerRef: ViewContainerRef;
+
+ /**
+ * @internal
+ */
+ @ContentChild(TemplateRef) _templateRef: TemplateRef;
+
+ protected _nativeSnazzyInfoWindow: any;
+ protected _snazzyInfoWindowInitialized: Promise|null = null;
+
+ constructor(
+ @Optional() @Host() @SkipSelf() private _marker: AgmMarker,
+ private _wrapper: GoogleMapsAPIWrapper,
+ private _manager: MarkerManager,
+ private _loader: MapsAPILoader
+ ) {}
+
+ /**
+ * @internal
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (this._nativeSnazzyInfoWindow == null) {
+ return;
+ }
+ if ('isOpen' in changes && this.isOpen) {
+ this._openInfoWindow();
+ } else if ('isOpen' in changes && !this.isOpen) {
+ this._closeInfoWindow();
+ }
+ if (('latitude' in changes || 'longitude' in changes) && this._marker == null) {
+ this._updatePosition();
+ }
+ }
+
+ /**
+ * @internal
+ */
+ ngAfterViewInit() {
+ const m = this._manager != null ? this._manager.getNativeMarker(this._marker) : null;
+ this._snazzyInfoWindowInitialized = this._loader.load()
+ .then(() => System.import('snazzy-info-window'))
+ .then((module: any) => Promise.all([module, m, this._wrapper.getNativeMap()]))
+ .then((elems) => {
+ const options: any = {
+ map: elems[2],
+ content: '',
+ placement: this.placement,
+ maxWidth: this.maxWidth,
+ maxHeight: this.maxHeight,
+ backgroundColor: this.backgroundColor,
+ padding: this.padding,
+ border: this.border,
+ borderRadius: this.borderRadius,
+ fontColor: this.fontColor,
+ pointer: this.pointer,
+ shadow: this.shadow,
+ openOnMarkerClick: this.openOnMarkerClick,
+ closeWhenOthersOpen: this.closeWhenOthersOpen,
+ showCloseButton: this.showCloseButton,
+ panOnOpen: this.panOnOpen,
+ wrapperClass: this.wrapperClass,
+ callbacks: {
+ beforeOpen: () => {
+ this._createViewContent();
+ this.beforeOpen.emit();
+ },
+ afterOpen: () => {
+ this.isOpenChange.emit(this.openStatus());
+ },
+ afterClose: () => {
+ this.afterClose.emit();
+ this.isOpenChange.emit(this.openStatus());
+ }
+ }
+ };
+ if (elems[1] != null) {
+ options.marker = elems[1];
+ } else {
+ options.position = {
+ lat: this.latitude,
+ lng: this.longitude
+ };
+ }
+ this._nativeSnazzyInfoWindow = new elems[0](options);
+ });
+ this._snazzyInfoWindowInitialized.then(() => {
+ if (this.isOpen) {
+ this._openInfoWindow();
+ }
+ });
+ }
+
+ protected _openInfoWindow() {
+ this._snazzyInfoWindowInitialized.then(() => {
+ this._createViewContent();
+ this._nativeSnazzyInfoWindow.open();
+ });
+ }
+
+ protected _closeInfoWindow() {
+ this._snazzyInfoWindowInitialized.then(() => {
+ this._nativeSnazzyInfoWindow.close();
+ });
+ }
+
+ protected _createViewContent() {
+ if (this._viewContainerRef.length === 1) {
+ return;
+ }
+ const evr = this._viewContainerRef.createEmbeddedView(this._templateRef);
+ this._nativeSnazzyInfoWindow.setContent(this._outerWrapper.nativeElement);
+ // we have to run this in a separate cycle.
+ setTimeout(() => {
+ evr.detectChanges();
+ });
+ }
+
+ protected _updatePosition() {
+ this._nativeSnazzyInfoWindow.setPosition({
+ lat: this.latitude,
+ lng: this.longitude
+ });
+ }
+
+ /**
+ * Returns true when the Snazzy Info Window is initialized and open.
+ */
+ openStatus(): boolean {
+ return this._nativeSnazzyInfoWindow && this._nativeSnazzyInfoWindow.isOpen();
+ }
+
+ /**
+ * @internal
+ */
+ ngOnDestroy() {
+ if (this._nativeSnazzyInfoWindow) {
+ this._nativeSnazzyInfoWindow.destroy();
+ }
+ }
+}
diff --git a/packages/snazzy-info-window/index.ts b/packages/snazzy-info-window/index.ts
new file mode 100644
index 000000000..b85997a88
--- /dev/null
+++ b/packages/snazzy-info-window/index.ts
@@ -0,0 +1,3 @@
+// public API
+export { AgmSnazzyInfoWindowModule } from './snazzy-info-window.module';
+export { AgmSnazzyInfoWindow } from './directives/snazzy-info-window';
diff --git a/packages/snazzy-info-window/package.tpl.json b/packages/snazzy-info-window/package.tpl.json
new file mode 100644
index 000000000..823ad34dc
--- /dev/null
+++ b/packages/snazzy-info-window/package.tpl.json
@@ -0,0 +1,28 @@
+{
+ "name": "@agm/snazzy-info-window",
+ "version": "1.0.0-beta.1",
+ "description": "Angular Google Maps (AGM) package for Snazzy Info Window support",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/SebastianM/angular-google-maps.git"
+ },
+ "keywords": [
+ "angular-google-maps",
+ "angular",
+ "snazzy-info-window",
+ "info-window",
+ "google-maps",
+ "agm"
+ ],
+ "author": "Sebastian Müller",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/SebastianM/angular-google-maps/issues"
+ },
+ "peerDependencies": {
+ "@angular/core": "^4.0.0",
+ "@agm/core": "^1.0.0-beta.0",
+ "snazzy-info-window": "^1.1.0"
+ },
+ "homepage": "https://github.com/SebastianM/angular-google-maps#readme"
+}
diff --git a/packages/snazzy-info-window/snazzy-info-window.module.ts b/packages/snazzy-info-window/snazzy-info-window.module.ts
new file mode 100644
index 000000000..94b605f3e
--- /dev/null
+++ b/packages/snazzy-info-window/snazzy-info-window.module.ts
@@ -0,0 +1,8 @@
+import { NgModule } from '@angular/core';
+import { AgmSnazzyInfoWindow } from './directives/snazzy-info-window';
+
+@NgModule({
+ declarations: [AgmSnazzyInfoWindow],
+ exports: [AgmSnazzyInfoWindow]
+})
+export class AgmSnazzyInfoWindowModule {}
diff --git a/rollup.snazzy-info-window.config.js b/rollup.snazzy-info-window.config.js
new file mode 100644
index 000000000..1e3076854
--- /dev/null
+++ b/rollup.snazzy-info-window.config.js
@@ -0,0 +1,22 @@
+export default {
+ entry: 'dist/snazzy-info-window/index.js',
+ dest: 'dist/snazzy-info-window/snazzy-info-window.umd.js',
+ format: 'umd',
+ moduleName: 'ngmaps.snazzyInfoWindow',
+ sourceMap: true,
+ globals: {
+ '@angular/core': 'ng.core',
+ '@angular/common': 'ng.common',
+ '@angular/compiler': 'ng.compiler',
+ '@angular/platform-browser': 'ng.platformBrowser',
+ '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',
+ 'rxjs/Subject': 'Rx',
+ 'rxjs/observable/PromiseObservable': 'Rx',
+ 'rxjs/operator/toPromise': 'Rx.Observable.prototype',
+ 'rxjs/Observable': 'Rx',
+ 'rxjs/Rx': 'Rx',
+ '@agm/core': 'ngmaps.core'
+ },
+ context: 'window',
+ external: ['rxjs', '@angular/core', 'rxjs/Observable']
+}
diff --git a/scripts/create-package-json.js b/scripts/create-package-json.js
index ccb1a9aed..9c12e4a86 100644
--- a/scripts/create-package-json.js
+++ b/scripts/create-package-json.js
@@ -4,7 +4,7 @@
const fs = require('fs');
const path = require('path');
-const pkgNames = ['core'];
+const pkgNames = ['core', 'snazzy-info-window'];
pkgNames.forEach(function(pkgName) {
let basePkgJson;
diff --git a/tslint.json b/tslint.json
index 21da335dc..64baa6c09 100644
--- a/tslint.json
+++ b/tslint.json
@@ -77,7 +77,6 @@
"check-module",
"check-type"
],
- "directive-selector": [true, "element", "agm", "kebab-case"],
"component-selector": [true, "element", "agm", "kebab-case"],
"use-input-property-decorator": false,
"use-output-property-decorator": false,