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

Commit f8b1607

Browse files
authored
feat(flex): detect display precedence in fxLayout directive (#1385)
1 parent 80b4e5a commit f8b1607

File tree

5 files changed

+80
-48
lines changed

5 files changed

+80
-48
lines changed

projects/libs/flex-layout/_private-utils/layout-validator.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse'];
1414
export function buildLayoutCSS(value: string) {
1515
let [direction, wrap, isInline] = validateValue(value);
1616
return buildCSS(direction, wrap, isInline);
17-
}
17+
}
1818

1919
/**
2020
* Validate the value to be one of the acceptable value options
2121
* Use default fallback of 'row'
2222
*/
2323
export function validateValue(value: string): [string, string, boolean] {
24-
value = value ? value.toLowerCase() : '';
24+
value = value?.toLowerCase() ?? '';
2525
let [direction, wrap, inline] = value.split(' ');
2626

2727
// First value must be the `flex-direction`
@@ -84,9 +84,9 @@ export function validateWrapValue(value: string) {
8484
*/
8585
function buildCSS(direction: string, wrap: string | null = null, inline = false) {
8686
return {
87-
'display': inline ? 'inline-flex' : 'flex',
87+
display: inline ? 'inline-flex' : 'flex',
8888
'box-sizing': 'border-box',
8989
'flex-direction': direction,
90-
'flex-wrap': !!wrap ? wrap : null
90+
'flex-wrap': wrap || null,
9191
};
9292
}

projects/libs/flex-layout/core/style-utils/style-utils.ts

+37-37
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ export class StyleUtils {
6969
* Find the DOM element's raw attribute value (if any)
7070
*/
7171
lookupAttributeValue(element: HTMLElement, attribute: string): string {
72-
return element.getAttribute(attribute) || '';
72+
return element.getAttribute(attribute) ?? '';
7373
}
7474

7575
/**
7676
* Find the DOM element's inline style value (if any)
7777
*/
7878
lookupInlineStyle(element: HTMLElement, styleName: string): string {
7979
return isPlatformBrowser(this._platformId) ?
80-
element.style.getPropertyValue(styleName) : this._getServerStyle(element, styleName);
80+
element.style.getPropertyValue(styleName) : getServerStyle(element, styleName);
8181
}
8282

8383
/**
@@ -121,56 +121,56 @@ export class StyleUtils {
121121
value = value ? value + '' : '';
122122
if (isPlatformBrowser(this._platformId) || !this._serverModuleLoaded) {
123123
isPlatformBrowser(this._platformId) ?
124-
element.style.setProperty(key, value) : this._setServerStyle(element, key, value);
124+
element.style.setProperty(key, value) : setServerStyle(element, key, value);
125125
} else {
126126
this._serverStylesheet.addStyleToElement(element, key, value);
127127
}
128128
}
129129
});
130130
}
131+
}
131132

132-
private _setServerStyle(element: any, styleName: string, styleValue?: string|null) {
133-
styleName = styleName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
134-
const styleMap = this._readStyleAttribute(element);
135-
styleMap[styleName] = styleValue || '';
136-
this._writeStyleAttribute(element, styleMap);
137-
}
133+
function getServerStyle(element: any, styleName: string): string {
134+
const styleMap = readStyleAttribute(element);
135+
return styleMap[styleName] ?? '';
136+
}
138137

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

144-
private _readStyleAttribute(element: any): {[name: string]: string} {
145-
const styleMap: {[name: string]: string} = {};
146-
const styleAttribute = element.getAttribute('style');
147-
if (styleAttribute) {
148-
const styleList = styleAttribute.split(/;+/g);
149-
for (let i = 0; i < styleList.length; i++) {
150-
const style = styleList[i].trim();
151-
if (style.length > 0) {
152-
const colonIndex = style.indexOf(':');
153-
if (colonIndex === -1) {
154-
throw new Error(`Invalid CSS style: ${style}`);
155-
}
156-
const name = style.substr(0, colonIndex).trim();
157-
styleMap[name] = style.substr(colonIndex + 1).trim();
158-
}
159-
}
145+
function writeStyleAttribute(element: any, styleMap: {[name: string]: string}) {
146+
let styleAttrValue = '';
147+
for (const key in styleMap) {
148+
const newValue = styleMap[key];
149+
if (newValue) {
150+
styleAttrValue += `${key}:${styleMap[key]};`;
160151
}
161-
return styleMap;
162152
}
153+
element.setAttribute('style', styleAttrValue);
154+
}
163155

164-
private _writeStyleAttribute(element: any, styleMap: {[name: string]: string}) {
165-
let styleAttrValue = '';
166-
for (const key in styleMap) {
167-
const newValue = styleMap[key];
168-
if (newValue) {
169-
styleAttrValue += key + ':' + styleMap[key] + ';';
156+
function readStyleAttribute(element: any): {[name: string]: string} {
157+
const styleMap: {[name: string]: string} = {};
158+
const styleAttribute = element.getAttribute('style');
159+
if (styleAttribute) {
160+
const styleList = styleAttribute.split(/;+/g);
161+
for (let i = 0; i < styleList.length; i++) {
162+
const style = styleList[i].trim();
163+
if (style.length > 0) {
164+
const colonIndex = style.indexOf(':');
165+
if (colonIndex === -1) {
166+
throw new Error(`Invalid CSS style: ${style}`);
167+
}
168+
const name = style.substr(0, colonIndex).trim();
169+
styleMap[name] = style.substr(colonIndex + 1).trim();
170170
}
171171
}
172-
element.setAttribute('style', styleAttrValue);
173172
}
173+
return styleMap;
174174
}
175175

176176
/**

projects/libs/flex-layout/core/tokens/library-config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface LayoutConfigOptions {
2121
ssrObserveBreakpoints?: string[];
2222
multiplier?: Multiplier;
2323
defaultUnit?: string;
24+
detectLayoutDisplay?: boolean;
2425
}
2526

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

4345
export const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(

projects/libs/flex-layout/flex/layout/layout.spec.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('layout directive', () => {
3939

4040
// Configure testbed to prepare services
4141
TestBed.configureTestingModule({
42-
imports: [CommonModule, FlexLayoutModule],
42+
imports: [CommonModule, FlexLayoutModule.withConfig({detectLayoutDisplay: true})],
4343
declarations: [TestLayoutComponent],
4444
providers: [
4545
MockMatchMediaProvider,
@@ -66,6 +66,14 @@ describe('layout directive', () => {
6666
'box-sizing': 'border-box'
6767
}, styler);
6868
});
69+
it('should not override pre-existing styles', () => {
70+
createTestComponent(`<div fxLayout style="display: none;"></div>`);
71+
expectNativeEl(fixture).toHaveStyle({
72+
'display': 'none',
73+
'flex-direction': 'row',
74+
'box-sizing': 'border-box'
75+
}, styler);
76+
});
6977
it('should add correct styles for `fxLayout="row wrap"` usage', () => {
7078
createTestComponent(`<div fxLayout='row wrap'></div>`);
7179
expectNativeEl(fixture).toHaveStyle({

projects/libs/flex-layout/flex/layout/layout.ts

+28-6
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,31 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {Directive, ElementRef, OnChanges, Injectable} from '@angular/core';
8+
import {Directive, ElementRef, OnChanges, Injectable, Inject} from '@angular/core';
99
import {
1010
BaseDirective2,
1111
StyleBuilder,
1212
StyleDefinition,
1313
StyleUtils,
1414
MediaMarshaller,
15+
LAYOUT_CONFIG,
16+
LayoutConfigOptions,
1517
} from '@angular/flex-layout/core';
1618

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

21+
export interface LayoutStyleDisplay {
22+
readonly display: string;
23+
}
24+
1925
@Injectable({providedIn: 'root'})
2026
export class LayoutStyleBuilder extends StyleBuilder {
21-
buildStyles(input: string) {
22-
return buildLayoutCSS(input);
27+
buildStyles(input: string, {display}: LayoutStyleDisplay) {
28+
const css = buildLayoutCSS(input);
29+
return {
30+
...css,
31+
display: display === 'none' ? display : css.display,
32+
};
2333
}
2434
}
2535

@@ -51,17 +61,29 @@ export class LayoutDirective extends BaseDirective2 implements OnChanges {
5161
constructor(elRef: ElementRef,
5262
styleUtils: StyleUtils,
5363
styleBuilder: LayoutStyleBuilder,
54-
marshal: MediaMarshaller) {
64+
marshal: MediaMarshaller,
65+
@Inject(LAYOUT_CONFIG) private _config: LayoutConfigOptions) {
5566
super(elRef, styleBuilder, styleUtils, marshal);
5667
this.init();
5768
}
5869

59-
protected styleCache = layoutCache;
70+
protected updateWithValue(input: string) {
71+
const detectLayoutDisplay = this._config.detectLayoutDisplay;
72+
const display = detectLayoutDisplay ? this.styler.lookupStyle(this.nativeElement, 'display') : '';
73+
this.styleCache = cacheMap.get(display) ?? new Map();
74+
cacheMap.set(display, this.styleCache);
75+
76+
if (this.currentValue !== input) {
77+
this.addStyles(input, {display});
78+
this.currentValue = input;
79+
}
80+
}
6081
}
6182

6283
@Directive({selector, inputs})
6384
export class DefaultLayoutDirective extends LayoutDirective {
6485
protected inputs = inputs;
6586
}
6687

67-
const layoutCache: Map<string, StyleDefinition> = new Map();
88+
type CacheMap = Map<string, StyleDefinition>;
89+
const cacheMap = new Map<string, CacheMap>();

0 commit comments

Comments
 (0)