Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

feat(flex): detect display precedence in fxLayout directive #1385

Merged
merged 1 commit into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions projects/libs/flex-layout/_private-utils/layout-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse'];
export function buildLayoutCSS(value: string) {
let [direction, wrap, isInline] = validateValue(value);
return buildCSS(direction, wrap, isInline);
}
}

/**
* Validate the value to be one of the acceptable value options
* Use default fallback of 'row'
*/
export function validateValue(value: string): [string, string, boolean] {
value = value ? value.toLowerCase() : '';
value = value?.toLowerCase() ?? '';
let [direction, wrap, inline] = value.split(' ');

// First value must be the `flex-direction`
Expand Down Expand Up @@ -84,9 +84,9 @@ export function validateWrapValue(value: string) {
*/
function buildCSS(direction: string, wrap: string | null = null, inline = false) {
return {
'display': inline ? 'inline-flex' : 'flex',
display: inline ? 'inline-flex' : 'flex',
'box-sizing': 'border-box',
'flex-direction': direction,
'flex-wrap': !!wrap ? wrap : null
'flex-wrap': wrap || null,
};
}
74 changes: 37 additions & 37 deletions projects/libs/flex-layout/core/style-utils/style-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ export class StyleUtils {
* Find the DOM element's raw attribute value (if any)
*/
lookupAttributeValue(element: HTMLElement, attribute: string): string {
return element.getAttribute(attribute) || '';
return element.getAttribute(attribute) ?? '';
}

/**
* Find the DOM element's inline style value (if any)
*/
lookupInlineStyle(element: HTMLElement, styleName: string): string {
return isPlatformBrowser(this._platformId) ?
element.style.getPropertyValue(styleName) : this._getServerStyle(element, styleName);
element.style.getPropertyValue(styleName) : getServerStyle(element, styleName);
}

/**
Expand Down Expand Up @@ -121,56 +121,56 @@ export class StyleUtils {
value = value ? value + '' : '';
if (isPlatformBrowser(this._platformId) || !this._serverModuleLoaded) {
isPlatformBrowser(this._platformId) ?
element.style.setProperty(key, value) : this._setServerStyle(element, key, value);
element.style.setProperty(key, value) : 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);
}
function getServerStyle(element: any, styleName: string): string {
const styleMap = readStyleAttribute(element);
return styleMap[styleName] ?? '';
}

private _getServerStyle(element: any, styleName: string): string {
const styleMap = this._readStyleAttribute(element);
return styleMap[styleName] || '';
}
function setServerStyle(element: any, styleName: string, styleValue?: string|null) {
styleName = styleName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
const styleMap = readStyleAttribute(element);
styleMap[styleName] = styleValue ?? '';
writeStyleAttribute(element, styleMap);
}

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();
}
}
function writeStyleAttribute(element: any, styleMap: {[name: string]: string}) {
let styleAttrValue = '';
for (const key in styleMap) {
const newValue = styleMap[key];
if (newValue) {
styleAttrValue += `${key}:${styleMap[key]};`;
}
return styleMap;
}
element.setAttribute('style', styleAttrValue);
}

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] + ';';
function 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();
}
}
element.setAttribute('style', styleAttrValue);
}
return styleMap;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions projects/libs/flex-layout/core/tokens/library-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface LayoutConfigOptions {
ssrObserveBreakpoints?: string[];
multiplier?: Multiplier;
defaultUnit?: string;
detectLayoutDisplay?: boolean;
}

export const DEFAULT_CONFIG: Required<LayoutConfigOptions> = {
Expand All @@ -38,6 +39,7 @@ export const DEFAULT_CONFIG: Required<LayoutConfigOptions> = {
// Instead, we disable it by default, which requires this ugly cast.
multiplier: undefined as unknown as Multiplier,
defaultUnit: 'px',
detectLayoutDisplay: false,
};

export const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(
Expand Down
10 changes: 9 additions & 1 deletion projects/libs/flex-layout/flex/layout/layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('layout directive', () => {

// Configure testbed to prepare services
TestBed.configureTestingModule({
imports: [CommonModule, FlexLayoutModule],
imports: [CommonModule, FlexLayoutModule.withConfig({detectLayoutDisplay: true})],
declarations: [TestLayoutComponent],
providers: [
MockMatchMediaProvider,
Expand All @@ -66,6 +66,14 @@ describe('layout directive', () => {
'box-sizing': 'border-box'
}, styler);
});
it('should not override pre-existing styles', () => {
createTestComponent(`<div fxLayout style="display: none;"></div>`);
expectNativeEl(fixture).toHaveStyle({
'display': 'none',
'flex-direction': 'row',
'box-sizing': 'border-box'
}, styler);
});
it('should add correct styles for `fxLayout="row wrap"` usage', () => {
createTestComponent(`<div fxLayout='row wrap'></div>`);
expectNativeEl(fixture).toHaveStyle({
Expand Down
34 changes: 28 additions & 6 deletions projects/libs/flex-layout/flex/layout/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,31 @@
* 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, OnChanges, Injectable} from '@angular/core';
import {Directive, ElementRef, OnChanges, Injectable, Inject} from '@angular/core';
import {
BaseDirective2,
StyleBuilder,
StyleDefinition,
StyleUtils,
MediaMarshaller,
LAYOUT_CONFIG,
LayoutConfigOptions,
} from '@angular/flex-layout/core';

import {buildLayoutCSS} from '@angular/flex-layout/_private-utils';

export interface LayoutStyleDisplay {
readonly display: string;
}

@Injectable({providedIn: 'root'})
export class LayoutStyleBuilder extends StyleBuilder {
buildStyles(input: string) {
return buildLayoutCSS(input);
buildStyles(input: string, {display}: LayoutStyleDisplay) {
const css = buildLayoutCSS(input);
return {
...css,
display: display === 'none' ? display : css.display,
};
}
}

Expand Down Expand Up @@ -51,17 +61,29 @@ export class LayoutDirective extends BaseDirective2 implements OnChanges {
constructor(elRef: ElementRef,
styleUtils: StyleUtils,
styleBuilder: LayoutStyleBuilder,
marshal: MediaMarshaller) {
marshal: MediaMarshaller,
@Inject(LAYOUT_CONFIG) private _config: LayoutConfigOptions) {
super(elRef, styleBuilder, styleUtils, marshal);
this.init();
}

protected styleCache = layoutCache;
protected updateWithValue(input: string) {
const detectLayoutDisplay = this._config.detectLayoutDisplay;
const display = detectLayoutDisplay ? this.styler.lookupStyle(this.nativeElement, 'display') : '';
this.styleCache = cacheMap.get(display) ?? new Map();
cacheMap.set(display, this.styleCache);

if (this.currentValue !== input) {
this.addStyles(input, {display});
this.currentValue = input;
}
}
}

@Directive({selector, inputs})
export class DefaultLayoutDirective extends LayoutDirective {
protected inputs = inputs;
}

const layoutCache: Map<string, StyleDefinition> = new Map();
type CacheMap = Map<string, StyleDefinition>;
const cacheMap = new Map<string, CacheMap>();