diff --git a/package.json b/package.json
index bef007b67..e57fd132d 100644
--- a/package.json
+++ b/package.json
@@ -28,29 +28,29 @@
"postinstall": "ngcc --properties es2015 browser module main --create-ivy-entry-points"
},
"version": "9.0.0-beta.29",
- "requiredAngularVersion": ">=9.0.0-rc.11",
+ "requiredAngularVersion": ">=9.0.0",
"dependencies": {
- "@angular/cdk": "^9.0.0-rc.8",
- "@angular/common": "^9.0.0-rc.11",
- "@angular/compiler": "^9.0.0-rc.11",
- "@angular/core": "^9.0.0-rc.11",
- "@angular/platform-browser": "^9.0.0-rc.11",
+ "@angular/cdk": "^9.0.0",
+ "@angular/common": "^9.0.0",
+ "@angular/compiler": "^9.0.0",
+ "@angular/core": "^9.0.0",
+ "@angular/platform-browser": "^9.0.0",
"core-js": "^2.5.7",
"karma-parallel": "^0.3.1",
"rxjs": "^6.5.1",
"systemjs": "0.19.43",
- "tsickle": "^0.37.1",
+ "tsickle": "^0.38.0",
"tslib": "^1.9.3",
"zone.js": "~0.10.2"
},
"devDependencies": {
- "@angular/animations": "^9.0.0-rc.11",
- "@angular/compiler-cli": "^9.0.0-rc.11",
- "@angular/forms": "^9.0.0-rc.11",
- "@angular/material": "^9.0.0-rc.8",
- "@angular/platform-browser-dynamic": "^9.0.0-rc.11",
- "@angular/platform-server": "^9.0.0-rc.11",
- "@angular/router": "^9.0.0-rc.11",
+ "@angular/animations": "^9.0.0",
+ "@angular/compiler-cli": "^9.0.0",
+ "@angular/forms": "^9.0.0",
+ "@angular/material": "^9.0.0",
+ "@angular/platform-browser-dynamic": "^9.0.0",
+ "@angular/platform-server": "^9.0.0",
+ "@angular/router": "^9.0.0",
"@firebase/app-types": "^0.3.2",
"@types/chalk": "^0.4.31",
"@types/fs-extra": "^4.0.5",
diff --git a/src/lib/uni/README.md b/src/lib/uni/README.md
new file mode 100644
index 000000000..619db67d7
--- /dev/null
+++ b/src/lib/uni/README.md
@@ -0,0 +1,55 @@
+The `uni` entrypoint is a completely new approach to the Layout library. The emphasis here
+is on simplicity, with as little defined in the library as possible. The goal is to create
+a utility around the hardest part of layout management: creating and defining interchangeable
+builders and coordinating that with the media query system in the browser.
+
+This is a backwards-incompatible approach to the current version of Angular Flex Layout, but
+we believe that this approach is more ergonomic in nature, and is the right way of doing things
+moving forward. That being said, this is completely opt-in, with no actual plans to transition
+anywhere in the near- or long-term.
+
+To use this is quite simple:
+
+### Step 1: Import the new module
+```ts
+// app.module.ts
+
+@NgModule({
+ imports: [UnifiedModule.withDefaults()] // brings in default tags and breakpoints
+})
+export class AppModule {}
+```
+
+### Step 2: Use the new syntax
+```html
+
+
+
+
+```
+
+Here, `ngl` stands for "Angular (ng) Layout (l)". The `bp` elements are optional if
+you don't plan to use breakpoints in your template. Conceptually, it's cleaner than
+cramming all of the breakpoints on one root element, and the `bp` elements don't
+actually render in the template anyway.
+
+We've removed the `fx` and `gd` prefixes, since now we can rely on attributes that
+are only applicable on the `bp` and root `ngl` elements.
+
+Again, our goal here is simplicity. The most basic usage could be construed as the
+following:
+
+```html
+
+```
+
+This roughly corresponds to the old syntax for accomplishing the same:
+
+```html
+
+```
+
+While users who don't use many attributes per directive may not see a difference,
+it will become quite apparent to those who use many, or as you scale.
+
+This is only a proposal, and all feedback is welcome.
\ No newline at end of file
diff --git a/src/lib/uni/index.ts b/src/lib/uni/index.ts
new file mode 100644
index 000000000..676ca90f1
--- /dev/null
+++ b/src/lib/uni/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/uni/module.ts b/src/lib/uni/module.ts
new file mode 100644
index 000000000..a417e9ffa
--- /dev/null
+++ b/src/lib/uni/module.ts
@@ -0,0 +1,49 @@
+/**
+ * @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 {ModuleWithProviders, NgModule} from '@angular/core';
+
+import {BreakpointDirective, UnifiedDirective} from './src/unified';
+import {FLEX_PROVIDER, GRID_PROVIDER, TAGS_PROVIDER} from './src/tags/tags';
+import {BREAKPOINTS_PROVIDER} from './src/breakpoint';
+
+
+@NgModule({
+ declarations: [UnifiedDirective, BreakpointDirective],
+ exports: [UnifiedDirective, BreakpointDirective]
+})
+export class UnifiedModule {
+ static withDefaults(withDefaultBp: boolean = true): ModuleWithProviders {
+ return {
+ ngModule: UnifiedModule,
+ providers: withDefaultBp ? [
+ TAGS_PROVIDER,
+ BREAKPOINTS_PROVIDER,
+ ] : [TAGS_PROVIDER],
+ };
+ }
+
+ static withFlex(withDefaultBp: boolean = true): ModuleWithProviders {
+ return {
+ ngModule: UnifiedModule,
+ providers: withDefaultBp ? [
+ FLEX_PROVIDER,
+ BREAKPOINTS_PROVIDER
+ ] : [FLEX_PROVIDER]
+ };
+ }
+
+ static withGrid(withDefaultBp: boolean = true): ModuleWithProviders {
+ return {
+ ngModule: UnifiedModule,
+ providers: withDefaultBp ? [
+ GRID_PROVIDER,
+ BREAKPOINTS_PROVIDER
+ ] : [GRID_PROVIDER]
+ };
+ }
+}
diff --git a/src/lib/uni/public-api.ts b/src/lib/uni/public-api.ts
new file mode 100644
index 000000000..c7336dc13
--- /dev/null
+++ b/src/lib/uni/public-api.ts
@@ -0,0 +1,10 @@
+/**
+ * @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 './src/uni';
+export * from './module';
diff --git a/src/lib/uni/src/breakpoint.ts b/src/lib/uni/src/breakpoint.ts
new file mode 100644
index 000000000..99ee31590
--- /dev/null
+++ b/src/lib/uni/src/breakpoint.ts
@@ -0,0 +1,145 @@
+/**
+ * @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 {inject, InjectionToken} from '@angular/core';
+
+
+/**
+ * A breakpoint is a wrapper interface around the browser's mediaQuery,
+ * which is a condition string used for matching based on browser window
+ * parameters. Here, a breakpoint has a shortcut name, e.g. 'xs', the
+ * corresponding mediaQuery, and a priority in case the mediaQuery overlaps
+ * with other registered breakpoints.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries
+ */
+export interface Breakpoint {
+ /** The shortcut name for the breakpoint, e.g. 'xs' */
+ name: string;
+ /** The mediaQuery for the breakpoint, e.g. 'screen and (max-width: 500px)' */
+ media: string;
+ /** The priority of the breakpoint compared to other breakpoints */
+ priority: number;
+}
+
+export const FALLBACK_BREAKPOINT_KEY: string = '__FALLBACK__';
+
+/**
+ * The fallback breakpoint, which has no real name and is
+ * superseded by any other breakpoint value
+ */
+export const FALLBACK_BREAKPOINT: Breakpoint = {
+ name: FALLBACK_BREAKPOINT_KEY,
+ media: 'all',
+ priority: -Number.MAX_SAFE_INTEGER,
+};
+
+/**
+ * The default breakpoints as provided by Google's Material Design.
+ * These do not include orientation breakpoints or device breakpoints.
+ */
+export const DEFAULT_BREAKPOINTS: Breakpoint[] = [
+ {
+ name: 'xs',
+ media: 'screen and (min-width: 0px) and (max-width: 599.9px)',
+ priority: 1000,
+ },
+ {
+ name: 'sm',
+ media: 'screen and (min-width: 600px) and (max-width: 959.9px)',
+ priority: 900,
+ },
+ {
+ name: 'md',
+ media: 'screen and (min-width: 960px) and (max-width: 1279.9px)',
+ priority: 800,
+ },
+ {
+ name: 'lg',
+ media: 'screen and (min-width: 1280px) and (max-width: 1919.9px)',
+ priority: 700,
+ },
+ {
+ name: 'xl',
+ media: 'screen and (min-width: 1920px) and (max-width: 4999.9px)',
+ priority: 600,
+ },
+ {
+ name: 'lt-sm',
+ media: 'screen and (max-width: 599.9px)',
+ priority: 950,
+ },
+ {
+ name: 'lt-md',
+ media: 'screen and (max-width: 959.9px)',
+ priority: 850,
+ },
+ {
+ name: 'lt-lg',
+ media: 'screen and (max-width: 1279.9px)',
+ priority: 750,
+ },
+ {
+ name: 'lt-xl',
+ priority: 650,
+ media: 'screen and (max-width: 1919.9px)',
+ },
+ {
+ name: 'gt-xs',
+ media: 'screen and (min-width: 600px)',
+ priority: -950,
+ },
+ {
+ name: 'gt-sm',
+ media: 'screen and (min-width: 960px)',
+ priority: -850,
+ }, {
+ name: 'gt-md',
+ media: 'screen and (min-width: 1280px)',
+ priority: -750,
+ },
+ {
+ name: 'gt-lg',
+ media: 'screen and (min-width: 1920px)',
+ priority: -650,
+ }
+];
+
+/**
+ * The user-facing injection token for providing breakpoints,
+ * this is meant to be provided as a multi-provider, and
+ * consolidated later.
+ */
+export const BREAKPOINTS =
+ new InjectionToken>>('Angular Layout Breakpoints');
+
+/** An internal-facing provider for the default breakpoints */
+export const BREAKPOINTS_PROVIDER = {
+ provide: BREAKPOINTS,
+ useValue: DEFAULT_BREAKPOINTS,
+ multi: true,
+};
+
+/**
+ * An internal-facing injection token to consolidate all registered
+ * breakpoints for use in the application.
+ */
+export const BPS = new InjectionToken('Angular Layout Condensed Breakpoints', {
+ providedIn: 'root',
+ factory: () => {
+ const providedBps = inject(BREAKPOINTS);
+ const bpMap: Map = new Map();
+
+ providedBps.forEach(bps => {
+ bps.forEach(bp => {
+ bpMap.set(bp.name, bp);
+ });
+ });
+
+ return [...Array.from(bpMap.values()), FALLBACK_BREAKPOINT];
+ }
+});
diff --git a/src/lib/uni/src/central.ts b/src/lib/uni/src/central.ts
new file mode 100644
index 000000000..92570df8c
--- /dev/null
+++ b/src/lib/uni/src/central.ts
@@ -0,0 +1,175 @@
+/**
+ * @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 {Inject, Injectable} from '@angular/core';
+import {Directionality} from '@angular/cdk/bidi';
+import {MediaMatcher} from '@angular/cdk/layout';
+
+import {UnifiedDirective} from './unified';
+import {BPS, Breakpoint} from './breakpoint';
+import {Tag, ValuePriority} from './tags/tag';
+import {TAGS} from './tags/tags';
+import {DIR_KEY, KEY_DELIMITER, NO_VALUE, PARENT_KEY, SELF_KEY, STYLE_KEY} from './constants';
+
+
+/**
+ * GrandCentral is a switchyard for all of the various Layout directives
+ * registered in an application. It is the single source of truth for all of
+ * the layout changes that occur in an application. For instance, any changes
+ * to the browser state via registered media queries are monitored and updated
+ * in this service. The directives themselves simply store their respective values
+ * for each of the media states.
+ */
+@Injectable({providedIn: 'root'})
+export class GrandCentral {
+ private mediaQueries: Map = new Map();
+ private activations: Breakpoint[] = [];
+ private activating: boolean = false;
+ private elementsMap: Map> = new Map();
+ private elementDataMap: Map> = new Map();
+ private dirListeners: Set = new Set();
+ private elListeners: Map> = new Map();
+
+ constructor(private readonly directionality: Directionality,
+ mediaMatcher: MediaMatcher,
+ @Inject(BPS) private readonly bps: Breakpoint[],
+ @Inject(TAGS) private readonly tags: Map) {
+ bps.forEach(bp => {
+ this.elementsMap.set(bp, new Set());
+ const mediaQueryList = mediaMatcher.matchMedia(bp.media);
+ this.mediaQueries.set(bp.name, mediaQueryList);
+ mediaQueryList.addListener(e => {
+ const activate = e.matches && this.activations.indexOf(bp) === -1;
+ const deactivate = !e.matches && this.activations.indexOf(bp) > -1;
+ if (!this.activating && (activate || deactivate)) {
+ this.dirListeners.clear();
+ this.elListeners.clear();
+ this.activating = true;
+ this.computeValues();
+ }
+ });
+ });
+ this.computeActivations();
+ directionality.change.subscribe(() => this.dirListeners.forEach(this.addValues));
+ }
+
+ /** Add a directive for a corresponding breakpoint */
+ addDirective(dir: UnifiedDirective, bp: Breakpoint) {
+ this.elementsMap.get(bp)!.add(dir);
+ this.updateDirective(dir);
+ }
+
+ /** Trigger an update for a directive */
+ updateDirective(dir: UnifiedDirective) {
+ this.computeDirective(dir);
+ this.addValues(dir);
+ const listeners = this.elListeners.get(dir);
+ if (listeners) {
+ listeners.forEach(this.addValues);
+ }
+ }
+
+ /** Remove a directive from all future updates */
+ removeDirective(dir: UnifiedDirective) {
+ this.dirListeners.delete(dir);
+ const parentListeners = this.elListeners.get(dir.parent);
+ if (parentListeners) {
+ parentListeners.delete(dir);
+ }
+ this.bps.forEach(bp => this.elementsMap.get(bp)!.delete(dir));
+ }
+
+ /** Compute the active breakpoints and sort by descending priority */
+ private computeActivations() {
+ this.activations = this.bps
+ .filter(bp => this.mediaQueries.get(bp.name)!.matches)
+ .sort((a, b) => b.priority - a.priority);
+ }
+
+ /** Compute the values and update the directives for all active breakpoints */
+ private computeValues() {
+ this.computeActivations();
+ this.activations.forEach(bp =>
+ this.elementsMap.get(bp)!.forEach(this.computeDirective.bind(this)));
+ Array.from(this.elementDataMap.keys()).forEach(this.addValues.bind(this));
+ this.activating = false;
+ }
+
+ /** Compute the values for an individual directive */
+ private computeDirective(dir: UnifiedDirective) {
+ const values: Map = new Map();
+ this.activations.forEach(bp => {
+ const valueMap = dir.valueMap.get(bp.name);
+
+ if (valueMap) {
+ valueMap.forEach((value, key) => {
+ if (!values.has(key)) {
+ values.set(key, value);
+ }
+ });
+ }
+ });
+
+ this.elementDataMap.set(dir, values);
+ }
+
+ /** Add the computed values for an individual directive */
+ private addValues(dir: UnifiedDirective) {
+ const values = this.elementDataMap.get(dir)!;
+ const map: Map = new Map();
+ values.forEach((value, key) => {
+ const tag = this.tags.get(key)!;
+ const priorityMap = this.calculate(tag.tag, value, dir);
+ priorityMap.forEach((v, k) => {
+ let [type, typeKey] = k.split(KEY_DELIMITER);
+ if (typeKey === undefined) {
+ typeKey = type;
+ type = STYLE_KEY;
+ }
+ k = [type, typeKey].join(KEY_DELIMITER);
+ const valuePriority = map.get(k);
+ if (!valuePriority || valuePriority.priority < v.priority) {
+ map.set(k, v);
+ }
+ });
+ });
+
+ dir.apply(map);
+ }
+
+ /** Compute the values to apply for a directive given a tag and input value */
+ private calculate(tagName: string,
+ value: string,
+ dir: UnifiedDirective): Map {
+ const tag = this.tags.get(tagName)!;
+ const args = this.resolve(dir, tag.deps);
+ return tag.compute(value, ...args);
+ }
+
+ /** Resolve the arguments for a builder given a directive */
+ private resolve(dir: UnifiedDirective, deps: string[]): string[] {
+ return deps.map(dep => {
+ const keys = dep.split(KEY_DELIMITER);
+ if (keys.length > 1 && keys[0] === PARENT_KEY || keys[0] === SELF_KEY) {
+ // NOTE: elListeners does not account for directionality change, because
+ // the assumption is that directionality does not change the parent values
+ const dataMap = this.elementDataMap.get(keys[0] === PARENT_KEY ? dir.parent : dir);
+ if (dataMap) {
+ const elements = this.elListeners.get(dir.parent) || new Set();
+ elements.add(dir);
+ this.elListeners.set(dir.parent, elements);
+ return dataMap.get(keys[1]) ?? NO_VALUE;
+ }
+ } else if (dep === DIR_KEY) {
+ this.dirListeners.add(dir);
+ return this.directionality.value;
+ }
+
+ return NO_VALUE;
+ });
+ }
+}
diff --git a/src/lib/uni/src/constants.ts b/src/lib/uni/src/constants.ts
new file mode 100644
index 000000000..a4b5a8ea9
--- /dev/null
+++ b/src/lib/uni/src/constants.ts
@@ -0,0 +1,34 @@
+/**
+ * @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 const JUSTIFY_CONTENT = 'justify-content';
+export const JUSTIFY_ITEMS = 'justify-items';
+export const JUSTIFY_SELF = 'justify-self';
+export const ALIGN_CONTENT = 'align-content';
+export const ALIGN_ITEMS = 'align-items';
+export const ALIGN_SELF = 'align-self';
+export const CENTER = 'center';
+export const STRETCH = 'stretch';
+export const BASELINE = 'baseline';
+export const END = 'end';
+export const FLEX_END = 'flex-end';
+export const START = 'start';
+export const FLEX_START = 'flex-start';
+export const SPACE_AROUND = 'space-around';
+export const SPACE_BETWEEN = 'space-between';
+export const SPACE_EVENLY = 'space-evenly';
+export const KEY_DELIMITER = '.';
+export const PARENT_KEY = 'parent';
+export const SELF_KEY = 'self';
+export const DIR_KEY = 'directionality';
+export const CLASS_KEY = 'class';
+export const STYLE_KEY = 'style';
+export const ATTR_KEY = 'attr';
+/** Special key to clear an applied value and restore the fallback */
+export const CLEAR_VALUE = '__CLEAR__';
+/** Special key indicating that a value cannot be resolved for a dependency */
+export const NO_VALUE = '__NO_VALUE__';
diff --git a/src/lib/uni/src/tags/core/fill.ts b/src/lib/uni/src/tags/core/fill.ts
new file mode 100644
index 000000000..333864695
--- /dev/null
+++ b/src/lib/uni/src/tags/core/fill.ts
@@ -0,0 +1,24 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+const STYLES = new Map()
+ .set('margin', {value: '0', priority: 0})
+ .set('width', {value: '100%', priority: 0})
+ .set('height', {value: '100%', priority: 0})
+ .set('min-width', {value: '100%', priority: 0})
+ .set('min-height', {value: '100%', priority: 0});
+
+export class Fill extends Tag {
+ readonly tag = 'fill';
+
+ build(): Map {
+ return STYLES;
+ }
+}
diff --git a/src/lib/uni/src/tags/core/gap.ts b/src/lib/uni/src/tags/core/gap.ts
new file mode 100644
index 000000000..08c73d34b
--- /dev/null
+++ b/src/lib/uni/src/tags/core/gap.ts
@@ -0,0 +1,21 @@
+/**
+ * @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 {Direction} from '@angular/cdk/bidi';
+
+import {Tag, ValuePriority} from '../tag';
+
+
+export class Gap extends Tag {
+ readonly tag = 'gap';
+ readonly deps = ['self.layout', 'directionality'];
+
+ build(_: string, __: string, ___: Direction): Map {
+ const styles: Map = new Map();
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/core/offset.ts b/src/lib/uni/src/tags/core/offset.ts
new file mode 100644
index 000000000..23368b956
--- /dev/null
+++ b/src/lib/uni/src/tags/core/offset.ts
@@ -0,0 +1,30 @@
+/**
+ * @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 {Direction} from '@angular/cdk/bidi';
+
+import {Tag, ValuePriority} from '../tag';
+import {isFlowHorizontal} from '../utils';
+
+
+export class Offset extends Tag {
+ readonly tag = 'flexOffset';
+ readonly deps = ['parent.layout', 'directionality'];
+
+ build(input: string, layout: string, direction: Direction): Map {
+ input = input || '0';
+ const isRtl = direction === 'rtl';
+ const isPercent = input.indexOf('%') > -1;
+ const isPx = input.indexOf('px') > -1;
+ if (!isPx && !isPercent && !isNaN(+input)) {
+ input = input + '%';
+ }
+ const horizontalLayoutKey = isRtl ? 'margin-right' : 'margin-left';
+ const key = isFlowHorizontal(layout) ? horizontalLayoutKey : 'margin-top';
+ return new Map().set(key, input);
+ }
+}
diff --git a/src/lib/uni/src/tags/core/order.ts b/src/lib/uni/src/tags/core/order.ts
new file mode 100644
index 000000000..4db293ace
--- /dev/null
+++ b/src/lib/uni/src/tags/core/order.ts
@@ -0,0 +1,18 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Order extends Tag {
+ readonly tag = 'order';
+
+ build(input: string): Map {
+ input = input || '0';
+ return new Map().set('order', {value: parseInt(input, 10), priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/core/show-hide.ts b/src/lib/uni/src/tags/core/show-hide.ts
new file mode 100644
index 000000000..f49399468
--- /dev/null
+++ b/src/lib/uni/src/tags/core/show-hide.ts
@@ -0,0 +1,32 @@
+/**
+ * @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 {coerceBooleanProperty} from '@angular/cdk/coercion';
+
+import {Tag, ValuePriority} from '../tag';
+import {NO_VALUE} from '../../constants';
+
+
+const HIDE_STYLES = new Map().set('display', {value: 'none', priority: 100});
+const EMPTY_MAP = new Map();
+
+export class Hide extends Tag {
+ readonly tag = 'hide';
+ readonly deps = ['self.show'];
+
+ build(input: string, show: string): Map {
+ return coerceBooleanProperty(input) && show === NO_VALUE ? HIDE_STYLES : EMPTY_MAP;
+ }
+}
+
+export class Show extends Tag {
+ readonly tag = 'show';
+
+ build(): Map {
+ return EMPTY_MAP;
+ }
+}
diff --git a/src/lib/uni/src/tags/flex/flex-align.ts b/src/lib/uni/src/tags/flex/flex-align.ts
new file mode 100644
index 000000000..fb91af8d2
--- /dev/null
+++ b/src/lib/uni/src/tags/flex/flex-align.ts
@@ -0,0 +1,34 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {ALIGN_SELF, END, FLEX_END, FLEX_START, START, STRETCH} from '../../constants';
+
+
+export class FlexAlign extends Tag {
+ readonly tag = 'flexAlign';
+
+ build(input: string): Map {
+ input = input || STRETCH;
+ const styles: Map = new Map();
+
+ // Cross-axis
+ switch (input) {
+ case START:
+ styles.set(ALIGN_SELF, {value: FLEX_START, priority: 0});
+ break;
+ case END:
+ styles.set(ALIGN_SELF, {value: FLEX_END, priority: 0});
+ break;
+ default:
+ styles.set(ALIGN_SELF, {value: input, priority: 0});
+ break;
+ }
+
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/flex/flex.ts b/src/lib/uni/src/tags/flex/flex.ts
new file mode 100644
index 000000000..251ebd8ba
--- /dev/null
+++ b/src/lib/uni/src/tags/flex/flex.ts
@@ -0,0 +1,147 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {isFlowHorizontal} from '../utils';
+
+
+export class Flex extends Tag {
+ readonly tag = 'flex';
+ readonly deps = ['parent.layout'];
+
+ build(input: string, layout: string): Map {
+ const styles: Map = new Map()
+ .set('box-sizing', {value: 'border-box', priority: 0});
+ const wrap = layout.indexOf('wrap') > -1;
+ let [grow, shrink, ...basisParts]: string[] = input.split(' ');
+ const zeroIndex = basisParts.indexOf('zero');
+ const useColumnBasisZero = zeroIndex > -1;
+ let basis = !useColumnBasisZero ? basisParts.join(' ') : basisParts.splice(zeroIndex, 1).join(' ');
+
+ // The flex-direction of this element's flex container. Defaults to 'row'.
+ const direction = layout.indexOf('column') > -1 ? 'column' : 'row';
+ const max = isFlowHorizontal(direction) ? 'max-width' : 'max-height';
+ const min = isFlowHorizontal(direction) ? 'min-width' : 'min-height';
+ const hasCalc = basis.indexOf('calc') > -1;
+ const usingCalc = hasCalc || (basis === 'auto');
+ const isPercent = basis.indexOf('%') > -1 && !hasCalc;
+ const hasUnits = basis.indexOf('px') > -1 || basis.indexOf('rem') > -1 ||
+ basis.indexOf('em') > -1 || basis.indexOf('vw') > -1 || basis.indexOf('vh') > -1;
+ let isValue = (hasCalc || hasUnits);
+
+ // make box inflexible when shrink and grow are both zero
+ // should not set a min when the grow is zero
+ // should not set a max when the shrink is zero
+ const isFixed = !grow && !shrink;
+
+ // flex-basis allows you to specify the initial/starting main-axis size of the element,
+ // before anything else is computed. It can either be a percentage or an absolute value.
+ // It is, however, not the breaking point for flex-grow/shrink properties
+ //
+ // flex-grow can be seen as this:
+ // 0: Do not stretch. Either size to element's content width, or obey 'flex-basis'.
+ // 1: (Default value). Stretch; will be the same size to all other flex items on
+ // the same row since they have a default value of 1.
+ // ≥2 (integer n): Stretch. Will be n times the size of other elements
+ // with 'flex-grow: 1' on the same row.
+ switch (basis) {
+ case '':
+ basis = direction === 'row' ? '0%' : (useColumnBasisZero ? '0.000000001px' : 'auto');
+ break;
+ // default
+ case 'initial':
+ case 'nogrow':
+ grow = '0';
+ basis = 'auto';
+ break;
+ case 'grow':
+ basis = '100%';
+ break;
+ case 'noshrink':
+ shrink = '0';
+ basis = 'auto';
+ break;
+ case 'auto':
+ break;
+ case 'none':
+ grow = '0';
+ shrink = '0';
+ basis = 'auto';
+ break;
+ default:
+ // Defaults to percentage sizing unless `px` is explicitly set
+ if (!isValue && !isPercent && !isNaN(basis as any)) {
+ basis = basis + '%';
+ }
+
+ // Fix for issue #280
+ if (basis === '0%') {
+ isValue = true;
+ }
+
+ if (basis === '0px') {
+ basis = '0%';
+ }
+
+ // Fix for issue #5345
+ if (hasCalc) {
+ styles.set('flex-grow', {value: grow, priority: 0});
+ styles.set('flex-shrink', {value: shrink, priority: 0});
+ styles.set('flex-basis', {value: isValue ? basis : '100%', priority: 0});
+ } else {
+ styles.set('flex', {value: `${grow} ${shrink} ${isValue ? basis : '100%'}`, priority: 0});
+ }
+
+ break;
+ }
+
+ if (!styles.has('flex') || !styles.has('flex-grow')) {
+ if (hasCalc) {
+ styles.set('flex-grow', {value: grow, priority: 0});
+ styles.set('flex-shrink', {value: shrink, priority: 0});
+ styles.set('flex-basis', {value: basis, priority: 0});
+ } else {
+ styles.set('flex', {value: `${grow} ${shrink} ${basis}`, priority: 0});
+ }
+ }
+
+ // Fix for issues #277, #534, and #728
+ if (basis !== '0%' && basis !== '0px' && basis !== '0.000000001px' && basis !== 'auto') {
+ if (isFixed) {
+ styles.set(min, {value: basis, priority: 0});
+ styles.set(max, {value: basis, priority: 0});
+ } else {
+ if (isValue && grow) {
+ styles.set(min, {value: basis, priority: 0});
+ }
+ if (!usingCalc && shrink) {
+ styles.set(max, {value: basis, priority: 0});
+ }
+ }
+ }
+
+ // Fix for issue #528
+ if (!styles.has(min) && !styles.has(max)) {
+ if (hasCalc) {
+ styles.set('flex-grow', {value: grow, priority: 0});
+ styles.set('flex-shrink', {value: shrink, priority: 0});
+ styles.set('flex-basis', {value: basis, priority: 0});
+ } else {
+ styles.set('flex', {value: `${grow} ${shrink} ${basis}`, priority: 0});
+ }
+ } else {
+ // Fix for issue #660
+ if (wrap) {
+ const value = styles.has(max) ? styles.get(max)!.value : styles.get(min)!.value;
+ styles.set(hasCalc ? 'flex-basis' : 'flex',
+ {value: hasCalc ? value : `${grow} ${shrink} ${value}`, priority: 0});
+ }
+ }
+
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/flex/layout-align.ts b/src/lib/uni/src/tags/flex/layout-align.ts
new file mode 100644
index 000000000..a90e7f70d
--- /dev/null
+++ b/src/lib/uni/src/tags/flex/layout-align.ts
@@ -0,0 +1,90 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {isFlowHorizontal} from '../utils';
+import {
+ ALIGN_CONTENT,
+ ALIGN_ITEMS,
+ BASELINE,
+ CENTER,
+ END,
+ FLEX_END,
+ FLEX_START,
+ JUSTIFY_CONTENT,
+ SPACE_AROUND,
+ SPACE_BETWEEN,
+ SPACE_EVENLY,
+ START,
+ STRETCH
+} from '../../constants';
+
+
+export class LayoutAlign extends Tag {
+ readonly tag = 'layoutAlign';
+ readonly deps = ['self.layout', 'directionality'];
+
+ build(input: string, layout: string): Map {
+ layout = layout || 'row';
+ const [mainAxis, crossAxis] = input.split(' ');
+ const maxKey = crossAxis === STRETCH && isFlowHorizontal(layout) ? 'max-height' : 'max-width';
+ const styles: Map = new Map()
+ .set('display', {value: 'flex', priority: -1})
+ .set('flex-direction', {value: 'row', priority: -1})
+ .set('box-sizing', {value: 'border-box', priority: 0})
+ .set(maxKey, {value: '100%', priority: 0});
+
+ switch (mainAxis) {
+ case FLEX_START:
+ case FLEX_END:
+ case SPACE_EVENLY:
+ case SPACE_BETWEEN:
+ case SPACE_AROUND:
+ case CENTER:
+ styles.set(JUSTIFY_CONTENT, {value: mainAxis, priority: 0});
+ break;
+ case END:
+ styles.set(JUSTIFY_CONTENT, {value: FLEX_END, priority: 0});
+ break;
+ case START:
+ default:
+ styles.set(JUSTIFY_CONTENT, {value: FLEX_START, priority: 0});
+ }
+
+ switch (crossAxis) {
+ case STRETCH:
+ case FLEX_START:
+ case FLEX_END:
+ case CENTER:
+ styles.set(ALIGN_ITEMS, {value: crossAxis, priority: 0});
+ styles.set(ALIGN_CONTENT, {value: crossAxis, priority: 0});
+ break;
+ case START:
+ styles.set(ALIGN_ITEMS, {value: FLEX_START, priority: 0});
+ styles.set(ALIGN_CONTENT, {value: FLEX_START, priority: 0});
+ break;
+ case END:
+ styles.set(ALIGN_ITEMS, {value: FLEX_END, priority: 0});
+ styles.set(ALIGN_CONTENT, {value: FLEX_END, priority: 0});
+ break;
+ case SPACE_BETWEEN:
+ case SPACE_AROUND:
+ styles.set(ALIGN_CONTENT, {value: crossAxis, priority: 0});
+ styles.set(ALIGN_ITEMS, {value: STRETCH, priority: 0});
+ break;
+ case BASELINE:
+ styles.set(ALIGN_CONTENT, {value: STRETCH, priority: 0});
+ styles.set(ALIGN_ITEMS, {value: BASELINE, priority: 0});
+ break;
+ default:
+ styles.set(ALIGN_CONTENT, {value: STRETCH, priority: 0});
+ styles.set(ALIGN_ITEMS, {value: STRETCH, priority: 0});
+ }
+
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/flex/layout.ts b/src/lib/uni/src/tags/flex/layout.ts
new file mode 100644
index 000000000..aa6392317
--- /dev/null
+++ b/src/lib/uni/src/tags/flex/layout.ts
@@ -0,0 +1,26 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {validateValue} from '../utils';
+
+
+export class Layout extends Tag {
+ readonly tag = 'layout';
+
+ build(input: string): Map {
+ const [direction, wrap, isInline] = validateValue(input);
+ const styles: Map = new Map()
+ .set('display', {value: isInline ? 'inline-flex' : 'flex', priority: 1})
+ .set('box-sizing', {value: 'border-box', priority: 0})
+ .set('flex-direction', {value: direction, priority: 0});
+ if (!!wrap) {
+ styles.set('flex-wrap', {value: wrap, priority: 0});
+ }
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/align-columns.ts b/src/lib/uni/src/tags/grid/align-columns.ts
new file mode 100644
index 000000000..f8ef1bbf1
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/align-columns.ts
@@ -0,0 +1,61 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {
+ ALIGN_CONTENT,
+ ALIGN_ITEMS,
+ CENTER,
+ END,
+ SPACE_AROUND,
+ SPACE_BETWEEN,
+ SPACE_EVENLY,
+ START,
+ STRETCH,
+} from '../../constants';
+
+
+export class AlignColumns extends Tag {
+ readonly tag = 'alignColumns';
+
+ build(input: string): Map {
+ const [mainAxis, crossAxis] = input.split(' ');
+ const styles: Map = new Map();
+
+ // Main axis
+ switch (mainAxis) {
+ case CENTER:
+ case SPACE_AROUND:
+ case SPACE_BETWEEN:
+ case SPACE_EVENLY:
+ case END:
+ case START:
+ case STRETCH:
+ styles.set(ALIGN_CONTENT, {value: mainAxis, priority: 0});
+ break;
+ default:
+ styles.set(ALIGN_CONTENT, {value: DEFAULT_MAIN, priority: 0});
+ }
+
+ // Cross-axis
+ switch (crossAxis) {
+ case START:
+ case CENTER:
+ case END:
+ case STRETCH:
+ styles.set(ALIGN_ITEMS, {value: crossAxis, priority: 0});
+ break;
+ default:
+ styles.set(ALIGN_ITEMS, {value: DEFAULT_CROSS, priority: 0});
+ }
+
+ return styles;
+ }
+}
+
+const DEFAULT_MAIN = 'start';
+const DEFAULT_CROSS = 'stretch';
diff --git a/src/lib/uni/src/tags/grid/align-rows.ts b/src/lib/uni/src/tags/grid/align-rows.ts
new file mode 100644
index 000000000..b53baef2a
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/align-rows.ts
@@ -0,0 +1,61 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {
+ CENTER,
+ END,
+ JUSTIFY_CONTENT,
+ JUSTIFY_ITEMS,
+ SPACE_AROUND,
+ SPACE_BETWEEN,
+ SPACE_EVENLY,
+ START,
+ STRETCH,
+} from '../../constants';
+
+
+export class AlignRows extends Tag {
+ readonly tag = 'alignRows';
+
+ build(input: string): Map {
+ const [mainAxis, crossAxis] = input.split(' ');
+ const styles: Map = new Map();
+
+ // Main axis
+ switch (mainAxis) {
+ case CENTER:
+ case SPACE_AROUND:
+ case SPACE_BETWEEN:
+ case SPACE_EVENLY:
+ case END:
+ case START:
+ case STRETCH:
+ styles.set(JUSTIFY_CONTENT, {value: mainAxis, priority: 0});
+ break;
+ default:
+ styles.set(JUSTIFY_CONTENT, {value: DEFAULT_MAIN, priority: 0});
+ }
+
+ // Cross-axis
+ switch (crossAxis) {
+ case START:
+ case CENTER:
+ case END:
+ case STRETCH:
+ styles.set(JUSTIFY_ITEMS, {value: crossAxis, priority: 0});
+ break;
+ default:
+ styles.set(JUSTIFY_ITEMS, {value: DEFAULT_CROSS, priority: 0});
+ }
+
+ return styles;
+ }
+}
+
+const DEFAULT_MAIN = 'start';
+const DEFAULT_CROSS = 'stretch';
diff --git a/src/lib/uni/src/tags/grid/align.ts b/src/lib/uni/src/tags/grid/align.ts
new file mode 100644
index 000000000..d590a4c11
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/align.ts
@@ -0,0 +1,53 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+import {
+ ALIGN_SELF,
+ CENTER,
+ END,
+ JUSTIFY_SELF,
+ START,
+ STRETCH
+} from '../../constants';
+
+
+export class Align extends Tag {
+ readonly tag = 'gridAlign';
+
+ build(input: string): Map {
+ input = input || 'stretch';
+ const styles: Map = new Map();
+ const [rowAxis, columnAxis] = input.split(' ');
+
+ // Row axis
+ switch (rowAxis) {
+ case END:
+ case CENTER:
+ case STRETCH:
+ case START:
+ styles.set(JUSTIFY_SELF, {value: rowAxis, priority: 0});
+ break;
+ default:
+ styles.set(JUSTIFY_SELF, {value: STRETCH, priority: 0});
+ }
+
+ // Column axis
+ switch (columnAxis) {
+ case END:
+ case CENTER:
+ case STRETCH:
+ case START:
+ styles.set(ALIGN_SELF, {value: rowAxis, priority: 0});
+ break;
+ default:
+ styles.set(ALIGN_SELF, {value: STRETCH, priority: 0});
+ }
+
+ return styles;
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/area.ts b/src/lib/uni/src/tags/grid/area.ts
new file mode 100644
index 000000000..09c887f94
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/area.ts
@@ -0,0 +1,18 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Area extends Tag {
+ readonly tag = 'area';
+
+ build(input: string): Map {
+ input = input || 'auto';
+ return new Map().set('grid-area', {value: input, priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/areas.ts b/src/lib/uni/src/tags/grid/areas.ts
new file mode 100644
index 000000000..03a88c0c5
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/areas.ts
@@ -0,0 +1,23 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Areas extends Tag {
+ readonly tag = 'areas';
+
+ build(input: string): Map {
+ const areas = (input || DEFAULT_VALUE).split(DELIMETER).map(v => `"${v.trim()}"`);
+ return new Map()
+ .set('display', {value: 'grid', priority: 0})
+ .set('grid-template-areas', {value: areas.join(' '), priority: 0});
+ }
+}
+
+const DEFAULT_VALUE = 'none';
+const DELIMETER = '|';
diff --git a/src/lib/uni/src/tags/grid/auto.ts b/src/lib/uni/src/tags/grid/auto.ts
new file mode 100644
index 000000000..df96a9d42
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/auto.ts
@@ -0,0 +1,27 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Auto extends Tag {
+ readonly tag = 'auto';
+
+ build(input: string): Map {
+ input = input || 'initial';
+ let [direction, dense] = input.split(' ');
+ if (direction !== 'column' && direction !== 'row' && direction !== 'dense') {
+ direction = 'row';
+ }
+
+ dense = (dense === 'dense' && direction !== 'dense') ? ' dense' : '';
+
+ return new Map()
+ .set('display', {value: 'grid', priority: 0})
+ .set('grid-auto-flow', {value: direction + dense, priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/column.ts b/src/lib/uni/src/tags/grid/column.ts
new file mode 100644
index 000000000..a03fcfa0e
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/column.ts
@@ -0,0 +1,18 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Column extends Tag {
+ readonly tag = 'column';
+
+ build(input: string): Map {
+ input = input || 'auto';
+ return new Map().set('grid-column', {value: input, priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/columns.ts b/src/lib/uni/src/tags/grid/columns.ts
new file mode 100644
index 000000000..35f6e734a
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/columns.ts
@@ -0,0 +1,29 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Columns extends Tag {
+ readonly tag = 'columns';
+
+ build(input: string): Map {
+ input = input || 'none';
+ let auto = false;
+ if (input.endsWith(AUTO_SPECIFIER)) {
+ input = input.substring(0, input.indexOf(AUTO_SPECIFIER));
+ auto = true;
+ }
+
+ const key = auto ? 'grid-auto-columns' : 'grid-template-columns';
+ return new Map()
+ .set('display', {value: 'grid', priority: 0})
+ .set(key, {value: input, priority: 0});
+ }
+}
+
+const AUTO_SPECIFIER = '!';
diff --git a/src/lib/uni/src/tags/grid/gap.ts b/src/lib/uni/src/tags/grid/gap.ts
new file mode 100644
index 000000000..6ea8674bf
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/gap.ts
@@ -0,0 +1,26 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+/**
+ * '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
+ */
+export class GridGap extends Tag {
+ readonly tag = 'gridGap';
+
+ build(input: string): Map {
+ input = input || '0';
+ return new Map()
+ .set('display', {value: 'grid', priority: -1})
+ .set('grid-gap', {value: input, priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/inline.ts b/src/lib/uni/src/tags/grid/inline.ts
new file mode 100644
index 000000000..1c56ce97e
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/inline.ts
@@ -0,0 +1,19 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+const INLINE_STYLES = new Map().set('display', {value: 'inline-grid', priority: 1});
+
+export class Inline extends Tag {
+ readonly tag = 'inline';
+
+ build(): Map {
+ return INLINE_STYLES;
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/row.ts b/src/lib/uni/src/tags/grid/row.ts
new file mode 100644
index 000000000..b8d0770fa
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/row.ts
@@ -0,0 +1,18 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Row extends Tag {
+ readonly tag = 'row';
+
+ build(input: string): Map {
+ input = input || 'auto';
+ return new Map().set('grid-row', {value: input, priority: 0});
+ }
+}
diff --git a/src/lib/uni/src/tags/grid/rows.ts b/src/lib/uni/src/tags/grid/rows.ts
new file mode 100644
index 000000000..c796759e5
--- /dev/null
+++ b/src/lib/uni/src/tags/grid/rows.ts
@@ -0,0 +1,29 @@
+/**
+ * @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 {Tag, ValuePriority} from '../tag';
+
+
+export class Rows extends Tag {
+ readonly tag = 'rows';
+
+ build(input: string): Map {
+ input = input || 'none';
+ let auto = false;
+ if (input.endsWith(AUTO_SPECIFIER)) {
+ input = input.substring(0, input.indexOf(AUTO_SPECIFIER));
+ auto = true;
+ }
+
+ const key = auto ? 'grid-auto-rows' : 'grid-template-rows';
+ return new Map()
+ .set('display', {value: 'grid', priority: 0})
+ .set(key, {value: input, priority: 0});
+ }
+}
+
+const AUTO_SPECIFIER = '!';
diff --git a/src/lib/uni/src/tags/tag.ts b/src/lib/uni/src/tags/tag.ts
new file mode 100644
index 000000000..22453039b
--- /dev/null
+++ b/src/lib/uni/src/tags/tag.ts
@@ -0,0 +1,68 @@
+/**
+ * @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
+ */
+
+/**
+ * A bundle used for comparing conflicting CSS styles
+ * generated by multiple tags.
+ */
+export interface ValuePriority {
+ /** The value of a corresponding CSS style */
+ value: string;
+ /**
+ * The priority of the associated value compared to
+ * other comparable values
+ */
+ priority: number;
+}
+
+/**
+ * A tag is a way of consolidating logic about a style pattern. For instance,
+ * setting the 'flex' attribute could be done with a Flex Tag. Each tag has an
+ * associated name, builder, cache, and dependencies on other builder input
+ * values.
+ */
+export abstract class Tag {
+ private cache: Map> = new Map();
+
+ /** The name of the tag, e.g. 'flex' */
+ abstract readonly tag: string;
+
+ /**
+ * The deps required to build this pattern. This can be from the
+ * directive the tag is on, its parent, or other outside dependencies
+ * like Directionality.
+ */
+ readonly deps: string[] = [];
+
+ compute(input: string, ...args: string[]): Map {
+ const cacheKey = input + args.join('');
+ const cache = this.getCache(cacheKey);
+
+ if (cache) {
+ return cache;
+ }
+
+ const map = this.build(input, ...args);
+ this.setCache(input, map);
+ return map;
+ }
+
+ /**
+ * The builder for this tag, outputting a map of computed styles.
+ * @param input the value for the tag, e.g. flex="50" the input is "50"
+ * @param args the resolved dependencies for this tag
+ */
+ protected abstract build(input: string, ...args: string[]): Map;
+
+ private setCache(input: string, value: Map) {
+ this.cache.set(input, value);
+ }
+ private getCache(input: string) {
+ return this.cache.get(input);
+ }
+}
diff --git a/src/lib/uni/src/tags/tags.ts b/src/lib/uni/src/tags/tags.ts
new file mode 100644
index 000000000..6e05193c0
--- /dev/null
+++ b/src/lib/uni/src/tags/tags.ts
@@ -0,0 +1,118 @@
+/**
+ * @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 {inject, InjectionToken} from '@angular/core';
+
+import {Tag} from './tag';
+import {Fill} from './core/fill';
+import {Gap} from './core/gap';
+import {Offset} from './core/offset';
+import {Order} from './core/order';
+import {Hide, Show} from './core/show-hide';
+import {Flex} from './flex/flex';
+import {FlexAlign} from './flex/flex-align';
+import {LayoutAlign} from './flex/layout-align';
+import {Layout} from './flex/layout';
+import {GridGap} from './grid/gap';
+import {Inline} from './grid/inline';
+import {AlignColumns} from './grid/align-columns';
+import {AlignRows} from './grid/align-rows';
+import {Area} from './grid/area';
+import {Areas} from './grid/areas';
+import {Auto} from './grid/auto';
+import {Align} from './grid/align';
+import {Column} from './grid/column';
+import {Columns} from './grid/columns';
+import {Row} from './grid/row';
+import {Rows} from './grid/rows';
+
+
+/** All of the extended features that are not CSS standard */
+export const CORE_TAGS: Tag[] = [
+ new Fill(),
+ new Gap(),
+ new Hide(),
+ new Offset(),
+ new Order(),
+ new Show()
+];
+
+/** All of the standard CSS flexbox-related tags */
+export const FLEX_TAGS: Tag[] = [
+ new Flex(),
+ new FlexAlign(),
+ new LayoutAlign(),
+ new Layout(),
+];
+
+/** All of the standard CSS grid-related tags */
+export const GRID_TAGS: Tag[] = [
+ new Inline(),
+ new GridGap(),
+ new AlignColumns(),
+ new AlignRows(),
+ new Area(),
+ new Areas(),
+ new Auto(),
+ new Align(),
+ new Column(),
+ new Columns(),
+ new Row(),
+ new Rows(),
+];
+
+/**
+ * The default tags as provided by Angular Layout. These include both
+ * flex and grid type tags.
+*/
+export const DEFAULT_TAGS: Tag[] = [...CORE_TAGS, ...FLEX_TAGS, ...GRID_TAGS];
+
+/**
+ * The user-facing injection token for providing tags,
+ * this is meant to be provided as a multi-provider, and
+ * consolidated later.
+ */
+export const LAYOUT_TAGS = new InjectionToken>>('Angular Layout Tags');
+
+/** An internal-facing provider for the default flex tags */
+export const FLEX_PROVIDER = {
+ provide: LAYOUT_TAGS,
+ useValue: FLEX_TAGS,
+ multi: true,
+};
+
+/** An internal-facing provider for the default grid tags */
+export const GRID_PROVIDER = {
+ provide: LAYOUT_TAGS,
+ useValue: GRID_TAGS,
+ multi: true,
+};
+
+/** An internal-facing provider for the default tags */
+export const TAGS_PROVIDER = {
+ provide: LAYOUT_TAGS,
+ useValue: DEFAULT_TAGS,
+ multi: true,
+};
+
+/**
+ * An internal-facing injection token to consolidate all registered
+ * tags for use in the application.
+ */
+export const TAGS = new InjectionToken