diff --git a/docs/documentation/API-Documentation.md b/docs/documentation/API-Documentation.md
index a1020f5a8..24c4519c4 100644
--- a/docs/documentation/API-Documentation.md
+++ b/docs/documentation/API-Documentation.md
@@ -17,7 +17,7 @@ import {BREAKPOINTS} from '@angular/flex-layout';
providers: [{provide: BREAKPOINTS, useValue: MY_CUSTOM_BREAKPOINTS }]
```
-* **[BaseFxDirectiveAdapter](https://github.com/angular/flex-layout/wiki/BaseFxDirectiveAdapter)**:
+* **[BaseDirectiveAdapter](https://github.com/angular/flex-layout/wiki/BaseDirectiveAdapter)**:
Adapter class useful to extend existing Directives with MediaQuery activation features.
```typescript
import {NgClass} from '@angular/core';
diff --git a/docs/documentation/Custom-Breakpoints.md b/docs/documentation/Custom-Breakpoints.md
index b64cf0775..8d1b19031 100644
--- a/docs/documentation/Custom-Breakpoints.md
+++ b/docs/documentation/Custom-Breakpoints.md
@@ -81,7 +81,7 @@ Consider, for example, the
```typescript
import {Directive} from '@angular/core';
-import {BaseFxDirective} from '@angular/flex-layout';
+import {BaseDirective} from '@angular/flex-layout';
@Directive({selector: `
[fxLayout],
@@ -95,7 +95,7 @@ import {BaseFxDirective} from '@angular/flex-layout';
[fxLayout.gt-lg],
[fxLayout.xl]
`})
-export class LayoutDirective extends BaseFxDirective {
+export class LayoutDirective extends BaseDirective {
...
}
```
diff --git a/guides/Grid.md b/guides/Grid.md
new file mode 100644
index 000000000..2dfcd1491
--- /dev/null
+++ b/guides/Grid.md
@@ -0,0 +1,64 @@
+# CSS Grid with Angular Layout
+
+### Introduction
+
+CSS Grid is a relatively new, powerful layout library available in all evergreen browsers. It provides
+an extra level of dimensionality for constructing web layouts compared to Flexbox. We have added 11 new
+directives with responsive functionality to the Angular Layout library to enable developers to easily add
+the new engine to their apps.
+
+### Usage
+
+The new suite of directives is extensive, and covers the majority of CSS Grid functionality. The
+following table shows the parity between directives and CSS properties:
+
+| Grid Directive | CSS Property(s) | Extra Inputs |
+| ---------------- |:-----------------------------------------:| --------------------------:|
+| `gdAlignColumns` | `align-content` and `align-items` | `gdInline` for inline-grid |
+| `gdAlignRows` | `justify-content` and `justify-items` | `gdInline` for inline-grid |
+| `gdArea` | `grid-area` | none |
+| `gdAreas` | `grid-areas` | `gdInline` for inline-grid |
+| `gdAuto` | `grid-auto-flow` | `gdInline` for inline-grid |
+| `gdColumn` | `grid-column` | none |
+| `gdColumns` | `grid-template-columns` | `gdInline` for inline-grid `!` at the end means `grid-auto-columns` |
+| `gdGap` | `grid-gap` | `gdInline` for inline-grid |
+| `gdGridAlign` | `justify-self` and `align-self` | none |
+| `gdRow` | `grid-row` | none |
+| `gdRows` | `grid-template-rows` | `gdInline` for inline-grid `!` at the end means `grid-auto-rows` |
+
+Note: unless otherwise specified, the above table represents exact parity. The inputs for the
+directives will be mapped verbatim to the CSS property without sanitization
+
+### Limitations
+
+While CSS Grid has excellent cross-browser and mobile support, it is currently unsupported in IE11
+due to an outdated spec implementation
+
+### Using with Flexbox
+
+The new CSS Grid directives can be used in concert with the existing Flexbox directives seamlessly.
+Simply import the top-level `FlexLayoutModule`, or both `FlexModule` and `GridModule` as follows:
+
+```typescript
+import {FlexLayoutModule} from '@angular/flex-layout';
+```
+
+```typescript
+import {FlexModule} from '@angular/flex-layout/flex';
+import {GridModule} from '@angular/flex-layout/grid';
+```
+
+This allows you to use, for example, Flexbox inside a CSS Grid as follows:
+
+```html
+
+```
+
+### References
+
+The design doc for this part of the library can be found
+[here](https://docs.google.com/document/d/1YjKHAV7-wh5UZd4snoyw6bVWe1X5JF-zDaUFa8-JDtE)
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 6df0da67a..e4f309c11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16618,9 +16618,9 @@
"dev": true
},
"rxjs": {
- "version": "6.0.0-beta.4",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.0.0-beta.4.tgz",
- "integrity": "sha512-H+ghJ4H2mPrugoMfgbeky0yhmOrk4y0ykRyZpYvU2za0VbE2WTKgY/9pF7HJCogPFNIyI4GqI9Ujk3Xr4XxHbQ==",
+ "version": "6.0.0-rc.0",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.0.0-rc.0.tgz",
+ "integrity": "sha512-nQqUjqiyiD3OkPd4xlg4eDNG4k8UiarBhU9qr3xKncHYhn3REjC4fCAFlg862JEwg50vQImaI/bv8yzreAHzng==",
"requires": {
"tslib": "1.9.0"
}
diff --git a/package.json b/package.json
index e399a329f..14fb85a04 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"@angular/core": "^6.0.0-rc.0",
"@angular/platform-browser": "^6.0.0-rc.0",
"core-js": "^2.4.1",
- "rxjs": "^6.0.0-beta.4",
+ "rxjs": "^6.0.0-rc.0",
"systemjs": "0.19.43",
"tsickle": "^0.27.0",
"tslib": "^1.8.0",
diff --git a/src/apps/demo-app/package.json b/src/apps/demo-app/package.json
index 9d6cd8142..f9abe6775 100644
--- a/src/apps/demo-app/package.json
+++ b/src/apps/demo-app/package.json
@@ -32,7 +32,7 @@
"devDependencies": {
"@angular/cli": "1.7.2",
"@angular/compiler-cli": "file:../../../node_modules/@angular/compiler-cli",
- "@angular/language-service": "^6.0.0-rc.0",
+ "@angular/language-service": "^6.0.0-rc.1",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
diff --git a/src/apps/demo-app/src/app/app.component.html b/src/apps/demo-app/src/app/app.component.html
index 13dbd6b41..f685d1235 100644
--- a/src/apps/demo-app/src/app/app.component.html
+++ b/src/apps/demo-app/src/app/app.component.html
@@ -9,12 +9,10 @@ Layout Demos:
- These Layout demos are curated from the Angular Material v1.x documentation, GitHub Issues,
- StackOverflow,
- and CodePen.
+ These Layout demos are curated from the AngularJS Material documentation, GitHub Issues, StackOverflow, and CodePen.
- Hint: Click on any of the samples below to toggle the layout direction(s).
-
+ Hint: Click on any of the samples below to toggle the layout direction(s).
+
Layout Demos:
Static
+
Grids
Responsive
Github
diff --git a/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.spec.ts b/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.spec.ts
new file mode 100644
index 000000000..a60142a88
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DocsLayoutComponent } from './docs-grid.component';
+
+describe('DocsLayoutComponent', () => {
+ let component: DocsLayoutComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DocsLayoutComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DocsLayoutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.ts b/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.ts
new file mode 100644
index 000000000..0ee5438df
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/docs-grid/docs-grid.component.ts
@@ -0,0 +1,13 @@
+import {Component} from '@angular/core';
+
+@Component({
+ selector: 'demo-docs-grid',
+ template: `
+
+
+
+
+
+ `
+})
+export class DocsGridComponent {}
diff --git a/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.spec.ts b/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.spec.ts
new file mode 100644
index 000000000..057ed9de1
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FlexOffsetValuesComponent } from './grid-layout.component';
+
+describe('FlexOffsetValuesComponent', () => {
+ let component: FlexOffsetValuesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ FlexOffsetValuesComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FlexOffsetValuesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.ts b/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.ts
new file mode 100644
index 000000000..d0544d234
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-layout/grid-layout.component.ts
@@ -0,0 +1,31 @@
+import {Component} from '@angular/core';
+
+// Example taken from https://gridbyexample.com/examples/example13/
+/* tslint:disable */
+@Component({
+ selector: 'demo-grid-layout',
+ template: `
+
+ Basic Responsive Grid App
+
+
+
+
Header
+
Sidebar
+
Sidebar 2
+
Content
+ More content than we had before so this column is now quite tall.
+
Footer
+
+
+
+
+ `
+})
+export class GridLayoutComponent {}
diff --git a/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.spec.ts b/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.spec.ts
new file mode 100644
index 000000000..d7a5b8886
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GridMinmaxComponent } from './grid-minmax.component';
+
+describe('GridMinmaxComponent', () => {
+ let component: GridMinmaxComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GridMinmaxComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GridMinmaxComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.ts b/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.ts
new file mode 100644
index 000000000..7eb37743b
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-minmax/grid-minmax.component.ts
@@ -0,0 +1,40 @@
+import { Component } from '@angular/core';
+
+// Example taken from https://gridbyexample.com/examples/example29/
+@Component({
+ selector: 'demo-grid-minmax',
+ template: `
+
+ Grid with Minmax
+
+
+
+
A
+
B
+
C
+
D
+
E
+
F
+
G
+
H
+
I
+
J
+
K
+
L
+
M
+
+
+
+
+ `,
+ styles: [`.box {
+ /*background-color: #444;*/
+ /*color: #fff;*/
+ border-radius: 5px;
+ padding: 20px;
+ font-size: 150%;
+
+ }`]
+})
+export class GridMinmaxComponent {
+}
diff --git a/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.spec.ts b/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.spec.ts
new file mode 100644
index 000000000..90df58a7e
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GridNestedComponent } from './grid-nested.component';
+
+describe('GridNestedComponent', () => {
+ let component: GridNestedComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GridNestedComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GridNestedComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.ts b/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.ts
new file mode 100644
index 000000000..4ffdbd4d1
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-nested/grid-nested.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+
+// Example taken from https://gridbyexample.com/examples/example21/
+@Component({
+ selector: 'demo-grid-nested',
+ template: `
+
+ Nested Grid
+
+
+
+
+ `,
+ styles: [`.box {
+ border-radius: 5px;
+ padding: 20px;
+ font-size: 150%;
+ }`]
+})
+export class GridNestedComponent {
+}
diff --git a/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.spec.ts b/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.spec.ts
new file mode 100644
index 000000000..c21751d75
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GridOverlayComponent } from './grid-overlay.component';
+
+describe('GridOverlayComponent', () => {
+ let component: GridOverlayComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GridOverlayComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GridOverlayComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.ts b/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.ts
new file mode 100644
index 000000000..1cffb13bf
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-overlay/grid-overlay.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'demo-grid-overlay',
+ template: `
+
+ Grid with Overlay
+
+
+
+
Header
+
Sidebar
+
Content
+
+ overlay
+
+
+
+
+
+ `,
+ styles: [`.box {
+ border-radius: 5px;
+ padding: 20px;
+ }`, `.overlay {
+ background-color: red;
+ z-index: 10;
+ }`]
+})
+export class GridOverlayComponent {
+}
diff --git a/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.spec.ts b/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.spec.ts
new file mode 100644
index 000000000..cda857f92
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GridPositionComponent } from './grid-position.component';
+
+describe('GridPositionComponent', () => {
+ let component: GridPositionComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GridPositionComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GridPositionComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.ts b/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.ts
new file mode 100644
index 000000000..3fef0b9df
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid-position/grid-position.component.ts
@@ -0,0 +1,54 @@
+import { Component } from '@angular/core';
+
+// Example taken from https://gridbyexample.com/examples/example16/
+@Component({
+ selector: 'demo-grid-position',
+ template: `
+
+ Grid with Positioning
+
+
+
+
Header
+
Sidebar
+
Content
+
The four arrows are inline images inside the content area.
+
+
+
+
+
Footer
+
+
+
+
+ `,
+ styles: [`.topleft {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }`, `.topright {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }`, `.bottomleft {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }`, `.bottomright {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }`, `.box {
+ border-radius: 5px;
+ padding: 50px;
+ font-size: 150%;
+ }`]
+})
+export class GridPositionComponent {
+}
diff --git a/src/apps/demo-app/src/app/grid/grid.module.ts b/src/apps/demo-app/src/app/grid/grid.module.ts
new file mode 100644
index 000000000..4864511a5
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/grid.module.ts
@@ -0,0 +1,35 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from '@angular/forms';
+import {MatCardModule} from '@angular/material/card';
+import {MatRadioModule} from '@angular/material/radio';
+import {FlexLayoutModule} from '@angular/flex-layout';
+
+import {DocsGridComponent} from './docs-grid/docs-grid.component';
+import {GridLayoutComponent} from './grid-layout/grid-layout.component';
+import {RoutingModule} from './routing.module';
+import {GridNestedComponent} from './grid-nested/grid-nested.component';
+import {GridMinmaxComponent} from './grid-minmax/grid-minmax.component';
+import {GridPositionComponent} from './grid-position/grid-position.component';
+import {GridOverlayComponent} from './grid-overlay/grid-overlay.component';
+
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ MatCardModule,
+ MatRadioModule,
+ FlexLayoutModule,
+ RoutingModule,
+ ],
+ declarations: [
+ DocsGridComponent,
+ GridLayoutComponent,
+ GridNestedComponent,
+ GridMinmaxComponent,
+ GridPositionComponent,
+ GridOverlayComponent,
+ ]
+})
+export class DocsGridModule {}
diff --git a/src/apps/demo-app/src/app/grid/routing.module.ts b/src/apps/demo-app/src/app/grid/routing.module.ts
new file mode 100644
index 000000000..ee95f8711
--- /dev/null
+++ b/src/apps/demo-app/src/app/grid/routing.module.ts
@@ -0,0 +1,17 @@
+import {NgModule} from '@angular/core';
+import {RouterModule} from '@angular/router';
+
+import {DocsGridComponent} from './docs-grid/docs-grid.component';
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([
+ {
+ path: '',
+ component: DocsGridComponent
+ }
+ ])
+ ],
+ exports: [RouterModule]
+})
+export class RoutingModule {}
diff --git a/src/apps/demo-app/src/app/routing.module.ts b/src/apps/demo-app/src/app/routing.module.ts
index 6b2ccb5af..a2aff8feb 100644
--- a/src/apps/demo-app/src/app/routing.module.ts
+++ b/src/apps/demo-app/src/app/routing.module.ts
@@ -4,6 +4,7 @@ import {HashLocationStrategy, LocationStrategy} from '@angular/common';
const DEMO_APP_ROUTES: Routes = [
{path: '', redirectTo: 'docs', pathMatch: 'full'},
+ {path: 'grid', loadChildren: './grid/grid.module#DocsGridModule'},
{path: 'docs', loadChildren: './layout/layout.module#DocsLayoutModule'},
{path: 'responsive', loadChildren: './responsive/responsive.module#DocsResponsiveModule'},
{path: 'issues', loadChildren: './github-issues/github-issues.module#DocsGithubIssuesModule'},
diff --git a/src/apps/demo-app/src/styles.scss b/src/apps/demo-app/src/styles.scss
index e4b373157..f37e79889 100644
--- a/src/apps/demo-app/src/styles.scss
+++ b/src/apps/demo-app/src/styles.scss
@@ -45,29 +45,56 @@ div.coloredContainerX > div, div.colorNested > div > div {
text-align: center;
}
-div.coloredContainerX > div:nth-child(1), div.colorNested > div > div:nth-child(1), div.box1 {
+div.coloredContainerX > div:nth-child(10n+1), div.colorNested > div > div:nth-child(10n+1),
+div.box1 {
background-color: #009688;
}
-div.coloredContainerX > div:nth-child(2), div.colorNested > div > div:nth-child(2), div.box2 {
+div.coloredContainerX > div:nth-child(10n+2), div.colorNested > div > div:nth-child(10n+2),
+div.box2 {
background-color: #3949ab;
}
-div.coloredContainerX > div:nth-child(3), div.coloredChildren > div:nth-child(3),
-div.colorNested > div > div:nth-child(3), div.box3 {
+div.coloredContainerX > div:nth-child(10n+3), div.coloredChildren > div:nth-child(10n+3),
+div.colorNested > div > div:nth-child(10n+3), div.box3 {
background-color: #9c27b0;
}
-div.coloredContainerX > div:nth-child(4), div.coloredChildren > div:nth-child(4),
-div.colorNested > div > div:nth-child(4) {
+div.coloredContainerX > div:nth-child(10n+4), div.coloredChildren > div:nth-child(10n+4),
+div.colorNested > div > div:nth-child(10n+4) {
background-color: #b08752;
}
-div.coloredContainerX > div:nth-child(5), div.coloredChildren > div:nth-child(5),
-div.colorNested > div > div:nth-child(5) {
+div.coloredContainerX > div:nth-child(10n+5), div.coloredChildren > div:nth-child(10n+5),
+div.colorNested > div > div:nth-child(10n+5) {
background-color: #5ca6b0;
}
+div.coloredContainerX > div:nth-child(10n+6), div.coloredChildren > div:nth-child(10n+6),
+div.colorNested > div > div:nth-child(10n+6) {
+ background-color: #8bc34a;
+}
+
+div.coloredContainerX > div:nth-child(10n+7), div.coloredChildren > div:nth-child(10n+7),
+div.colorNested > div > div:nth-child(10n+7) {
+ background-color: #9c27b0;
+}
+
+div.coloredContainerX > div:nth-child(10n+8), div.coloredChildren > div:nth-child(10n+8),
+div.colorNested > div > div:nth-child(10n+8) {
+ background-color: #c9954e;
+}
+
+div.coloredContainerX > div:nth-child(10n+9), div.coloredChildren > div:nth-child(10n+9),
+div.colorNested > div > div:nth-child(10n+9) {
+ background-color: #ff5722;
+}
+
+div.coloredContainerX > div:nth-child(10n), div.coloredChildren > div:nth-child(10n),
+div.colorNested > div > div:nth-child(10n) {
+ background-color: #03a9f4;
+}
+
div.colored > div {
padding: 8px;
box-shadow: 0 2px 5px 0 rgba(52, 47, 51, 0.63);
@@ -76,39 +103,38 @@ div.colored > div {
text-align: center;
}
-div.colored > div:nth-child(1), .one {
+div.colored > div:nth-child(10n+1), .one {
background-color: #009688;
}
-div.colored > div:nth-child(2), .two {
+div.colored > div:nth-child(10n+2), .two {
background-color: #3949ab;
}
-div.colored > div:nth-child(3), .three {
+div.colored > div:nth-child(10n+3), .three {
background-color: #9c27b0;
}
-div.colored > div:nth-child(4), .four {
+div.colored > div:nth-child(10n+4), .four {
background-color: #8bc34a;
}
-div.colored > div:nth-child(5), .five {
+div.colored > div:nth-child(10n+5), .five {
background-color: #03a9f4;
}
-div.colored > div:nth-child(6), .six {
+div.colored > div:nth-child(10n+6), .six {
background-color: #c9954e;
}
-div.colored > div:nth-child(7), .seven {
+div.colored > div:nth-child(10n+7), .seven {
background-color: #ff5722;
}
.hint {
- margin: 5px;
+ margin: 5px 5px 0;
font-size: 0.9em;
color: #a3a3a3;
- margin-bottom: 0;
}
.title {
@@ -183,8 +209,7 @@ div.colored.box.nopad > div {
}
.bigger {
- padding: 0 20px;
- padding-bottom: 30px;
+ padding: 0 20px 30px;
}
mat-toolbar .mat-toolbar-layout mat-toolbar-row {
@@ -287,8 +312,7 @@ mat-card-content pre {
.hint {
color: #a3a3a3;
font-size: 0.9em;
- margin: 5px;
- margin-bottom: 0;
+ margin: 5px 5px 0;
}
.forceAbove {
diff --git a/src/apps/hello-world/package.json b/src/apps/hello-world/package.json
index 897788152..81f85eac9 100644
--- a/src/apps/hello-world/package.json
+++ b/src/apps/hello-world/package.json
@@ -31,7 +31,7 @@
"devDependencies": {
"@angular/cli": "~1.7.2",
"@angular/compiler-cli": "file:../../../node_modules/@angular/compiler-cli",
- "@angular/language-service": "^6.0.0-rc.0",
+ "@angular/language-service": "^6.0.0-rc.1",
"typescript": "file:../../../node_modules/typescript"
}
}
diff --git a/src/apps/universal-app/package.json b/src/apps/universal-app/package.json
index e66f6f709..fb78a6720 100644
--- a/src/apps/universal-app/package.json
+++ b/src/apps/universal-app/package.json
@@ -36,7 +36,7 @@
"devDependencies": {
"@angular/cli": "1.7.2",
"@angular/compiler-cli": "file:../../../node_modules/@angular/compiler-cli",
- "@angular/language-service": "^6.0.0-rc.0",
+ "@angular/language-service": "^6.0.0-rc.1",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
diff --git a/src/lib/core/README.md b/src/lib/core/README.md
index e3fcec6ce..7e618b3f2 100644
--- a/src/lib/core/README.md
+++ b/src/lib/core/README.md
@@ -2,7 +2,7 @@ The `core` entrypoint contains all of the common utilities to build Layout
components. Its primary exports are the `MediaQuery` utilities (`MatchMedia`,
`ObservableMedia`) and the module that encapsulates the imports of these
providers, the `CoreModule`, and the base directive for layout
-components, `BaseFxDirective`. These utilies can be imported separately
+components, `BaseDirective`. These utilies can be imported separately
from the root module to take advantage of tree shaking.
```typescript
@@ -19,7 +19,7 @@ export class AppModule {}
```
```typescript
-import {BaseFxDirective} from '@angular/flex-layout/core';
+import {BaseDirective} from '@angular/flex-layout/core';
-export class NewLayoutDirective extends BaseFxDirective {}
+export class NewLayoutDirective extends BaseDirective {}
```
\ No newline at end of file
diff --git a/src/lib/core/base/base-adapter.spec.ts b/src/lib/core/base/base-adapter.spec.ts
index dde126ca7..83110cfdf 100644
--- a/src/lib/core/base/base-adapter.spec.ts
+++ b/src/lib/core/base/base-adapter.spec.ts
@@ -6,15 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ElementRef} from '@angular/core';
-import {BaseFxDirectiveAdapter} from './base-adapter';
+import {BaseDirectiveAdapter} from './base-adapter';
import {expect} from '../../utils/testing/custom-matchers';
import {MediaMonitor} from '../media-monitor/media-monitor';
import {StyleUtils} from '../style-utils/style-utils';
-describe('BaseFxDirectiveAdapter class', () => {
+describe('BaseDirectiveAdapter class', () => {
let component;
beforeEach(() => {
- component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, {} as ElementRef, {} as StyleUtils); // tslint:disable-line:max-line-length
+ component = new BaseDirectiveAdapter('', {} as MediaMonitor, {} as ElementRef, {} as StyleUtils); // tslint:disable-line:max-line-length
});
describe('cacheInput', () => {
it('should call _cacheInputArray when source is an array', () => {
diff --git a/src/lib/core/base/base-adapter.ts b/src/lib/core/base/base-adapter.ts
index c4639edcb..2bf121180 100644
--- a/src/lib/core/base/base-adapter.ts
+++ b/src/lib/core/base/base-adapter.ts
@@ -7,7 +7,7 @@
*/
import {ElementRef} from '@angular/core';
-import {BaseFxDirective} from './base';
+import {BaseDirective} from './base';
import {ResponsiveActivation} from '../responsive-activation/responsive-activation';
import {MediaQuerySubscriber} from '../media-change';
import {MediaMonitor} from '../media-monitor/media-monitor';
@@ -15,10 +15,10 @@ import {StyleUtils} from '../style-utils/style-utils';
/**
- * Adapter to the BaseFxDirective abstract class so it can be used via composition.
- * @see BaseFxDirective
+ * Adapter to the BaseDirective abstract class so it can be used via composition.
+ * @see BaseDirective
*/
-export class BaseFxDirectiveAdapter extends BaseFxDirective {
+export class BaseDirectiveAdapter extends BaseDirective {
/**
* Accessor to determine which @Input property is "active"
@@ -37,14 +37,14 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
}
/**
- * @see BaseFxDirective._mqActivation
+ * @see BaseDirective._mqActivation
*/
get mqActivation(): ResponsiveActivation {
return this._mqActivation;
}
/**
- * BaseFxDirectiveAdapter constructor
+ * BaseDirectiveAdapter constructor
*/
constructor(protected _baseKey: string, // non-responsive @Input property name
protected _mediaMonitor: MediaMonitor,
@@ -62,7 +62,7 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
}
/**
- * @see BaseFxDirective._queryInput
+ * @see BaseDirective._queryInput
*/
queryInput(key) {
return key ? this._queryInput(key) : undefined;
@@ -88,7 +88,7 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
}
/**
- * @see BaseFxDirective._listenForMediaQueryChanges
+ * @see BaseDirective._listenForMediaQueryChanges
*/
listenForMediaQueryChanges(key: string,
defaultValue: any,
diff --git a/src/lib/core/base/base-legacy.ts b/src/lib/core/base/base-legacy.ts
new file mode 100644
index 000000000..b56f722ba
--- /dev/null
+++ b/src/lib/core/base/base-legacy.ts
@@ -0,0 +1,282 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ ElementRef,
+ OnDestroy,
+ SimpleChanges,
+ OnChanges,
+ SimpleChange,
+} from '@angular/core';
+
+import {buildLayoutCSS} from '../../utils/layout-validator';
+import {
+ StyleDefinition,
+ StyleUtils,
+} from '../style-utils/style-utils';
+
+import {ResponsiveActivation, KeyOptions} from '../responsive-activation/responsive-activation';
+import {MediaMonitor} from '../media-monitor/media-monitor';
+import {MediaQuerySubscriber} from '../media-change';
+
+/**
+ * @deprecated
+ * @deletion-target v6.0.0-beta.17
+ * Abstract base class for the Layout API styling directives.
+ */
+export abstract class BaseFxDirective implements OnDestroy, OnChanges {
+ get hasMediaQueryListener() {
+ return !!this._mqActivation;
+ }
+
+ /**
+ * Imperatively determine the current activated [input] value;
+ * if called before ngOnInit() this will return `undefined`
+ */
+ get activatedValue(): string | number {
+ return this._mqActivation ? this._mqActivation.activatedInput : undefined;
+ }
+
+ /**
+ * Change the currently activated input value and force-update
+ * the injected CSS (by-passing change detection).
+ *
+ * NOTE: Only the currently activated input value will be modified;
+ * other input values will NOT be affected.
+ */
+ set activatedValue(value: string | number) {
+ let key = 'baseKey', previousVal;
+
+ if (this._mqActivation) {
+ key = this._mqActivation.activatedInputKey;
+ previousVal = this._inputMap[key];
+ this._inputMap[key] = value;
+ }
+ let change = new SimpleChange(previousVal, value, false);
+
+ this.ngOnChanges({[key]: change} as SimpleChanges);
+ }
+
+
+ /**
+ * Constructor
+ */
+ constructor(protected _mediaMonitor: MediaMonitor,
+ protected _elementRef: ElementRef,
+ protected _styler: StyleUtils) {
+ }
+
+ // *********************************************
+ // Accessor Methods
+ // *********************************************
+
+ /**
+ * Access to host element's parent DOM node
+ */
+ protected get parentElement(): any {
+ return this._elementRef.nativeElement.parentNode;
+ }
+
+ protected get nativeElement(): HTMLElement {
+ return this._elementRef.nativeElement;
+ }
+
+ /**
+ * Access the current value (if any) of the @Input property.
+ */
+ protected _queryInput(key) {
+ return this._inputMap[key];
+ }
+
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * Use post-component-initialization event to perform extra
+ * querying such as computed Display style
+ */
+ ngOnInit() {
+ this._display = this._getDisplayStyle();
+ this._hasInitialized = true;
+ }
+
+ ngOnChanges(change: SimpleChanges) {
+ throw new Error(`BaseFxDirective::ngOnChanges should be overridden in subclass: ${change}`);
+ }
+
+ ngOnDestroy() {
+ if (this._mqActivation) {
+ this._mqActivation.destroy();
+ }
+ delete this._mediaMonitor;
+ }
+
+ // *********************************************
+ // Protected Methods
+ // *********************************************
+
+ /**
+ * Was the directive's default selector used ?
+ * If not, use the fallback value!
+ */
+ protected _getDefaultVal(key: string, fallbackVal: any): string | boolean {
+ let val = this._queryInput(key);
+ let hasDefaultVal = (val !== undefined && val !== null);
+ return (hasDefaultVal && val !== '') ? val : fallbackVal;
+ }
+
+ /**
+ * Quick accessor to the current HTMLElement's `display` style
+ * Note: this allows us to preserve the original style
+ * and optional restore it when the mediaQueries deactivate
+ */
+ protected _getDisplayStyle(source: HTMLElement = this.nativeElement): string {
+ const query = 'display';
+ return this._styler.lookupStyle(source, query);
+ }
+
+ /**
+ * Quick accessor to raw attribute value on the target DOM element
+ */
+ protected _getAttributeValue(attribute: string,
+ source: HTMLElement = this.nativeElement): string {
+ return this._styler.lookupAttributeValue(source, attribute);
+ }
+
+ /**
+ * Determine the DOM element's Flexbox flow (flex-direction).
+ *
+ * Check inline style first then check computed (stylesheet) style.
+ * And optionally add the flow value to element's inline style.
+ */
+ protected _getFlowDirection(target: HTMLElement, addIfMissing = false): string {
+ let value = 'row';
+ let hasInlineValue = '';
+
+ if (target) {
+ [value, hasInlineValue] = this._styler.getFlowDirection(target);
+
+ if (!hasInlineValue && addIfMissing) {
+ const style = buildLayoutCSS(value);
+ const elements = [target];
+ this._styler.applyStyleToElements(style, elements);
+ }
+ }
+
+ return value.trim() || 'row';
+ }
+
+ /**
+ * Applies styles given via string pair or object map to the directive element.
+ */
+ protected _applyStyleToElement(style: StyleDefinition,
+ value?: string | number,
+ element: HTMLElement = this.nativeElement) {
+ this._styler.applyStyleToElement(element, style, value);
+ }
+
+ /**
+ * Applies styles given via string pair or object map to the directive's element.
+ */
+ protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[]) {
+ this._styler.applyStyleToElements(style, elements);
+ }
+
+ /**
+ * Save the property value; which may be a complex object.
+ * Complex objects support property chains
+ */
+ protected _cacheInput(key?: string, source?: any) {
+ if (typeof source === 'object') {
+ for (let prop in source) {
+ this._inputMap[prop] = source[prop];
+ }
+ } else {
+ if (!!key) {
+ this._inputMap[key] = source;
+ }
+ }
+ }
+
+ /**
+ * Build a ResponsiveActivation object used to manage subscriptions to mediaChange notifications
+ * and intelligent lookup of the directive's property value that corresponds to that mediaQuery
+ * (or closest match).
+ */
+ protected _listenForMediaQueryChanges(key: string,
+ defaultValue: any,
+ onMediaQueryChange: MediaQuerySubscriber): ResponsiveActivation { // tslint:disable-line:max-line-length
+ if (!this._mqActivation) {
+ let keyOptions = new KeyOptions(key, defaultValue, this._inputMap);
+ this._mqActivation = new ResponsiveActivation(
+ keyOptions,
+ this._mediaMonitor,
+ (change) => onMediaQueryChange(change)
+ );
+ }
+ return this._mqActivation;
+ }
+
+ /**
+ * Special accessor to query for all child 'element' nodes regardless of type, class, etc.
+ */
+ protected get childrenNodes() {
+ const obj = this.nativeElement.children;
+ const buffer: any[] = [];
+
+ // iterate backwards ensuring that length is an UInt32
+ for (let i = obj.length; i--; ) {
+ buffer[i] = obj[i];
+ }
+ return buffer;
+ }
+
+ /**
+ * Does this directive have 1 or more responsive keys defined
+ * Note: we exclude the 'baseKey' key (which is NOT considered responsive)
+ */
+ hasResponsiveAPI(baseKey: string) {
+ const totalKeys = Object.keys(this._inputMap).length;
+ const baseValue = this._inputMap[baseKey];
+ return (totalKeys - (!!baseValue ? 1 : 0)) > 0;
+ }
+
+
+ /**
+ * Fast validator for presence of attribute on the host element
+ */
+ protected hasKeyValue(key) {
+ return this._mqActivation.hasKeyValue(key);
+ }
+
+ protected get hasInitialized() {
+ return this._hasInitialized;
+ }
+
+ /** Original dom Elements CSS display style */
+ protected _display;
+
+ /**
+ * MediaQuery Activation Tracker
+ */
+ protected _mqActivation: ResponsiveActivation;
+
+ /**
+ * Dictionary of input keys with associated values
+ */
+ protected _inputMap = {};
+
+ /**
+ * Has the `ngOnInit()` method fired
+ *
+ * Used to allow *ngFor tasks to finish and support queries like
+ * getComputedStyle() during ngOnInit().
+ */
+ protected _hasInitialized = false;
+}
diff --git a/src/lib/core/base/base.ts b/src/lib/core/base/base.ts
index 955e4730a..9e395b4c3 100644
--- a/src/lib/core/base/base.ts
+++ b/src/lib/core/base/base.ts
@@ -18,13 +18,12 @@ import {
StyleDefinition,
StyleUtils,
} from '../style-utils/style-utils';
-
import {ResponsiveActivation, KeyOptions} from '../responsive-activation/responsive-activation';
import {MediaMonitor} from '../media-monitor/media-monitor';
import {MediaQuerySubscriber} from '../media-change';
/** Abstract base class for the Layout API styling directives. */
-export abstract class BaseFxDirective implements OnDestroy, OnChanges {
+export abstract class BaseDirective implements OnDestroy, OnChanges {
get hasMediaQueryListener() {
return !!this._mqActivation;
}
@@ -103,7 +102,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
}
ngOnChanges(change: SimpleChanges) {
- throw new Error(`BaseFxDirective::ngOnChanges should be overridden in subclass: ${change}`);
+ throw new Error(`BaseDirective::ngOnChanges should be overridden in subclass: ${change}`);
}
ngOnDestroy() {
@@ -151,7 +150,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
* Check inline style first then check computed (stylesheet) style.
* And optionally add the flow value to element's inline style.
*/
- protected _getFlowDirection(target: HTMLElement, addIfMissing = false): string {
+ protected _getFlexFlowDirection(target: HTMLElement, addIfMissing = false): string {
let value = 'row';
let hasInlineValue = '';
diff --git a/src/lib/core/base/index.ts b/src/lib/core/base/index.ts
index 855382060..df276cde0 100644
--- a/src/lib/core/base/index.ts
+++ b/src/lib/core/base/index.ts
@@ -8,3 +8,4 @@
export * from './base';
export * from './base-adapter';
+export * from './base-legacy';
diff --git a/src/lib/core/style-utils/style-utils.ts b/src/lib/core/style-utils/style-utils.ts
index 280f320cb..fb3da8fc1 100644
--- a/src/lib/core/style-utils/style-utils.ts
+++ b/src/lib/core/style-utils/style-utils.ts
@@ -72,7 +72,8 @@ export class StyleUtils {
* Find the DOM element's inline style value (if any)
*/
lookupInlineStyle(element: HTMLElement, styleName: string): string {
- return element.style[styleName] || element.style.getPropertyValue(styleName) || '';
+ return isPlatformBrowser(this._platformId) ?
+ element.style[styleName] : this._getServerStyle(element, styleName);
}
/**
@@ -112,13 +113,57 @@ export class StyleUtils {
values.sort();
for (let value of values) {
if (isPlatformBrowser(this._platformId) || !this._serverModuleLoaded) {
- element.style.setProperty(key, value);
+ isPlatformBrowser(this._platformId) ?
+ element.style.setProperty(key, value) : this._setServerStyle(element, key, value);
} else {
this._serverStylesheet.addStyleToElement(element, key, value);
}
}
});
}
+
+ private _setServerStyle(element: any, styleName: string, styleValue?: string|null) {
+ styleName = styleName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+ const styleMap = this._readStyleAttribute(element);
+ styleMap[styleName] = styleValue || '';
+ this._writeStyleAttribute(element, styleMap);
+ }
+
+ private _getServerStyle(element: any, styleName: string): string {
+ const styleMap = this._readStyleAttribute(element);
+ return styleMap[styleName] || '';
+ }
+
+ private _readStyleAttribute(element: any): {[name: string]: string} {
+ const styleMap: {[name: string]: string} = {};
+ const styleAttribute = element.getAttribute('style');
+ if (styleAttribute) {
+ const styleList = styleAttribute.split(/;+/g);
+ for (let i = 0; i < styleList.length; i++) {
+ const style = styleList[i].trim();
+ if (style.length > 0) {
+ const colonIndex = style.indexOf(':');
+ if (colonIndex === -1) {
+ throw new Error(`Invalid CSS style: ${style}`);
+ }
+ const name = style.substr(0, colonIndex).trim();
+ styleMap[name] = style.substr(colonIndex + 1).trim();
+ }
+ }
+ }
+ return styleMap;
+ }
+
+ private _writeStyleAttribute(element: any, styleMap: {[name: string]: string}) {
+ let styleAttrValue = '';
+ for (const key in styleMap) {
+ const newValue = styleMap[key];
+ if (newValue) {
+ styleAttrValue += key + ':' + styleMap[key] + ';';
+ }
+ }
+ element.setAttribute('style', styleAttrValue);
+ }
}
/**
diff --git a/src/lib/extended/class/class.ts b/src/lib/extended/class/class.ts
index 4966a9367..62dc9f452 100644
--- a/src/lib/extended/class/class.ts
+++ b/src/lib/extended/class/class.ts
@@ -22,8 +22,8 @@ import {
} from '@angular/core';
import {NgClass} from '@angular/common';
import {
- BaseFxDirective,
- BaseFxDirectiveAdapter,
+ BaseDirective,
+ BaseDirectiveAdapter,
MediaChange,
MediaMonitor,
StyleUtils,
@@ -45,7 +45,7 @@ export type NgClassType = string | string[] | Set | {[klass: string]: an
[ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]
`
})
-export class ClassDirective extends BaseFxDirective
+export class ClassDirective extends BaseDirective
implements DoCheck, OnChanges, OnDestroy, OnInit {
/**
@@ -138,7 +138,7 @@ export class ClassDirective extends BaseFxDirective
* keys have been defined.
*/
protected _configureAdapters() {
- this._base = new BaseFxDirectiveAdapter(
+ this._base = new BaseDirectiveAdapter(
'ngClass',
this.monitor,
this._ngEl,
@@ -171,5 +171,5 @@ export class ClassDirective extends BaseFxDirective
* Special adapter to cross-cut responsive behaviors and capture mediaQuery changes
* Delegate value changes to the internal `_ngClassInstance` for processing
*/
- protected _base: BaseFxDirectiveAdapter; // used for `ngClass.xxx` selectors
+ protected _base: BaseDirectiveAdapter; // used for `ngClass.xxx` selectors
}
diff --git a/src/lib/extended/img-src/img-src.ts b/src/lib/extended/img-src/img-src.ts
index e4e17ba9e..a4848e3fe 100644
--- a/src/lib/extended/img-src/img-src.ts
+++ b/src/lib/extended/img-src/img-src.ts
@@ -17,7 +17,7 @@ import {
} from '@angular/core';
import {isPlatformServer} from '@angular/common';
import {
- BaseFxDirective,
+ BaseDirective,
MediaMonitor,
SERVER_TOKEN,
StyleUtils,
@@ -40,7 +40,7 @@ import {
img[src.gt-xs], img[src.gt-sm], img[src.gt-md], img[src.gt-lg]
`
})
-export class ImgSrcDirective extends BaseFxDirective implements OnInit, OnChanges {
+export class ImgSrcDirective extends BaseDirective implements OnInit, OnChanges {
/* tslint:disable */
@Input('src') set srcBase(val) { this.cacheDefaultSrc(val); }
diff --git a/src/lib/extended/show-hide/show-hide.ts b/src/lib/extended/show-hide/show-hide.ts
index c488a00ae..d1be0423d 100644
--- a/src/lib/extended/show-hide/show-hide.ts
+++ b/src/lib/extended/show-hide/show-hide.ts
@@ -20,7 +20,7 @@ import {
} from '@angular/core';
import {isPlatformServer} from '@angular/common';
import {
- BaseFxDirective,
+ BaseDirective,
MediaChange,
MediaMonitor,
SERVER_TOKEN,
@@ -59,7 +59,7 @@ export function negativeOf(hide: any) {
[fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]
`
})
-export class ShowHideDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class ShowHideDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
/**
* Subscription to the parent flex container's layout changes.
diff --git a/src/lib/extended/style/style.ts b/src/lib/extended/style/style.ts
index ae8ad0087..3c5181a2e 100644
--- a/src/lib/extended/style/style.ts
+++ b/src/lib/extended/style/style.ts
@@ -23,8 +23,8 @@ import {
import {NgStyle} from '@angular/common';
import {DomSanitizer} from '@angular/platform-browser';
import {
- BaseFxDirective,
- BaseFxDirectiveAdapter,
+ BaseDirective,
+ BaseDirectiveAdapter,
MediaChange,
MediaMonitor,
StyleUtils,
@@ -50,7 +50,7 @@ import {
[ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]
`
})
-export class StyleDirective extends BaseFxDirective
+export class StyleDirective extends BaseDirective
implements DoCheck, OnChanges, OnDestroy, OnInit {
/**
@@ -136,7 +136,7 @@ export class StyleDirective extends BaseFxDirective
* keys have been defined.
*/
protected _configureAdapters() {
- this._base = new BaseFxDirectiveAdapter(
+ this._base = new BaseDirectiveAdapter(
'ngStyle',
this.monitor,
this._ngEl,
@@ -216,6 +216,6 @@ export class StyleDirective extends BaseFxDirective
* Special adapter to cross-cut responsive behaviors
* into the StyleDirective
*/
- protected _base: BaseFxDirectiveAdapter;
+ protected _base: BaseDirectiveAdapter;
}
diff --git a/src/lib/flex/flex-align/flex-align.ts b/src/lib/flex/flex-align/flex-align.ts
index 4cc703319..6300c19bb 100644
--- a/src/lib/flex/flex-align/flex-align.ts
+++ b/src/lib/flex/flex-align/flex-align.ts
@@ -14,7 +14,7 @@ import {
OnDestroy,
SimpleChanges,
} from '@angular/core';
-import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
/**
@@ -30,7 +30,7 @@ import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/f
[fxFlexAlign.gt-xs], [fxFlexAlign.gt-sm], [fxFlexAlign.gt-md], [fxFlexAlign.gt-lg]
`
})
-export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class FlexAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
/* tslint:disable */
@Input('fxFlexAlign') set align(val) { this._cacheInput('align', val); };
diff --git a/src/lib/flex/flex-fill/flex-fill.ts b/src/lib/flex/flex-fill/flex-fill.ts
index 7ec49629c..2b825794f 100644
--- a/src/lib/flex/flex-fill/flex-fill.ts
+++ b/src/lib/flex/flex-fill/flex-fill.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, ElementRef} from '@angular/core';
-import {BaseFxDirective, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {BaseDirective, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
const FLEX_FILL_CSS = {
@@ -27,7 +27,7 @@ const FLEX_FILL_CSS = {
[fxFill],
[fxFlexFill]
`})
-export class FlexFillDirective extends BaseFxDirective {
+export class FlexFillDirective extends BaseDirective {
constructor(monitor: MediaMonitor,
public elRef: ElementRef,
styleUtils: StyleUtils) {
diff --git a/src/lib/flex/flex-offset/flex-offset.ts b/src/lib/flex/flex-offset/flex-offset.ts
index 3380bb7b8..98cad98c9 100644
--- a/src/lib/flex/flex-offset/flex-offset.ts
+++ b/src/lib/flex/flex-offset/flex-offset.ts
@@ -18,7 +18,7 @@ import {
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {
- BaseFxDirective,
+ BaseDirective,
MediaChange,
MediaMonitor,
StyleDefinition,
@@ -39,7 +39,7 @@ import {isFlowHorizontal} from '../../utils/layout-validator';
[fxFlexOffset.lt-sm], [fxFlexOffset.lt-md], [fxFlexOffset.lt-lg], [fxFlexOffset.lt-xl],
[fxFlexOffset.gt-xs], [fxFlexOffset.gt-sm], [fxFlexOffset.gt-md], [fxFlexOffset.gt-lg]
`})
-export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
private _directionWatcher: Subscription;
/* tslint:disable */
@@ -171,7 +171,7 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
// The flex-direction of this element's flex container. Defaults to 'row'.
const isRtl = this._directionality.value === 'rtl';
- const layout = this._getFlowDirection(this.parentElement, true);
+ const layout = this._getFlexFlowDirection(this.parentElement, true);
const horizontalLayoutKey = isRtl ? 'margin-right' : 'margin-left';
return isFlowHorizontal(layout) ? {[horizontalLayoutKey]: `${offset}`} :
diff --git a/src/lib/flex/flex-order/flex-order.ts b/src/lib/flex/flex-order/flex-order.ts
index 5009d5408..54509c618 100644
--- a/src/lib/flex/flex-order/flex-order.ts
+++ b/src/lib/flex/flex-order/flex-order.ts
@@ -14,7 +14,7 @@ import {
OnDestroy,
SimpleChanges,
} from '@angular/core';
-import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
/**
@@ -28,7 +28,7 @@ import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/f
[fxFlexOrder.lt-sm], [fxFlexOrder.lt-md], [fxFlexOrder.lt-lg], [fxFlexOrder.lt-xl],
[fxFlexOrder.gt-xs], [fxFlexOrder.gt-sm], [fxFlexOrder.gt-md], [fxFlexOrder.gt-lg]
`})
-export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class FlexOrderDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
/* tslint:disable */
@Input('fxFlexOrder') set order(val) { this._cacheInput('order', val); }
diff --git a/src/lib/flex/flex/flex.spec.ts b/src/lib/flex/flex/flex.spec.ts
index e9facd4e0..70dbb73c1 100644
--- a/src/lib/flex/flex/flex.spec.ts
+++ b/src/lib/flex/flex/flex.spec.ts
@@ -283,7 +283,7 @@ describe('flex directive', () => {
it('should work with calc values', () => {
// @see http://caniuse.com/#feat=calc for IE issues with calc()
componentWithTemplate(`
`);
- if (!(platform.FIREFOX || platform.TRIDENT)) {
+ if (!(platform.FIREFOX || platform.EDGE)) {
expectNativeEl(fixture).toHaveStyle({
'box-sizing': 'border-box',
'flex-grow': '1',
@@ -296,7 +296,7 @@ describe('flex directive', () => {
it('should work with calc without internal whitespaces', async(() => {
// @see http://caniuse.com/#feat=calc for IE issues with calc()
componentWithTemplate('
');
- if (!(platform.FIREFOX || platform.TRIDENT)) {
+ if (!(platform.FIREFOX || platform.EDGE)) {
fixture.detectChanges();
setTimeout(() => {
expectNativeEl(fixture).toHaveStyle({
diff --git a/src/lib/flex/flex/flex.ts b/src/lib/flex/flex/flex.ts
index 2e4072b0a..26d183663 100644
--- a/src/lib/flex/flex/flex.ts
+++ b/src/lib/flex/flex/flex.ts
@@ -19,7 +19,7 @@ import {
} from '@angular/core';
import {
ADD_FLEX_STYLES,
- BaseFxDirective,
+ BaseDirective,
MediaChange,
MediaMonitor,
StyleUtils,
@@ -49,7 +49,7 @@ export type FlexBasisAlias = 'grow' | 'initial' | 'auto' | 'none' | 'nogrow' | '
[fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg],
`
})
-export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class FlexDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
/** The flex-direction of this element's flex container. Defaults to 'row'. */
protected _layout: Layout;
@@ -163,7 +163,7 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
shrink: number|string,
basis: string|number|FlexBasisAlias) {
// The flex-direction of this element's flex container. Defaults to 'row'.
- let layout = this._getFlowDirection(this.parentElement, !!this.addFlexStyles);
+ let layout = this._getFlexFlowDirection(this.parentElement, !!this.addFlexStyles);
let direction = (layout.indexOf('column') > -1) ? 'column' : 'row';
let max = isFlowHorizontal(direction) ? 'max-width' : 'max-height';
diff --git a/src/lib/flex/layout-align/layout-align.ts b/src/lib/flex/layout-align/layout-align.ts
index 13818108e..08720db3f 100644
--- a/src/lib/flex/layout-align/layout-align.ts
+++ b/src/lib/flex/layout-align/layout-align.ts
@@ -16,7 +16,7 @@ import {
SimpleChanges,
Self,
} from '@angular/core';
-import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
import {Subscription} from 'rxjs';
import {extendObject} from '../../utils/object-extend';
@@ -38,7 +38,7 @@ import {LAYOUT_VALUES, isFlowHorizontal} from '../../utils/layout-validator';
[fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl],
[fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]
`})
-export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class LayoutAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
protected _layout = 'row'; // default flex-direction
protected _layoutWatcher: Subscription;
diff --git a/src/lib/flex/layout-gap/layout-gap.ts b/src/lib/flex/layout-gap/layout-gap.ts
index 479d4da69..e387c9cbf 100644
--- a/src/lib/flex/layout-gap/layout-gap.ts
+++ b/src/lib/flex/layout-gap/layout-gap.ts
@@ -19,7 +19,7 @@ import {
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {
- BaseFxDirective,
+ BaseDirective,
MediaChange,
MediaMonitor,
StyleDefinition,
@@ -42,7 +42,7 @@ import {LAYOUT_VALUES} from '../../utils/layout-validator';
[fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]
`
})
-export class LayoutGapDirective extends BaseFxDirective
+export class LayoutGapDirective extends BaseDirective
implements AfterContentInit, OnChanges, OnDestroy {
protected _layout = 'row'; // default flex-direction
protected _layoutWatcher: Subscription;
diff --git a/src/lib/flex/layout/layout.ts b/src/lib/flex/layout/layout.ts
index 6bbfa37f6..50df899ab 100644
--- a/src/lib/flex/layout/layout.ts
+++ b/src/lib/flex/layout/layout.ts
@@ -14,7 +14,7 @@ import {
OnDestroy,
SimpleChanges,
} from '@angular/core';
-import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
import {Observable, ReplaySubject} from 'rxjs';
import {buildLayoutCSS} from '../../utils/layout-validator';
@@ -37,7 +37,7 @@ export type Layout = {
[fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl],
[fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]
`})
-export class LayoutDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
+export class LayoutDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
/**
* Create Observable for nested/child 'flex' directives. This allows
diff --git a/src/lib/grid/README.md b/src/lib/grid/README.md
new file mode 100644
index 000000000..cece012d0
--- /dev/null
+++ b/src/lib/grid/README.md
@@ -0,0 +1,19 @@
+The `grid` entrypoint contains all of the CSS Grid APIs provided by the
+Layout library. This includes directives for flexbox containers like
+`gdArea` (a.k.a. `GridAreaDirective`) and children like `gdRow`
+(a.k.a. `GdRowDirective`). The main export from this entrypoint is the
+`GridModule` that encapsulates these directives, and can be
+imported separately to take advantage of tree shaking.
+
+```typescript
+import {NgModule} from '@angular/core';
+import {GridModule} from '@angular/flex-layout/grid';
+
+@NgModule(({
+ imports: [
+ ... other imports here
+ GridModule,
+ ]
+}))
+export class AppModule {}
+```
diff --git a/src/lib/grid/align-columns/align-columns.spec.ts b/src/lib/grid/align-columns/align-columns.spec.ts
new file mode 100644
index 000000000..856315791
--- /dev/null
+++ b/src/lib/grid/align-columns/align-columns.spec.ts
@@ -0,0 +1,411 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component, OnInit} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {extendObject} from '../../utils/object-extend';
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('align columns directive', () => {
+ let fixture: ComponentFixture;
+ let matchMedia: MockMatchMedia;
+ let styler: StyleUtils;
+ let shouldRun = true;
+ let createTestComponent = (template: string) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestAlignComponent)(template);
+
+ inject([MatchMedia, StyleUtils, Platform],
+ (_matchMedia: MockMatchMedia, _styler: StyleUtils, _platform: Platform) => {
+ matchMedia = _matchMedia;
+ styler = _styler;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestAlignComponent],
+ providers: [MockMatchMediaProvider],
+ });
+ });
+
+ describe('with static features', () => {
+
+ it('should add correct styles for default `gdAlignColumns` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+ });
+
+ it('should work with inline grid', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({
+ 'display': 'inline-grid'
+ }, DEFAULT_ALIGNS),
+ styler);
+ });
+
+ describe('for "main-axis" testing', () => {
+ it('should add correct styles for `gdAlignColumns="start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'start'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'end'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="stretch"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'stretch'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'center'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="space-around"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'space-around'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="space-between"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'space-between'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="space-evenly"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'align-content': 'space-evenly'}, CROSS_DEFAULT), styler
+ );
+ });
+
+ it('should add ignore invalid row-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, CROSS_DEFAULT), styler
+ );
+ });
+ });
+
+ describe('for "cross-axis" testing', () => {
+ it('should add correct styles for `gdAlignColumns="start start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'align-items': 'start'}), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="start center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'align-items': 'center'}), styler
+ );
+ });
+ it('should add correct styles for `gdAlignColumns="start end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'align-items': 'end'}), styler
+ );
+ });
+ it('should add ignore invalid column-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, CROSS_DEFAULT), styler
+ );
+ });
+ });
+
+ describe('for dynamic inputs', () => {
+ it('should add correct styles and ignore invalid axes values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.componentInstance.alignBy = 'center end';
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center',
+ 'align-items': 'end'
+ }, styler);
+
+ fixture.componentInstance.alignBy = 'invalid invalid';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+
+ fixture.componentInstance.alignBy = '';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+ });
+ });
+
+ });
+
+ describe('with responsive features', () => {
+
+ it('should ignore responsive changes when not configured', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center',
+ 'align-items': 'center'
+ }, styler);
+ });
+
+ it('should add responsive styles when configured', () => {
+ createTestComponent(`
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center',
+ 'align-items': 'center'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'end',
+ 'align-items': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to default styles when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center',
+ 'align-items': 'stretch'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'end',
+ 'align-items': 'stretch'
+ }, styler);
+
+ matchMedia.activate('xs');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center',
+ 'align-items': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to closest overlapping value when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.useOverlaps = true;
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'start'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'center'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('lg', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'end'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('xl', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'align-content': 'end'
+ }, styler);
+ });
+
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestAlignComponent implements OnInit {
+ mainAxis = 'start';
+ crossAxis = 'end';
+
+ set alignBy(style) {
+ let vals = style.split(' ');
+ this.mainAxis = vals[0];
+ this.crossAxis = vals.length > 1 ? vals[1] : '';
+ }
+
+ get alignBy() {
+ return `${this.mainAxis} ${this.crossAxis}`;
+ }
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+}
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+const DEFAULT_ALIGNS = {
+ 'align-content': 'start',
+ 'align-items': 'stretch'
+};
+const MAIN_DEFAULT = {
+ 'align-content': 'start'
+};
+const CROSS_DEFAULT = {
+ 'align-items': 'stretch'
+};
+
diff --git a/src/lib/grid/align-columns/align-columns.ts b/src/lib/grid/align-columns/align-columns.ts
new file mode 100644
index 000000000..f512277d5
--- /dev/null
+++ b/src/lib/grid/align-columns/align-columns.ts
@@ -0,0 +1,162 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {extendObject} from '../../utils/object-extend';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'alignColumns';
+const DEFAULT_MAIN = 'start';
+const DEFAULT_CROSS = 'stretch';
+
+/**
+ * 'column alignment' CSS Grid styling directive
+ * Configures the alignment in the column direction
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-19
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-21
+ */
+@Directive({selector: `
+ [gdAlignColumns],
+ [gdAlignColumns.xs], [gdAlignColumns.sm], [gdAlignColumns.md],
+ [gdAlignColumns.lg], [gdAlignColumns.xl], [gdAlignColumns.lt-sm],
+ [gdAlignColumns.lt-md], [gdAlignColumns.lt-lg], [gdAlignColumns.lt-xl],
+ [gdAlignColumns.gt-xs], [gdAlignColumns.gt-sm], [gdAlignColumns.gt-md],
+ [gdAlignColumns.gt-lg]
+`})
+export class GridAlignColumnsDirective extends BaseDirective
+ implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdAlignColumns') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdAlignColumns.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdAlignColumns.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdAlignColumns.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdAlignColumns.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdAlignColumns.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdAlignColumns.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdAlignColumns.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdAlignColumns.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdAlignColumns.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdAlignColumns.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdAlignColumns.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdAlignColumns.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdAlignColumns.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, `${DEFAULT_MAIN} ${DEFAULT_CROSS}`,
+ (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(align) {
+ let css = {}, [mainAxis, crossAxis] = align.split(' ');
+
+ // Main axis
+ switch (mainAxis) {
+ case 'center':
+ css['align-content'] = 'center';
+ break;
+ case 'space-around':
+ css['align-content'] = 'space-around';
+ break;
+ case 'space-between':
+ css['align-content'] = 'space-between';
+ break;
+ case 'space-evenly':
+ css['align-content'] = 'space-evenly';
+ break;
+ case 'end':
+ css['align-content'] = 'end';
+ break;
+ case 'start':
+ css['align-content'] = 'start';
+ break;
+ case 'stretch':
+ css['align-content'] = 'stretch';
+ break;
+ default:
+ css['align-content'] = DEFAULT_MAIN; // default main axis
+ break;
+ }
+
+ // Cross-axis
+ switch (crossAxis) {
+ case 'start':
+ css['align-items'] = 'start';
+ break;
+ case 'center':
+ css['align-items'] = 'center';
+ break;
+ case 'end':
+ css['align-items'] = 'end';
+ break;
+ case 'stretch':
+ css['align-items'] = 'stretch';
+ break;
+ default : // 'stretch'
+ css['align-items'] = DEFAULT_CROSS; // default cross axis
+ break;
+ }
+
+ return extendObject(css, {'display' : this._queryInput('inline') ? 'inline-grid' : 'grid'});
+ }
+}
diff --git a/src/lib/grid/align-rows/align-rows.spec.ts b/src/lib/grid/align-rows/align-rows.spec.ts
new file mode 100644
index 000000000..89af7cd2d
--- /dev/null
+++ b/src/lib/grid/align-rows/align-rows.spec.ts
@@ -0,0 +1,411 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component, OnInit} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {extendObject} from '../../utils/object-extend';
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+import {Platform} from '@angular/cdk/platform';
+
+describe('align rows directive', () => {
+ let fixture: ComponentFixture;
+ let matchMedia: MockMatchMedia;
+ let styler: StyleUtils;
+ let shouldRun = true;
+ let createTestComponent = (template: string) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestAlignComponent)(template);
+
+ inject([MatchMedia, StyleUtils, Platform],
+ (_matchMedia: MockMatchMedia, _styler: StyleUtils, _platform: Platform) => {
+ matchMedia = _matchMedia;
+ styler = _styler;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestAlignComponent],
+ providers: [MockMatchMediaProvider],
+ });
+ });
+
+ describe('with static features', () => {
+
+ it('should add correct styles for default `gdAlignRows` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+ });
+
+ it('should work with inline grid', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({
+ 'display': 'inline-grid'
+ }, DEFAULT_ALIGNS),
+ styler);
+ });
+
+ describe('for "main-axis" testing', () => {
+ it('should add correct styles for `gdAlignRows="start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'start'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'end'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="stretch"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'stretch'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'center'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="space-around"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'space-around'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="space-between"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'space-between'}, CROSS_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="space-evenly"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-content': 'space-evenly'}, CROSS_DEFAULT), styler
+ );
+ });
+
+ it('should add ignore invalid row-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, CROSS_DEFAULT), styler
+ );
+ });
+ });
+
+ describe('for "cross-axis" testing', () => {
+ it('should add correct styles for `gdAlignRows="start start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'justify-items': 'start'}), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="start center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'justify-items': 'center'}), styler
+ );
+ });
+ it('should add correct styles for `gdAlignRows="start end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, {'justify-items': 'end'}), styler
+ );
+ });
+ it('should add ignore invalid column-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(MAIN_DEFAULT, CROSS_DEFAULT), styler
+ );
+ });
+ });
+
+ describe('for dynamic inputs', () => {
+ it('should add correct styles and ignore invalid axes values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.componentInstance.alignBy = 'center end';
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center',
+ 'justify-items': 'end'
+ }, styler);
+
+ fixture.componentInstance.alignBy = 'invalid invalid';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+
+ fixture.componentInstance.alignBy = '';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+ });
+ });
+
+ });
+
+ describe('with responsive features', () => {
+
+ it('should ignore responsive changes when not configured', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center',
+ 'justify-items': 'center'
+ }, styler);
+ });
+
+ it('should add responsive styles when configured', () => {
+ createTestComponent(`
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center',
+ 'justify-items': 'center'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'end',
+ 'justify-items': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to default styles when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center',
+ 'justify-items': 'stretch'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'end',
+ 'justify-items': 'stretch'
+ }, styler);
+
+ matchMedia.activate('xs');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center',
+ 'justify-items': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to closest overlapping value when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.useOverlaps = true;
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'start'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'center'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('lg', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'end'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('xl', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-content': 'end'
+ }, styler);
+ });
+
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestAlignComponent implements OnInit {
+ mainAxis = 'start';
+ crossAxis = 'end';
+
+ set alignBy(style) {
+ let vals = style.split(' ');
+ this.mainAxis = vals[0];
+ this.crossAxis = vals.length > 1 ? vals[1] : '';
+ }
+
+ get alignBy() {
+ return `${this.mainAxis} ${this.crossAxis}`;
+ }
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+}
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+const DEFAULT_ALIGNS = {
+ 'justify-content': 'start',
+ 'justify-items': 'stretch'
+};
+const MAIN_DEFAULT = {
+ 'justify-content': 'start'
+};
+const CROSS_DEFAULT = {
+ 'justify-items': 'stretch'
+};
+
diff --git a/src/lib/grid/align-rows/align-rows.ts b/src/lib/grid/align-rows/align-rows.ts
new file mode 100644
index 000000000..6b7e339cc
--- /dev/null
+++ b/src/lib/grid/align-rows/align-rows.ts
@@ -0,0 +1,143 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {extendObject} from '../../utils/object-extend';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'alignRows';
+const DEFAULT_MAIN = 'start';
+const DEFAULT_CROSS = 'stretch';
+
+/**
+ * 'row alignment' CSS Grid styling directive
+ * Configures the alignment in the row direction
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-18
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-20
+ */
+@Directive({selector: `
+ [gdAlignRows],
+ [gdAlignRows.xs], [gdAlignRows.sm], [gdAlignRows.md],
+ [gdAlignRows.lg], [gdAlignRows.xl], [gdAlignRows.lt-sm],
+ [gdAlignRows.lt-md], [gdAlignRows.lt-lg], [gdAlignRows.lt-xl],
+ [gdAlignRows.gt-xs], [gdAlignRows.gt-sm], [gdAlignRows.gt-md],
+ [gdAlignRows.gt-lg]
+`})
+export class GridAlignRowsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdAlignRows') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdAlignRows.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdAlignRows.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdAlignRows.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdAlignRows.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdAlignRows.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdAlignRows.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdAlignRows.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdAlignRows.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdAlignRows.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdAlignRows.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdAlignRows.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdAlignRows.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdAlignRows.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, `${DEFAULT_MAIN} ${DEFAULT_CROSS}`,
+ (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(align) {
+ let css = {}, [mainAxis, crossAxis] = align.split(' ');
+
+ // Main axis
+ switch (mainAxis) {
+ case 'center':
+ case 'space-around':
+ case 'space-between':
+ case 'space-evenly':
+ case 'end':
+ case 'start':
+ case 'stretch':
+ css['justify-content'] = mainAxis;
+ break;
+ default:
+ css['justify-content'] = DEFAULT_MAIN; // default main axis
+ break;
+ }
+
+ // Cross-axis
+ switch (crossAxis) {
+ case 'start':
+ case 'center':
+ case 'end':
+ case 'stretch':
+ css['justify-items'] = crossAxis;
+ break;
+ default : // 'stretch'
+ css['justify-items'] = DEFAULT_CROSS; // default cross axis
+ break;
+ }
+
+ return extendObject(css, {'display' : this._queryInput('inline') ? 'inline-grid' : 'grid'});
+ }
+}
diff --git a/src/lib/grid/area/area.spec.ts b/src/lib/grid/area/area.spec.ts
new file mode 100644
index 000000000..039f5225d
--- /dev/null
+++ b/src/lib/grid/area/area.spec.ts
@@ -0,0 +1,220 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {
+ expectEl,
+ expectNativeEl,
+ queryFor,
+ makeCreateTestComponent,
+} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid area child directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridAreaComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridAreaComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add area styles for children', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+
+ let nodes = queryFor(fixture, '[gdArea]');
+ expect(nodes.length).toBe(3);
+ if (platform.WEBKIT) {
+ expectEl(nodes[1]).toHaveStyle({
+ 'grid-row-start': 'grace',
+ 'grid-row-end': 'grace',
+ 'grid-column-start': 'sarah',
+ 'grid-column-end': 'sarah',
+ }, styler);
+ } else {
+ let areaStyles = styler.lookupStyle(nodes[1].nativeElement, 'grid-area');
+ let correctArea = areaStyles === 'grace / sarah' ||
+ areaStyles === 'grace / sarah / grace / sarah';
+ expect(correctArea).toBe(true);
+ }
+ });
+
+ it('should add dynamic area styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-row-start': 'sidebar',
+ 'grid-row-end': 'sidebar',
+ 'grid-column-start': 'sidebar',
+ 'grid-column-end': 'sidebar',
+ }, styler);
+ } else {
+ fixture.detectChanges();
+ let areaStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-area');
+ let correctArea = areaStyles === 'sidebar' ||
+ areaStyles === 'sidebar / sidebar / sidebar / sidebar';
+ expect(correctArea).toBe(true);
+ }
+
+ fixture.componentInstance.area = 'header';
+
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-row-start': 'header',
+ 'grid-row-end': 'header',
+ 'grid-column-start': 'header',
+ 'grid-column-end': 'header',
+ }, styler);
+ } else {
+ fixture.detectChanges();
+ let areaStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-area');
+ let correctArea = areaStyles === 'header' ||
+ areaStyles === 'header / header / header / header';
+ expect(correctArea).toBe(true);
+ }
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add row styles for a child', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-row-start': 'sidebar',
+ 'grid-row-end': 'sidebar',
+ 'grid-column-start': 'sidebar',
+ 'grid-column-end': 'sidebar',
+ }, styler);
+ } else {
+ fixture.detectChanges();
+ let areaStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-area');
+ let correctArea = areaStyles === 'sidebar' ||
+ areaStyles === 'sidebar / sidebar / sidebar / sidebar';
+ expect(correctArea).toBe(true);
+ }
+
+ matchMedia.activate('xs');
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-row-start': 'footer',
+ 'grid-row-end': 'footer',
+ 'grid-column-start': 'footer',
+ 'grid-column-end': 'footer',
+ }, styler);
+ } else {
+ let areaStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-area');
+ let correctArea = areaStyles === 'footer' ||
+ areaStyles === 'footer / footer / footer / footer';
+ expect(correctArea).toBe(true);
+ }
+
+ matchMedia.activate('md');
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-row-start': 'sidebar',
+ 'grid-row-end': 'sidebar',
+ 'grid-column-start': 'sidebar',
+ 'grid-column-end': 'sidebar',
+ }, styler);
+ } else {
+ let areaStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-area');
+ let correctArea = areaStyles === 'sidebar' ||
+ areaStyles === 'sidebar / sidebar / sidebar / sidebar';
+ expect(correctArea).toBe(true);
+ }
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridAreaComponent {
+ area = 'sidebar';
+}
diff --git a/src/lib/grid/area/area.ts b/src/lib/grid/area/area.ts
new file mode 100644
index 000000000..b207e7376
--- /dev/null
+++ b/src/lib/grid/area/area.ts
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+
+const CACHE_KEY = 'area';
+const DEFAULT_VALUE = 'auto';
+
+/**
+ * 'grid-area' CSS Grid styling directive
+ * Configures the name or position of an element within the grid
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-27
+ */
+@Directive({selector: `
+ [gdArea],
+ [gdArea.xs], [gdArea.sm], [gdArea.md], [gdArea.lg], [gdArea.xl],
+ [gdArea.lt-sm], [gdArea.lt-md], [gdArea.lt-lg], [gdArea.lt-xl],
+ [gdArea.gt-xs], [gdArea.gt-sm], [gdArea.gt-md], [gdArea.gt-lg]
+`})
+export class GridAreaDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdArea') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdArea.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdArea.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdArea.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdArea.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdArea.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdArea.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdArea.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdArea.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdArea.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdArea.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdArea.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdArea.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdArea.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ return {'grid-area': value};
+ }
+}
diff --git a/src/lib/grid/areas/areas.spec.ts b/src/lib/grid/areas/areas.spec.ts
new file mode 100644
index 000000000..e7b0714cb
--- /dev/null
+++ b/src/lib/grid/areas/areas.spec.ts
@@ -0,0 +1,220 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {expectNativeEl, makeCreateTestComponent} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid area parent directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridAreaComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridAreaComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add area styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Edge currently has a bug with template areas
+ // when they don't have columns/rows explicitly set. Remove when fixed
+ if (platform.EDGE) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas': '"header" "header" "sidebar" "footer"'
+ }, styler);
+ });
+
+ it('should work with inline grid', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Edge currently has a bug with template areas
+ // when they don't have columns/rows explicitly set. Remove when fixed
+ if (platform.EDGE) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'inline-grid',
+ 'grid-template-areas': '"header" "header" "sidebar" "footer"'
+ }, styler);
+ });
+
+ it('should work with weird spacing', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Edge currently has a bug with template areas
+ // when they don't have columns/rows explicitly set. Remove when fixed
+ if (platform.EDGE) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas': '"header" "header" "sidebar" "footer"'
+ }, styler);
+ });
+
+ it('should add dynamic area styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Edge currently has a bug with template areas
+ // when they don't have columns/rows explicitly set. Remove when fixed
+ if (platform.EDGE) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'grid-template-areas': '"sidebar" "sidebar"'
+ }, styler);
+
+ fixture.componentInstance.areas = 'header | header | sidebar';
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas': '"header" "header" "sidebar"'
+ }, styler);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add row styles for a child', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Edge currently has a bug with template areas
+ // when they don't have columns/rows explicitly set. Remove when fixed
+ if (platform.EDGE) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas':
+ '"header header header" "sidebar content content" "footer footer footer"'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas': '"header header" "sidebar content" "footer footer"'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-areas':
+ '"header header header" "sidebar content content" "footer footer footer"'
+ }, styler);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridAreaComponent {
+ areas = 'sidebar | sidebar';
+}
diff --git a/src/lib/grid/areas/areas.ts b/src/lib/grid/areas/areas.ts
new file mode 100644
index 000000000..f2343a34b
--- /dev/null
+++ b/src/lib/grid/areas/areas.ts
@@ -0,0 +1,112 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'areas';
+const DEFAULT_VALUE = 'none';
+const DELIMETER = '|';
+
+/**
+ * 'grid-template-areas' CSS Grid styling directive
+ * Configures the names of elements within the grid
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-14
+ */
+@Directive({selector: `
+ [gdAreas],
+ [gdAreas.xs], [gdAreas.sm], [gdAreas.md], [gdAreas.lg], [gdAreas.xl],
+ [gdAreas.lt-sm], [gdAreas.lt-md], [gdAreas.lt-lg], [gdAreas.lt-xl],
+ [gdAreas.gt-xs], [gdAreas.gt-sm], [gdAreas.gt-md], [gdAreas.gt-lg]
+`})
+export class GridAreasDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdAreas') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdAreas.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdAreas.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdAreas.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdAreas.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdAreas.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdAreas.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdAreas.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdAreas.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdAreas.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdAreas.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdAreas.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdAreas.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdAreas.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ const areas = value.split(DELIMETER).map(v => `"${v.trim()}"`);
+
+ return {
+ 'display': this._queryInput('inline') ? 'inline-grid' : 'grid',
+ 'grid-template-areas': areas.join(' ')
+ };
+ }
+}
diff --git a/src/lib/grid/auto/auto.spec.ts b/src/lib/grid/auto/auto.spec.ts
new file mode 100644
index 000000000..a73e80070
--- /dev/null
+++ b/src/lib/grid/auto/auto.spec.ts
@@ -0,0 +1,329 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {expectNativeEl, makeCreateTestComponent} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid auto parent directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridAutoComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridAutoComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add auto styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+ });
+
+ it('should work with inline grid', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'inline-grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+ });
+
+ it('should work with row values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+ });
+
+ it('should work with column values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'column'
+ }, styler);
+ });
+
+ it('should work with dense values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': (platform.FIREFOX || platform.EDGE) ? 'row dense' : 'dense'
+ }, styler);
+ });
+
+ it('should filter double dense values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': (platform.FIREFOX || platform.EDGE) ? 'row dense' : 'dense'
+ }, styler);
+ });
+
+ it('should work with column dense values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'column dense'
+ }, styler);
+ });
+
+ it('should work with row dense values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row dense'
+ }, styler);
+ });
+
+ it('should work with invalid direction values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row dense'
+ }, styler);
+ });
+
+ it('should work with invalid dense values', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'column'
+ }, styler);
+ });
+
+ it('should add dynamic area styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+
+ fixture.componentInstance.auto = 'column';
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'column'
+ }, styler);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add row styles for a child', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'column'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-flow': 'row'
+ }, styler);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridAutoComponent {
+ auto = 'row';
+}
diff --git a/src/lib/grid/auto/auto.ts b/src/lib/grid/auto/auto.ts
new file mode 100644
index 000000000..3eaf81540
--- /dev/null
+++ b/src/lib/grid/auto/auto.ts
@@ -0,0 +1,116 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'autoFlow';
+const DEFAULT_VALUE = 'initial';
+
+/**
+ * 'grid-auto-flow' CSS Grid styling directive
+ * Configures the auto placement algorithm for the grid
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-23
+ */
+@Directive({selector: `
+ [gdAuto],
+ [gdAuto.xs], [gdAuto.sm], [gdAuto.md], [gdAuto.lg], [gdAuto.xl],
+ [gdAuto.lt-sm], [gdAuto.lt-md], [gdAuto.lt-lg], [gdAuto.lt-xl],
+ [gdAuto.gt-xs], [gdAuto.gt-sm], [gdAuto.gt-md], [gdAuto.gt-lg]
+`})
+export class GridAutoDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdAuto') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdAuto.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdAuto.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdAuto.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdAuto.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdAuto.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdAuto.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdAuto.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdAuto.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdAuto.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdAuto.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdAuto.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdAuto.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdAuto.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ let [direction, dense] = value.split(' ');
+ if (direction !== 'column' && direction !== 'row' && direction !== 'dense') {
+ direction = 'row';
+ }
+
+ dense = (dense === 'dense' && direction !== 'dense') ? ' dense' : '';
+
+ return {
+ 'display': this._queryInput('inline') ? 'inline-grid' : 'grid',
+ 'grid-auto-flow': direction + dense
+ };
+ }
+}
diff --git a/src/lib/grid/column/column.spec.ts b/src/lib/grid/column/column.spec.ts
new file mode 100644
index 000000000..6bbddc0f3
--- /dev/null
+++ b/src/lib/grid/column/column.spec.ts
@@ -0,0 +1,170 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {
+ expectEl,
+ queryFor,
+ makeCreateTestComponent,
+} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid column child directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridColumnComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridColumnComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add column styles for children', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+
+ let nodes = queryFor(fixture, '[gdColumn]');
+ expect(nodes.length).toBe(3);
+
+ if (platform.WEBKIT) {
+ expectEl(nodes[1]).toHaveStyle({
+ 'grid-column-start': 'span 2',
+ 'grid-column-end': '6',
+ }, styler);
+ } else {
+ expectEl(nodes[1]).toHaveStyle({'grid-column': 'span 2 / 6'}, styler);
+ }
+ });
+
+ it('should add dynamic column styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+
+ let colStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-column');
+ let correctCol = colStyles === 'apples' || colStyles === 'apples / apples' ||
+ colStyles === 'apples apples';
+
+ expect(correctCol).toBe(true);
+
+ fixture.componentInstance.col = 'oranges';
+ fixture.detectChanges();
+
+ colStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement, 'grid-column');
+ correctCol = colStyles === 'oranges' || colStyles === 'oranges / oranges' ||
+ colStyles === 'oranges oranges';
+ expect(correctCol).toBe(true);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add row styles for a child', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+ let colStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-column');
+ let correctCol = colStyles === 'sidebar' || colStyles === 'sidebar / sidebar' ||
+ colStyles === 'sidebar sidebar';
+ expect(correctCol).toBe(true);
+
+ matchMedia.activate('xs');
+ colStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-column');
+ correctCol = colStyles === 'footer' || colStyles === 'footer / footer' ||
+ colStyles === 'footer footer';
+ expect(correctCol).toBe(true);
+
+ matchMedia.activate('md');
+ colStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-column');
+ correctCol = colStyles === 'sidebar' || colStyles === 'sidebar / sidebar' ||
+ colStyles === 'sidebar sidebar';
+ expect(correctCol).toBe(true);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridColumnComponent {
+ col = 'apples';
+}
diff --git a/src/lib/grid/column/column.ts b/src/lib/grid/column/column.ts
new file mode 100644
index 000000000..14b09f9c8
--- /dev/null
+++ b/src/lib/grid/column/column.ts
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+
+const CACHE_KEY = 'column';
+const DEFAULT_VALUE = 'auto';
+
+/**
+ * 'grid-column' CSS Grid styling directive
+ * Configures the name or position of an element within the grid
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26
+ */
+@Directive({selector: `
+ [gdColumn],
+ [gdColumn.xs], [gdColumn.sm], [gdColumn.md], [gdColumn.lg], [gdColumn.xl],
+ [gdColumn.lt-sm], [gdColumn.lt-md], [gdColumn.lt-lg], [gdColumn.lt-xl],
+ [gdColumn.gt-xs], [gdColumn.gt-sm], [gdColumn.gt-md], [gdColumn.gt-lg]
+`})
+export class GridColumnDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdColumn') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdColumn.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdColumn.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdColumn.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdColumn.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdColumn.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdColumn.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdColumn.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdColumn.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdColumn.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdColumn.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdColumn.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdColumn.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdColumn.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ return {'grid-column': value};
+ }
+}
diff --git a/src/lib/grid/columns/columns.spec.ts b/src/lib/grid/columns/columns.spec.ts
new file mode 100644
index 000000000..bbd894267
--- /dev/null
+++ b/src/lib/grid/columns/columns.spec.ts
@@ -0,0 +1,193 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {expectNativeEl, makeCreateTestComponent} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid columns parent directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridColumnsComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridColumnsComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add column styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '100px 1fr'
+ }, styler);
+ });
+
+ it('should add auto column styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Firefox has an issue with auto tracks,
+ // caused by rachelandrew/gridbugs#1
+ if (!platform.FIREFOX) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-columns': '100px 1fr auto'
+ }, styler);
+ }
+ });
+
+ it('should work with inline grid', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'inline-grid',
+ 'grid-template-columns': '100px 1fr'
+ }, styler);
+ });
+
+ it('should add dynamic columns styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '50px 1fr'
+ }, styler);
+
+ fixture.componentInstance.cols = '100px 1fr';
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '100px 1fr'
+ }, styler);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add col styles for a parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '100px 1fr'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '50px 1fr'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-columns': '100px 1fr'
+ }, styler);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridColumnsComponent {
+ cols = '50px 1fr';
+}
diff --git a/src/lib/grid/columns/columns.ts b/src/lib/grid/columns/columns.ts
new file mode 100644
index 000000000..2166e81d7
--- /dev/null
+++ b/src/lib/grid/columns/columns.ts
@@ -0,0 +1,122 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'columns';
+const DEFAULT_VALUE = 'none';
+const AUTO_SPECIFIER = '!';
+
+/**
+ * 'grid-template-columns' CSS Grid styling directive
+ * Configures the sizing for the columns in the grid
+ * Syntax: [auto]
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13
+ */
+@Directive({selector: `
+ [gdColumns],
+ [gdColumns.xs], [gdColumns.sm], [gdColumns.md], [gdColumns.lg], [gdColumns.xl],
+ [gdColumns.lt-sm], [gdColumns.lt-md], [gdColumns.lt-lg], [gdColumns.lt-xl],
+ [gdColumns.gt-xs], [gdColumns.gt-sm], [gdColumns.gt-md], [gdColumns.gt-lg]
+`})
+export class GridColumnsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdColumns') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdColumns.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdColumns.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdColumns.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdColumns.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdColumns.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdColumns.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdColumns.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdColumns.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdColumns.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdColumns.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdColumns.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdColumns.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdColumns.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ let auto = false;
+ if (value.endsWith(AUTO_SPECIFIER)) {
+ value = value.substring(0, value.indexOf(AUTO_SPECIFIER));
+ auto = true;
+ }
+
+ let css = {
+ 'display': this._queryInput('inline') ? 'inline-grid' : 'grid',
+ 'grid-auto-columns': '',
+ 'grid-template-columns': '',
+ };
+ const key = (auto ? 'grid-auto-columns' : 'grid-template-columns');
+ css[key] = value;
+
+ return css;
+ }
+}
diff --git a/src/lib/grid/gap/gap.spec.ts b/src/lib/grid/gap/gap.spec.ts
new file mode 100644
index 000000000..de9d1b216
--- /dev/null
+++ b/src/lib/grid/gap/gap.spec.ts
@@ -0,0 +1,259 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {expectNativeEl, makeCreateTestComponent} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid gap directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestLayoutGapComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestLayoutGapComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add gap styles for a parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '10px',
+ 'grid-column-gap': '10px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({'display': 'grid'}, styler);
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '10px' || gapStyle == '10px 10px';
+ expect(correctGap).toBe(true);
+ }
+ });
+
+ it('should add gap styles with multiple values for a parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '10px',
+ 'grid-column-gap': '15px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-gap': '10px 15px',
+ }, styler);
+ }
+ });
+
+ it('should add dynamic gap styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '8px',
+ 'grid-column-gap': '8px',
+ }, styler);
+ } else {
+ fixture.detectChanges();
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '8px' || gapStyle == '8px 8px';
+ expect(correctGap).toBe(true);
+ }
+
+ fixture.componentInstance.gap = '16px';
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '16px',
+ 'grid-column-gap': '16px',
+ }, styler);
+ } else {
+ fixture.detectChanges();
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '16px' || gapStyle == '16px 16px';
+ expect(correctGap).toBe(true);
+ }
+ });
+
+ it('should add inline grid css style', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'inline-grid',
+ 'grid-row-gap': '10px',
+ 'grid-column-gap': '10px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({'display': 'inline-grid'}, styler);
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '10px' || gapStyle == '10px 10px';
+ expect(correctGap).toBe(true);
+ }
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add gap styles for a parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '10px',
+ 'grid-column-gap': '10px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({'display': 'grid'}, styler);
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '10px' || gapStyle == '10px 10px';
+ expect(correctGap).toBe(true);
+ }
+
+ matchMedia.activate('xs');
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '16px',
+ 'grid-column-gap': '16px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({'display': 'grid'}, styler);
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '16px' || gapStyle == '16px 16px';
+ expect(correctGap).toBe(true);
+ }
+
+ matchMedia.activate('md');
+ if (platform.WEBKIT) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-row-gap': '10px',
+ 'grid-column-gap': '10px',
+ }, styler);
+ } else {
+ expectNativeEl(fixture).toHaveStyle({'display': 'grid'}, styler);
+ let gapStyle = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-gap');
+ let correctGap = gapStyle === '10px' || gapStyle == '10px 10px';
+ expect(correctGap).toBe(true);
+ }
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestLayoutGapComponent {
+ gap = '8px';
+}
diff --git a/src/lib/grid/gap/gap.ts b/src/lib/grid/gap/gap.ts
new file mode 100644
index 000000000..de163550c
--- /dev/null
+++ b/src/lib/grid/gap/gap.ts
@@ -0,0 +1,110 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'gap';
+const DEFAULT_VALUE = '0';
+
+/**
+ * 'grid-gap' CSS Grid styling directive
+ * Configures the gap between items in the grid
+ * Syntax: []
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-17
+ */
+@Directive({selector: `
+ [gdGap],
+ [gdGap.xs], [gdGap.sm], [gdGap.md], [gdGap.lg], [gdGap.xl],
+ [gdGap.lt-sm], [gdGap.lt-md], [gdGap.lt-lg], [gdGap.lt-xl],
+ [gdGap.gt-xs], [gdGap.gt-sm], [gdGap.gt-md], [gdGap.gt-lg]
+`})
+export class GridGapDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdGap') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdGap.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdGap.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdGap.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdGap.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdGap.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdGap.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdGap.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdGap.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdGap.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdGap.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdGap.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdGap.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdGap.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ return {
+ 'display': this._queryInput('inline') ? 'inline-grid' : 'grid',
+ 'grid-gap': value
+ };
+ }
+}
diff --git a/src/lib/grid/grid-align/grid-align.spec.ts b/src/lib/grid/grid-align/grid-align.spec.ts
new file mode 100644
index 000000000..db7c08324
--- /dev/null
+++ b/src/lib/grid/grid-align/grid-align.spec.ts
@@ -0,0 +1,366 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component, OnInit} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {FlexLayoutModule} from '../../module';
+import {extendObject} from '../../utils/object-extend';
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers';
+
+describe('align directive', () => {
+ let fixture: ComponentFixture;
+ let matchMedia: MockMatchMedia;
+ let styler: StyleUtils;
+ let shouldRun = true;
+ let createTestComponent = (template: string) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestAlignComponent)(template);
+
+ inject([MatchMedia, StyleUtils, Platform],
+ (_matchMedia: MockMatchMedia, _styler: StyleUtils, _platform: Platform) => {
+ matchMedia = _matchMedia;
+ styler = _styler;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FlexLayoutModule],
+ declarations: [TestAlignComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true}
+ ]
+ });
+ });
+
+ describe('with static features', () => {
+
+ it('should add correct styles for default `fxLayoutAlign` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({'justify-self': 'stretch'}, styler);
+ });
+
+ describe('for "main-axis" testing', () => {
+ it('should add correct styles for `gdGridAlign="start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-self': 'start'}, COLUMN_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdGridAlign="center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-self': 'center'}, COLUMN_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdGridAlign="end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-self': 'end'}, COLUMN_DEFAULT), styler
+ );
+ });
+ it('should add correct styles for `gdGridAlign="stretch"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-self': 'stretch'}, COLUMN_DEFAULT), styler
+ );
+ });
+ it('should add ignore invalid row-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject({'justify-self': 'stretch'}, COLUMN_DEFAULT), styler
+ );
+ });
+ });
+
+ describe('for "column-axis" testing', () => {
+ it('should add correct styles for `gdGridAlign="start start"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(ROW_DEFAULT, {'align-self': 'start'}), styler
+ );
+ });
+ it('should add correct styles for `gdGridAlign="start center"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(ROW_DEFAULT, {'align-self': 'center'}), styler
+ );
+ });
+ it('should add correct styles for `gdGridAlign="start end"` usage', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(ROW_DEFAULT, {'align-self': 'end'}), styler
+ );
+ });
+ it('should add ignore invalid column-axis values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle(
+ extendObject(ROW_DEFAULT, {'align-self': 'stretch'}), styler
+ );
+ });
+ });
+
+ describe('for dynamic inputs', () => {
+ it('should add correct styles and ignore invalid axes values', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.componentInstance.alignBy = 'center end';
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center',
+ 'align-self': 'end'
+ }, styler);
+
+ fixture.componentInstance.alignBy = 'invalid invalid';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+
+ fixture.componentInstance.alignBy = '';
+ expectNativeEl(fixture).toHaveStyle(DEFAULT_ALIGNS, styler);
+ });
+ });
+
+ });
+
+ describe('with responsive features', () => {
+
+ it('should ignore responsive changes when not configured', () => {
+ createTestComponent(`
`);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center',
+ 'align-self': 'center'
+ }, styler);
+ });
+
+ it('should add responsive styles when configured', () => {
+ createTestComponent(`
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center',
+ 'align-self': 'center'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'end',
+ 'align-self': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to default styles when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center',
+ 'align-self': 'stretch'
+ }, styler);
+
+ matchMedia.activate('md');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'end',
+ 'align-self': 'stretch'
+ }, styler);
+
+ matchMedia.activate('xs');
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center',
+ 'align-self': 'stretch'
+ }, styler);
+ });
+
+ it('should fallback to closest overlapping value when the active mediaQuery change is not configured', () => { // tslint:disable-line:max-line-length
+ createTestComponent(`
+
+
+ `);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ matchMedia.useOverlaps = true;
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'start'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'center'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('lg', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'end'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'start'
+ }, styler);
+
+ // Should fallback to value for 'gt-xs' or default
+ matchMedia.activate('xl', true);
+ expectNativeEl(fixture).toHaveStyle({
+ 'justify-self': 'end'
+ }, styler);
+ });
+
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestAlignComponent implements OnInit {
+ mainAxis = 'start';
+ crossAxis = 'end';
+
+ set alignBy(style) {
+ let vals = style.split(' ');
+ this.mainAxis = vals[0];
+ this.crossAxis = vals.length > 1 ? vals[1] : '';
+ }
+
+ get alignBy() {
+ return `${this.mainAxis} ${this.crossAxis}`;
+ }
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+}
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+
+const DEFAULT_ALIGNS = {
+ 'justify-self': 'stretch',
+ 'align-self': 'stretch'
+};
+const ROW_DEFAULT = {
+ 'justify-self': 'stretch'
+};
+const COLUMN_DEFAULT = {
+ 'align-self': 'stretch'
+};
+
diff --git a/src/lib/grid/grid-align/grid-align.ts b/src/lib/grid/grid-align/grid-align.ts
new file mode 100644
index 000000000..03d37923f
--- /dev/null
+++ b/src/lib/grid/grid-align/grid-align.ts
@@ -0,0 +1,146 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+
+const CACHE_KEY = 'align';
+const ROW_DEFAULT = 'stretch';
+const COL_DEFAULT = 'stretch';
+
+/**
+ * 'align' CSS Grid styling directive for grid children
+ * Defines positioning of child elements along row and column axis in a grid container
+ * Optional values: {row-axis} values or {row-axis column-axis} value pairs
+ *
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-justify-self
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-align-self
+ */
+@Directive({selector: `
+ [gdGridAlign],
+ [gdGridAlign.xs], [gdGridAlign.sm], [gdGridAlign.md], [gdGridAlign.lg],[gdGridAlign.xl],
+ [gdGridAlign.lt-sm], [gdGridAlign.lt-md], [gdGridAlign.lt-lg], [gdGridAlign.lt-xl],
+ [gdGridAlign.gt-xs], [gdGridAlign.gt-sm], [gdGridAlign.gt-md], [gdGridAlign.gt-lg]
+`})
+export class GridAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdGridAlign') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdGridAlign.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdGridAlign.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdGridAlign.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdGridAlign.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdGridAlign.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdGridAlign.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdGridAlign.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdGridAlign.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdGridAlign.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdGridAlign.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdGridAlign.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdGridAlign.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdGridAlign.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, ROW_DEFAULT, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ /**
+ *
+ */
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || ROW_DEFAULT;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+ protected _buildCSS(align) {
+ let css = {}, [rowAxis, columnAxis] = align.split(' ');
+
+ // Row axis
+ switch (rowAxis) {
+ case 'end':
+ css['justify-self'] = 'end';
+ break;
+ case 'center':
+ css['justify-self'] = 'center';
+ break;
+ case 'stretch':
+ css['justify-self'] = 'stretch';
+ break;
+ case 'start':
+ css['justify-self'] = 'start';
+ break;
+ default:
+ css['justify-self'] = ROW_DEFAULT; // default row axis
+ break;
+ }
+
+ // Column axis
+ switch (columnAxis) {
+ case 'end':
+ css['align-self'] = 'end';
+ break;
+ case 'center':
+ css['align-self'] = 'center';
+ break;
+ case 'stretch':
+ css['align-self'] = 'stretch';
+ break;
+ case 'start':
+ css['align-self'] = 'start';
+ break;
+ default:
+ css['align-self'] = COL_DEFAULT; // default column axis
+ break;
+ }
+
+ return css;
+ }
+}
diff --git a/src/lib/grid/index.ts b/src/lib/grid/index.ts
new file mode 100644
index 000000000..676ca90f1
--- /dev/null
+++ b/src/lib/grid/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './public-api';
diff --git a/src/lib/grid/module.ts b/src/lib/grid/module.ts
new file mode 100644
index 000000000..6bcf949e5
--- /dev/null
+++ b/src/lib/grid/module.ts
@@ -0,0 +1,50 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {NgModule} from '@angular/core';
+import {CoreModule} from '@angular/flex-layout/core';
+
+import {GridAlignDirective} from './grid-align/grid-align';
+import {GridAlignColumnsDirective} from './align-columns/align-columns';
+import {GridAlignRowsDirective} from './align-rows/align-rows';
+import {GridAreaDirective} from './area/area';
+import {GridAreasDirective} from './areas/areas';
+import {GridAutoDirective} from './auto/auto';
+import {GridColumnDirective} from './column/column';
+import {GridColumnsDirective} from './columns/columns';
+import {GridGapDirective} from './gap/gap';
+import {GridRowDirective} from './row/row';
+import {GridRowsDirective} from './rows/rows';
+
+
+const ALL_DIRECTIVES = [
+ GridAlignDirective,
+ GridAlignColumnsDirective,
+ GridAlignRowsDirective,
+ GridAreaDirective,
+ GridAreasDirective,
+ GridAutoDirective,
+ GridColumnDirective,
+ GridColumnsDirective,
+ GridGapDirective,
+ GridRowDirective,
+ GridRowsDirective,
+];
+
+/**
+ * *****************************************************************
+ * Define module for the CSS Grid API
+ * *****************************************************************
+ */
+
+@NgModule({
+ imports: [CoreModule],
+ declarations: [...ALL_DIRECTIVES],
+ exports: [...ALL_DIRECTIVES]
+})
+export class GridModule {
+}
diff --git a/src/lib/grid/public-api.ts b/src/lib/grid/public-api.ts
new file mode 100644
index 000000000..dad40e4ae
--- /dev/null
+++ b/src/lib/grid/public-api.ts
@@ -0,0 +1,11 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './module';
+
+
diff --git a/src/lib/grid/row/row.spec.ts b/src/lib/grid/row/row.spec.ts
new file mode 100644
index 000000000..d0fc0442d
--- /dev/null
+++ b/src/lib/grid/row/row.spec.ts
@@ -0,0 +1,169 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {
+ expectEl,
+ queryFor,
+ makeCreateTestComponent,
+} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid row child directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridRowComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridRowComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add row styles for children', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+
+ let nodes = queryFor(fixture, '[gdRow]');
+ expect(nodes.length).toBe(3);
+ if (platform.WEBKIT) {
+ expectEl(nodes[1]).toHaveStyle({
+ 'grid-row-start': 'span 2',
+ 'grid-row-end': '6',
+ }, styler);
+ } else {
+ expectEl(nodes[1]).toHaveStyle({'grid-row': 'span 2 / 6'}, styler);
+ }
+ });
+
+ it('should add dynamic row styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+
+ let rowStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-row');
+ let correctRow = rowStyles === 'apples' || rowStyles === 'apples / apples' ||
+ rowStyles === 'apples apples';
+
+ expect(correctRow).toBe(true);
+
+ fixture.componentInstance.row = 'oranges';
+ fixture.detectChanges();
+
+ rowStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement, 'grid-row');
+ correctRow = rowStyles === 'oranges' || rowStyles === 'oranges / oranges' ||
+ rowStyles === 'oranges oranges';
+ expect(correctRow).toBe(true);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add row styles for a child', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ fixture.detectChanges();
+ let rowStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-row');
+ let correctRow = rowStyles === 'sidebar' || rowStyles === 'sidebar / sidebar' ||
+ rowStyles === 'sidebar sidebar';
+ expect(correctRow).toBe(true);
+
+ matchMedia.activate('xs');
+ rowStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-row');
+ correctRow = rowStyles === 'footer' || rowStyles === 'footer / footer' ||
+ rowStyles === 'footer footer';
+ expect(correctRow).toBe(true);
+
+ matchMedia.activate('md');
+ rowStyles = styler.lookupStyle(fixture.debugElement.children[0].nativeElement,
+ 'grid-row');
+ correctRow = rowStyles === 'sidebar' || rowStyles === 'sidebar / sidebar' ||
+ rowStyles === 'sidebar sidebar';
+ expect(correctRow).toBe(true);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridRowComponent {
+ row = 'apples';
+}
diff --git a/src/lib/grid/row/row.ts b/src/lib/grid/row/row.ts
new file mode 100644
index 000000000..18761562f
--- /dev/null
+++ b/src/lib/grid/row/row.ts
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+
+const CACHE_KEY = 'row';
+const DEFAULT_VALUE = 'auto';
+
+/**
+ * 'grid-row' CSS Grid styling directive
+ * Configures the name or position of an element within the grid
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26
+ */
+@Directive({selector: `
+ [gdRow],
+ [gdRow.xs], [gdRow.sm], [gdRow.md], [gdRow.lg], [gdRow.xl],
+ [gdRow.lt-sm], [gdRow.lt-md], [gdRow.lt-lg], [gdRow.lt-xl],
+ [gdRow.gt-xs], [gdRow.gt-sm], [gdRow.gt-md], [gdRow.gt-lg]
+`})
+export class GridRowDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdRow') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdRow.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdRow.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdRow.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdRow.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdRow.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdRow.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdRow.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdRow.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdRow.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdRow.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdRow.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdRow.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdRow.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ return {'grid-row': value};
+ }
+}
diff --git a/src/lib/grid/rows/rows.spec.ts b/src/lib/grid/rows/rows.spec.ts
new file mode 100644
index 000000000..7894754f9
--- /dev/null
+++ b/src/lib/grid/rows/rows.spec.ts
@@ -0,0 +1,193 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {Component} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
+import {Platform} from '@angular/cdk/platform';
+import {
+ MatchMedia,
+ MockMatchMedia,
+ MockMatchMediaProvider,
+ SERVER_TOKEN,
+ StyleUtils,
+} from '@angular/flex-layout/core';
+
+import {customMatchers} from '../../utils/testing/custom-matchers';
+import {expectNativeEl, makeCreateTestComponent} from '../../utils/testing/helpers';
+
+import {GridModule} from '../module';
+
+describe('grid rows parent directive', () => {
+ let fixture: ComponentFixture;
+ let styler: StyleUtils;
+ let matchMedia: MockMatchMedia;
+ let platform: Platform;
+ let shouldRun = true;
+ let createTestComponent = (template: string, styles?: any) => {
+ shouldRun = true;
+ fixture = makeCreateTestComponent(() => TestGridRowsComponent)(template, styles);
+ inject([StyleUtils, MatchMedia, Platform],
+ (_styler: StyleUtils, _matchMedia: MockMatchMedia, _platform: Platform) => {
+ styler = _styler;
+ matchMedia = _matchMedia;
+ platform = _platform;
+
+ // TODO(CaerusKaru): Grid tests won't work with Edge 14
+ if (_platform.EDGE) {
+ shouldRun = false;
+ }
+ })();
+ };
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+
+ // Configure testbed to prepare services
+ TestBed.configureTestingModule({
+ imports: [CommonModule, GridModule],
+ declarations: [TestGridRowsComponent],
+ providers: [
+ MockMatchMediaProvider,
+ {provide: SERVER_TOKEN, useValue: true},
+ ],
+ });
+ });
+
+ describe('with static features', () => {
+ it('should add row styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '100px 1fr'
+ }, styler);
+ });
+
+ it('should add auto row styles for parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ // TODO(CaerusKaru): Firefox has an issue with auto tracks,
+ // caused by rachelandrew/gridbugs#1
+ if (!platform.FIREFOX) {
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-auto-rows': '100px 1fr auto'
+ }, styler);
+ }
+ });
+
+ it('should work with inline grid', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'inline-grid',
+ 'grid-template-rows': '100px 1fr'
+ }, styler);
+ });
+
+ it('should add dynamic rows styles', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '50px 1fr'
+ }, styler);
+
+ fixture.componentInstance.cols = '100px 1fr';
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '100px 1fr'
+ }, styler);
+ });
+ });
+
+ describe('with responsive features', () => {
+ it('should add col styles for a parent', () => {
+ let template = `
+
+ `;
+ createTestComponent(template);
+
+ if (!shouldRun) {
+ return;
+ }
+
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '100px 1fr'
+ }, styler);
+
+ matchMedia.activate('xs');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '50px 1fr'
+ }, styler);
+
+ matchMedia.activate('md');
+ expectNativeEl(fixture).toHaveStyle({
+ 'display': 'grid',
+ 'grid-template-rows': '100px 1fr'
+ }, styler);
+ });
+ });
+
+});
+
+
+// *****************************************************************
+// Template Component
+// *****************************************************************
+@Component({
+ selector: 'test-layout',
+ template: `PlaceHolder Template HTML `
+})
+class TestGridRowsComponent {
+ cols = '50px 1fr';
+}
diff --git a/src/lib/grid/rows/rows.ts b/src/lib/grid/rows/rows.ts
new file mode 100644
index 000000000..1f636175d
--- /dev/null
+++ b/src/lib/grid/rows/rows.ts
@@ -0,0 +1,122 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges,
+} from '@angular/core';
+import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+const CACHE_KEY = 'rows';
+const DEFAULT_VALUE = 'none';
+const AUTO_SPECIFIER = '!';
+
+/**
+ * 'grid-template-rows' CSS Grid styling directive
+ * Configures the sizing for the rows in the grid
+ * Syntax: [auto]
+ * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13
+ */
+@Directive({selector: `
+ [gdRows],
+ [gdRows.xs], [gdRows.sm], [gdRows.md], [gdRows.lg], [gdRows.xl],
+ [gdRows.lt-sm], [gdRows.lt-md], [gdRows.lt-lg], [gdRows.lt-xl],
+ [gdRows.gt-xs], [gdRows.gt-sm], [gdRows.gt-md], [gdRows.gt-lg]
+`})
+export class GridRowsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy {
+
+ /* tslint:disable */
+ @Input('gdRows') set align(val) { this._cacheInput(`${CACHE_KEY}`, val); }
+ @Input('gdRows.xs') set alignXs(val) { this._cacheInput(`${CACHE_KEY}Xs`, val); }
+ @Input('gdRows.sm') set alignSm(val) { this._cacheInput(`${CACHE_KEY}Sm`, val); };
+ @Input('gdRows.md') set alignMd(val) { this._cacheInput(`${CACHE_KEY}Md`, val); };
+ @Input('gdRows.lg') set alignLg(val) { this._cacheInput(`${CACHE_KEY}Lg`, val); };
+ @Input('gdRows.xl') set alignXl(val) { this._cacheInput(`${CACHE_KEY}Xl`, val); };
+
+ @Input('gdRows.gt-xs') set alignGtXs(val) { this._cacheInput(`${CACHE_KEY}GtXs`, val); };
+ @Input('gdRows.gt-sm') set alignGtSm(val) { this._cacheInput(`${CACHE_KEY}GtSm`, val); };
+ @Input('gdRows.gt-md') set alignGtMd(val) { this._cacheInput(`${CACHE_KEY}GtMd`, val); };
+ @Input('gdRows.gt-lg') set alignGtLg(val) { this._cacheInput(`${CACHE_KEY}GtLg`, val); };
+
+ @Input('gdRows.lt-sm') set alignLtSm(val) { this._cacheInput(`${CACHE_KEY}LtSm`, val); };
+ @Input('gdRows.lt-md') set alignLtMd(val) { this._cacheInput(`${CACHE_KEY}LtMd`, val); };
+ @Input('gdRows.lt-lg') set alignLtLg(val) { this._cacheInput(`${CACHE_KEY}LtLg`, val); };
+ @Input('gdRows.lt-xl') set alignLtXl(val) { this._cacheInput(`${CACHE_KEY}LtXl`, val); };
+
+ @Input('gdInline') set inline(val) { this._cacheInput('inline', coerceBooleanProperty(val)); };
+
+ /* tslint:enable */
+ constructor(monitor: MediaMonitor,
+ elRef: ElementRef,
+ styleUtils: StyleUtils) {
+ super(monitor, elRef, styleUtils);
+ }
+
+ // *********************************************
+ // Lifecycle Methods
+ // *********************************************
+
+ /**
+ * For @Input changes on the current mq activation property, see onMediaQueryChanges()
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes[CACHE_KEY] != null || this._mqActivation) {
+ this._updateWithValue();
+ }
+ }
+
+ /**
+ * After the initial onChanges, build an mqActivation object that bridges
+ * mql change events to onMediaQueryChange handlers
+ */
+ ngOnInit() {
+ super.ngOnInit();
+
+ this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => {
+ this._updateWithValue(changes.value);
+ });
+ this._updateWithValue();
+ }
+
+ // *********************************************
+ // Protected methods
+ // *********************************************
+
+ protected _updateWithValue(value?: string) {
+ value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE;
+ if (this._mqActivation) {
+ value = this._mqActivation.activatedInput;
+ }
+
+ this._applyStyleToElement(this._buildCSS(value));
+ }
+
+
+ protected _buildCSS(value) {
+ let auto = false;
+ if (value.endsWith(AUTO_SPECIFIER)) {
+ value = value.substring(0, value.indexOf(AUTO_SPECIFIER));
+ auto = true;
+ }
+
+ let css = {
+ 'display': this._queryInput('inline') ? 'inline-grid' : 'grid',
+ 'grid-auto-rows': '',
+ 'grid-template-rows': '',
+ };
+ const key = (auto ? 'grid-auto-rows' : 'grid-template-rows');
+ css[key] = value;
+
+ return css;
+ }
+}
diff --git a/src/lib/grid/tsconfig-build.json b/src/lib/grid/tsconfig-build.json
new file mode 100644
index 000000000..c235e7ee3
--- /dev/null
+++ b/src/lib/grid/tsconfig-build.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig-build",
+ "files": [
+ "public-api.ts",
+ "../typings.d.ts"
+ ],
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "strictMetadataEmit": false, // Workaround for Angular #22210
+ "flatModuleOutFile": "index.js",
+ "flatModuleId": "@angular/flex-layout/grid",
+ "skipTemplateCodegen": true,
+ "fullTemplateTypeCheck": true
+ }
+}
diff --git a/src/lib/module.ts b/src/lib/module.ts
index a75376dbf..9189ea5b7 100644
--- a/src/lib/module.ts
+++ b/src/lib/module.ts
@@ -10,6 +10,7 @@ import {isPlatformServer} from '@angular/common';
import {SERVER_TOKEN} from '@angular/flex-layout/core';
import {ExtendedModule} from '@angular/flex-layout/extended';
import {FlexModule} from '@angular/flex-layout/flex';
+import {GridModule} from '@angular/flex-layout/grid';
/**
@@ -24,8 +25,8 @@ import {FlexModule} from '@angular/flex-layout/flex';
*
*/
@NgModule({
- imports: [FlexModule, ExtendedModule],
- exports: [FlexModule, ExtendedModule]
+ imports: [FlexModule, ExtendedModule, GridModule],
+ exports: [FlexModule, ExtendedModule, GridModule]
})
export class FlexLayoutModule {
diff --git a/src/lib/package.json b/src/lib/package.json
index 07acc4f79..93c5e6329 100644
--- a/src/lib/package.json
+++ b/src/lib/package.json
@@ -26,7 +26,7 @@
"@angular/cdk": ">=6.0.0-beta.0 <7.0.0",
"@angular/core": "0.0.0-NG",
"@angular/common": "0.0.0-NG",
- "rxjs": "^6.0.0-beta.4"
+ "rxjs": "^6.0.0-rc.0"
},
"dependencies": {
"tslib": "^1.7.1"
diff --git a/src/lib/public-api.ts b/src/lib/public-api.ts
index 908fb5a5a..4328e143a 100644
--- a/src/lib/public-api.ts
+++ b/src/lib/public-api.ts
@@ -16,6 +16,7 @@ export * from './version';
export * from '@angular/flex-layout/core';
export * from '@angular/flex-layout/extended';
export * from '@angular/flex-layout/flex';
+export * from '@angular/flex-layout/grid';
// Flex-Layout Module
export * from './module';
diff --git a/test/browser-providers.js b/test/browser-providers.js
index ec8fe63bd..1d33f8d5b 100644
--- a/test/browser-providers.js
+++ b/test/browser-providers.js
@@ -13,7 +13,7 @@ const browserConfig = {
'FirefoxDev': { unitTest: {target: null, required: true }},
'IE9': { unitTest: {target: null, required: false }},
'IE10': { unitTest: {target: null, required: true }},
- 'IE11': { unitTest: {target: 'SL', required: true }},
+ 'IE11': { unitTest: {target: null, required: false }},
'Edge': { unitTest: {target: 'SL', required: true }},
'Android4.1': { unitTest: {target: null, required: false }},
'Android4.2': { unitTest: {target: null, required: false }},
@@ -22,11 +22,12 @@ const browserConfig = {
'Android5': { unitTest: {target: null, required: false }},
'Safari7': { unitTest: {target: null, required: false }},
'Safari8': { unitTest: {target: null, required: false }},
- 'Safari9': { unitTest: {target: 'SL', required: true }},
+ 'Safari9': { unitTest: {target: null, required: false }},
'Safari10': { unitTest: {target: 'BS', required: true }},
'iOS7': { unitTest: {target: null, required: false }},
'iOS8': { unitTest: {target: null, required: false }},
- 'iOS9': { unitTest: {target: 'BS', required: true }},
+ 'iOS9': { unitTest: {target: null, required: false }},
+ 'iOS10': { unitTest: {target: 'BS', required: true }},
'WindowsPhone': { unitTest: {target: null, required: false }}
};
diff --git a/test/karma-test-shim.js b/test/karma-test-shim.js
index 518e5f06f..ce80b2fcd 100644
--- a/test/karma-test-shim.js
+++ b/test/karma-test-shim.js
@@ -63,6 +63,7 @@ System.config({
'@angular/flex-layout/core': 'dist/packages/flex-layout/core/index.js',
'@angular/flex-layout/extended': 'dist/packages/flex-layout/extended/index.js',
'@angular/flex-layout/flex': 'dist/packages/flex-layout/flex/index.js',
+ '@angular/flex-layout/grid': 'dist/packages/flex-layout/grid/index.js',
'@angular/flex-layout/server': 'dist/packages/flex-layout/server/index.js',
},
packages: {
diff --git a/tools/package-tools/rollup-globals.ts b/tools/package-tools/rollup-globals.ts
index 44d8f7ee6..ffc668105 100644
--- a/tools/package-tools/rollup-globals.ts
+++ b/tools/package-tools/rollup-globals.ts
@@ -36,6 +36,7 @@ export const rollupGlobals = {
'@angular/common/http/testing': 'ng.common.http.testing',
'@angular/material/button': 'ng.material.button',
'@angular/cdk/bidi': 'ng.cdk.bidi',
+ '@angular/cdk/coercion': 'ng.cdk.coercion',
'@angular/cdk/platform': 'ng.cdk.platform',
// Some packages are not really needed for the UMD bundles, but for the missingRollupGlobals rule.