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

feat(layout): add wrap options support to fxLayout #207

Merged
merged 2 commits into from
Mar 8, 2017
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
21 changes: 18 additions & 3 deletions src/lib/flexbox/api/flex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
*/
import {Component, OnInit} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';

import {BreakPointsProvider} from '../../media-query/breakpoints/break-points';
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
import {MatchMedia} from '../../media-query/match-media';
import {FlexLayoutModule} from '../_module';

import {customMatchers, expect } from '../../utils/testing/custom-matchers';
import { _dom as _ } from '../../utils/testing/dom-tools';
import {customMatchers, expect} from '../../utils/testing/custom-matchers';
import {_dom as _} from '../../utils/testing/dom-tools';

import {
makeExpectDOMFrom,
Expand Down Expand Up @@ -118,6 +118,21 @@ describe('flex directive', () => {
}
});

it('should work with calc without internal whitespaces', async(() => {
// @see http://caniuse.com/#feat=calc for IE issues with calc()
if (!isIE) {
let fRef = componentWithTemplate('<div fxFlex="calc(75%-10px)"></div>');
fRef.detectChanges();

setTimeout(() => {
expectNativeEl(fRef).toHaveCssStyle({
'box-sizing': 'border-box',
'flex': '1 1 calc(75% - 10px)' // correct version has whitespace
});
});
}
}));

it('should work with "auto" values', () => {
expectDOMFrom(`<div fxFlex="auto"></div>`).toHaveCssStyle({
'flex': '1 1 auto'
Expand Down
45 changes: 8 additions & 37 deletions src/lib/flexbox/api/flex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {MediaMonitor} from '../../media-query/media-monitor';

import {LayoutDirective} from './layout';
import {LayoutWrapDirective} from './layout-wrap';
import {validateBasis} from '../../utils/basis-validator';


/** Built-in aliases for different flex-basis values. */
Expand Down Expand Up @@ -178,40 +179,9 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
flexBasis = this._mqActivation.activatedInput;
}

this._applyStyleToElement(this._validateValue.apply(this,
this._parseFlexParts(String(flexBasis))));
}

/**
* If the used the short-form `fxFlex="1 0 37%"`, then parse the parts
*/
protected _parseFlexParts(basis: string) {
basis = basis.replace(";", "");

let hasCalc = basis && basis.indexOf("calc") > -1;
let matches = !hasCalc ? basis.split(" ") : this._getPartsWithCalc(basis.trim());
return (matches.length === 3) ? matches : [this._queryInput("grow"),
this._queryInput("shrink"), basis];
}

/**
* Extract more complicated short-hand versions.
* e.g.
* fxFlex="3 3 calc(15em + 20px)"
*/
protected _getPartsWithCalc(value: string) {
let parts = [this._queryInput("grow"), this._queryInput("shrink"), value];
let j = value.indexOf('calc');

if (j > 0) {
parts[2] = value.substring(j);
let matches = value.substr(0, j).trim().split(" ");
if (matches.length == 2) {
parts[0] = matches[0];
parts[1] = matches[1];
}
}
return parts;
let basis = String(flexBasis).replace(";", "");
let parts = validateBasis(basis, this._queryInput("grow"), this._queryInput("shrink"));
this._applyStyleToElement(this._validateValue.apply(this, parts));
}

/**
Expand Down Expand Up @@ -277,10 +247,11 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
css = extendObject(clearStyles, {'flex': '0 0 auto'});
break;
default:
let isPercent = String(basis).indexOf('%') > -1;
let hasCalc = String(basis).indexOf('calc') > -1;
let isPercent = String(basis).indexOf('%') > -1 && !hasCalc;

isValue = String(basis).indexOf('px') > -1 ||
String(basis).indexOf('calc') > -1 ||
isValue = hasCalc ||
String(basis).indexOf('px') > -1 ||
String(basis).indexOf('em') > -1 ||
String(basis).indexOf('vw') > -1 ||
String(basis).indexOf('vh') > -1;
Expand Down
5 changes: 5 additions & 0 deletions src/lib/flexbox/api/layout-wrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ import {MediaMonitor} from '../../media-query/media-monitor';
import {LayoutDirective, LAYOUT_VALUES} from './layout';

/**
* @deprecated
* This functionality is now part of the `fxLayout` API
*
* 'layout-wrap' flexbox styling directive
* Defines wrapping of child elements in layout container
* Optional values: reverse, wrap-reverse, none, nowrap, wrap (default)]
*
*
* @see https://css-tricks.com/almanac/properties/f/flex-wrap/
*/
@Directive({selector: `
Expand Down
20 changes: 18 additions & 2 deletions src/lib/flexbox/api/layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ describe('layout directive', () => {
'box-sizing': 'border-box'
});
});
it('should add correct styles for `fxLayout="row wrap"` usage', () => {
expectDOMFrom(`
<div fxLayout="row wrap"></div>
`).toHaveCssStyle({
'display': 'flex',
'flex-direction': 'row',
'box-sizing': 'border-box',
'flex-wrap': "wrap"
});
});
it('should add correct styles for `fxLayout="column"` usage', () => {
expectDOMFrom(`<div fxLayout="column"></div>`).toHaveCssStyle({
'display': 'flex',
Expand Down Expand Up @@ -146,7 +156,7 @@ describe('layout directive', () => {
});
});
it('should add responsive styles when configured', () => {
fixture = createTestComponent(`<div fxLayout fxLayout.md="column"></div>`);
fixture = createTestComponent(`<div fxLayout fxLayout.md="column reverse-wrap"></div>`);

expectNativeEl(fixture).toHaveCssStyle({

Expand All @@ -159,7 +169,13 @@ describe('layout directive', () => {
expectNativeEl(fixture).toHaveCssStyle({
'display': 'flex',
'flex-direction': 'column',
'box-sizing': 'border-box'
'box-sizing': 'border-box',
'flex-wrap': 'wrap-reverse'
});

activateMediaQuery('lg');
expectNativeEl(fixture).not.toHaveCssStyle({
'flex-wrap': 'reverse-wrap'
});
});
it('should update responsive styles when the active mediaQuery changes', () => {
Expand Down
53 changes: 45 additions & 8 deletions src/lib/flexbox/api/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange
/**
* Validate the direction value and then update the host's inline flexbox styles
*/
protected _updateWithDirection(direction?: string) {
direction = direction || this._queryInput("layout") || 'row';
protected _updateWithDirection(value?: string) {
value = value || this._queryInput("layout") || 'row';
if (this._mqActivation) {
direction = this._mqActivation.activatedInput;
value = this._mqActivation.activatedInput;
}
direction = this._validateValue(direction);
let [direction, wrap] = this._validateValue(value);

// Update styles and announce to subscribers the *new* direction
this._applyStyleToElement(this._buildCSS(direction));
this._applyStyleToElement(this._buildCSS(direction, wrap));
this._announcer.next(direction);
}

Expand All @@ -135,8 +135,13 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange
* laid out and drawn inside that element's specified width and height.
*
*/
protected _buildCSS(value) {
return {'display': 'flex', 'box-sizing': 'border-box', 'flex-direction': value};
protected _buildCSS(direction, wrap = null) {
return {
'display': 'flex',
'box-sizing': 'border-box',
'flex-direction': direction,
'flex-wrap': !!wrap ? wrap : null
};
}

/**
Expand All @@ -145,6 +150,38 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange
*/
protected _validateValue(value) {
value = value ? value.toLowerCase() : '';
return LAYOUT_VALUES.find(x => x === value) ? value : LAYOUT_VALUES[0]; // "row"
let [ direction, wrap ] = value.split(" ");
if (!LAYOUT_VALUES.find(x => x === direction)) {
direction = LAYOUT_VALUES[0];
}
return [direction, this._validateWrapValue(wrap)];

}

/**
* Convert layout-wrap="<value>" to expected flex-wrap style
*/
protected _validateWrapValue(value) {
if (!!value) {
switch (value.toLowerCase()) {
case 'reverse':
case 'wrap-reverse':
case 'reverse-wrap':
value = 'wrap-reverse';
break;

case 'no':
case 'none':
case 'nowrap':
value = 'nowrap';
break;

// All other values fallback to "wrap"
default:
value = 'wrap';
break;
}
}
return value;
}
}
67 changes: 67 additions & 0 deletions src/lib/utils/basis-validator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license
* Copyright Google Inc. 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 {validateBasis} from './basis-validator';


describe('validateBasis', () => {

it('should validate single-value basis with default grow/shrink', () => {
let result = validateBasis('37px').join(" ");
expect( result ).toEqual('1 1 37px');
});

it('should validate single-value basis with custom grow and shrink', () => {
let result = validateBasis('37px', "2", "13").join(" ");
expect( result ).toEqual('2 13 37px');
});

it('should validate full `flex` value `2 1 37%`', () => {
let result = validateBasis('2 1 37%').join(" ");
expect( result ).toEqual('2 1 37%');
});

it('should validate with complex value that includes calc()', () => {
let result = validateBasis('3 3 calc(15em + 20px)').join(" ");
expect( result ).toEqual('3 3 calc(15em + 20px)');
});

it('should validate with complex value that includes a bad calc() expression', () => {
let result = validateBasis('3 3 calc(15em +20px)').join(" ");
expect( result ).toEqual('3 3 calc(15em + 20px)');
});

it('should validate with complex value that includes ONLY calc()', () => {
let result = validateBasis('calc(15em + 20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em + 20px)');
});

it('should validate with good calc(x + x) expression()', () => {
let result = validateBasis('calc(15em + 20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em + 20px)');
});

it('should validate with bad calc(x+x) expression()', () => {
let result = validateBasis('calc(15em+20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em + 20px)');
});

it('should validate with bad calc(x-x) expression()', () => {
let result = validateBasis('calc(15em-20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em - 20px)');
});

it('should validate with bad calc(x*x) expression()', () => {
let result = validateBasis('calc(15em*20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em * 20px)');
});

it('should validate with bad calc(x/x) expression()', () => {
let result = validateBasis('calc(15em/20px)').join(" ");
expect( result ).toEqual('1 1 calc(15em / 20px)');
});
});
52 changes: 52 additions & 0 deletions src/lib/utils/basis-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* The flex API permits 3 or 1 parts of the value:
* - `flex-grow flex-shrink flex-basis`, or
* - `flex-basis`
* Flex-Basis values can be complicated short-hand versions such as:
* - "3 3 calc(15em + 20px)"
* - "calc(15em + 20px)"
* - "calc(15em+20px)"
* - "37px"
* = "43%"
*/
export function validateBasis(basis: string, grow = "1", shrink = "1"): string[] {
let parts = [grow, shrink, basis];

let j = basis.indexOf('calc');
if (j > 0) {
parts[2] = _validateCalcValue(basis.substring(j).trim());
let matches = basis.substr(0, j).trim().split(" ");
if (matches.length == 2) {
parts[0] = matches[0];
parts[1] = matches[1];
}
} else if (j == 0) {
parts[2] = _validateCalcValue(basis.trim());
} else {
let matches = basis.split(" ");
parts = (matches.length === 3) ? matches : [
grow, shrink, basis
];
}

return parts;
}


/**
* Calc expressions require whitespace before & after the operator
* This is a simple, crude whitespace padding solution.
*/
function _validateCalcValue(calc: string): string {
let operators = ["+", "-", "*", "/"];
let findOperator = () => operators.reduce((index, operator) => {
return index || (calc.indexOf(operator) + 1);
}, 0);

if (findOperator() > 0) {
calc = calc.replace(/[\s]/g, "");
let offset = findOperator() - 1;
calc = calc.substr(0, offset) + " " + calc.charAt(offset) + " " + calc.substr(offset + 1);
}
return calc;
}