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

Commit 4d36b74

Browse files
authored
feat(core): add value multiplication suffix feature (#1383)
1 parent b11271f commit 4d36b74

File tree

7 files changed

+122
-17
lines changed

7 files changed

+122
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export interface Multiplier {
2+
readonly unit: string;
3+
readonly value: number;
4+
}
5+
6+
const MULTIPLIER_SUFFIX = 'x';
7+
8+
export function multiply(value: string, multiplier?: Multiplier): string {
9+
if (multiplier === undefined) {
10+
return value;
11+
}
12+
13+
const transformValue = (possibleValue: string) => {
14+
const numberValue = +(possibleValue.slice(0, -MULTIPLIER_SUFFIX.length));
15+
16+
if (value.endsWith(MULTIPLIER_SUFFIX) && !isNaN(numberValue)) {
17+
return `${numberValue * multiplier.value}${multiplier.unit}`;
18+
}
19+
20+
return value;
21+
};
22+
23+
return value.includes(' ') ?
24+
value.split(' ').map(transformValue).join(' ') : transformValue(value);
25+
}

projects/libs/flex-layout/core/public-api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export * from './style-builder/style-builder';
2929
export * from './basis-validator/basis-validator';
3030
export * from './media-marshaller/media-marshaller';
3131
export * from './media-marshaller/print-hook';
32+
export {Multiplier, multiply as ɵmultiply} from './multiply/multiplier';

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {InjectionToken} from '@angular/core';
9+
import {Multiplier} from '../multiply/multiplier';
910

1011
/** a set of configuration options for FlexLayoutModule */
1112
export interface LayoutConfigOptions {
@@ -18,9 +19,10 @@ export interface LayoutConfigOptions {
1819
printWithBreakpoints?: string[];
1920
mediaTriggerAutoRestore?: boolean;
2021
ssrObserveBreakpoints?: string[];
22+
multiplier?: Multiplier;
2123
}
2224

23-
export const DEFAULT_CONFIG: LayoutConfigOptions = {
25+
export const DEFAULT_CONFIG: Required<LayoutConfigOptions> = {
2426
addFlexToParent: true,
2527
addOrientationBps: false,
2628
disableDefaultBps: false,
@@ -30,6 +32,10 @@ export const DEFAULT_CONFIG: LayoutConfigOptions = {
3032
printWithBreakpoints: [],
3133
mediaTriggerAutoRestore: true,
3234
ssrObserveBreakpoints: [],
35+
// This is disabled by default because otherwise the multiplier would
36+
// run for all users, regardless of whether they're using this feature.
37+
// Instead, we disable it by default, which requires this ugly cast.
38+
multiplier: undefined as unknown as Multiplier,
3339
};
3440

3541
export const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ describe('flex-offset directive', () => {
4747

4848
// Configure testbed to prepare services
4949
TestBed.configureTestingModule({
50-
imports: [CommonModule, FlexLayoutModule],
50+
imports: [CommonModule, FlexLayoutModule.withConfig({
51+
multiplier: {
52+
value: 4,
53+
unit: 'px',
54+
},
55+
})],
5156
declarations: [TestFlexComponent],
5257
providers: [
5358
{provide: DIR_DOCUMENT, useValue: fakeDocument},
@@ -67,6 +72,15 @@ describe('flex-offset directive', () => {
6772
expectEl(dom).toHaveStyle({'flex': '1 1 0%'}, styler);
6873
});
6974

75+
it('should add correct styles for default `fxFlexOffset` usage w/ mulitplier', () => {
76+
componentWithTemplate(`<div fxFlexOffset='8x' fxFlex></div>`);
77+
fixture.detectChanges();
78+
79+
let dom = fixture.debugElement.children[0];
80+
expectEl(dom).toHaveStyle({'margin-left': '32px'}, styler);
81+
expectEl(dom).toHaveStyle({'flex': '1 1 0%'}, styler);
82+
});
83+
7084

7185
it('should work with percentage values', () => {
7286
componentWithTemplate(`<div fxFlexOffset='17' fxFlex='37'></div>`);

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

+13-7
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,21 @@
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 {Directionality} from '@angular/cdk/bidi';
1010
import {
1111
MediaMarshaller,
1212
BaseDirective2,
1313
StyleBuilder,
1414
StyleDefinition,
1515
StyleUtils,
16+
ɵmultiply as multiply,
17+
LAYOUT_CONFIG,
18+
LayoutConfigOptions,
1619
} from '@angular/flex-layout/core';
20+
import {isFlowHorizontal} from '@angular/flex-layout/_private-utils';
1721
import {takeUntil} from 'rxjs/operators';
1822

19-
import {isFlowHorizontal} from '@angular/flex-layout/_private-utils';
2023

2124
export interface FlexOffsetParent {
2225
layout: string;
@@ -25,18 +28,21 @@ export interface FlexOffsetParent {
2528

2629
@Injectable({providedIn: 'root'})
2730
export class FlexOffsetStyleBuilder extends StyleBuilder {
31+
constructor(@Inject(LAYOUT_CONFIG) private _config: LayoutConfigOptions) {
32+
super();
33+
}
34+
2835
buildStyles(offset: string, parent: FlexOffsetParent) {
29-
if (offset === '') {
30-
offset = '0';
31-
}
36+
offset ||= '0';
37+
offset = multiply(offset, this._config.multiplier);
3238
const isPercent = String(offset).indexOf('%') > -1;
3339
const isPx = String(offset).indexOf('px') > -1;
3440
if (!isPx && !isPercent && !isNaN(+offset)) {
35-
offset = offset + '%';
41+
offset = `${offset}%`;
3642
}
3743
const horizontalLayoutKey = parent.isRtl ? 'margin-right' : 'margin-left';
3844
const styles: StyleDefinition = isFlowHorizontal(parent.layout) ?
39-
{[horizontalLayoutKey]: `${offset}`} : {'margin-top': `${offset}`};
45+
{[horizontalLayoutKey]: offset} : {'margin-top': offset};
4046

4147
return styles;
4248
}

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

+49-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
import {Component, Injectable, OnInit, PLATFORM_ID} from '@angular/core';
99
import {CommonModule, isPlatformServer} from '@angular/common';
10-
import {TestBed, ComponentFixture, inject, async} from '@angular/core/testing';
10+
import {TestBed, ComponentFixture, inject, waitForAsync} from '@angular/core/testing';
1111
import {DIR_DOCUMENT} from '@angular/cdk/bidi';
1212
import {
1313
ɵMatchMedia as MatchMedia,
@@ -51,7 +51,12 @@ describe('layout-gap directive', () => {
5151

5252
// Configure testbed to prepare services
5353
TestBed.configureTestingModule({
54-
imports: [CommonModule, FlexLayoutModule],
54+
imports: [CommonModule, FlexLayoutModule.withConfig({
55+
multiplier: {
56+
value: 4,
57+
unit: 'px',
58+
}
59+
})],
5560
declarations: [TestLayoutGapComponent],
5661
providers: [
5762
MockMatchMediaProvider,
@@ -111,6 +116,25 @@ describe('layout-gap directive', () => {
111116
expectEl(nodes[2]).not.toHaveStyle({'margin-right': '0px'}, styler);
112117
});
113118

119+
it('should add gap styles to all children except the 1st child w/ multiplier', () => {
120+
let template = `
121+
<div fxLayoutAlign='center center' fxLayoutGap='13x'>
122+
<div fxFlex></div>
123+
<div fxFlex></div>
124+
<div fxFlex></div>
125+
</div>
126+
`;
127+
createTestComponent(template);
128+
fixture.detectChanges();
129+
130+
let nodes = queryFor(fixture, '[fxFlex]');
131+
expect(nodes.length).toEqual(3);
132+
expectEl(nodes[0]).toHaveStyle({'margin-right': '52px'}, styler);
133+
expectEl(nodes[1]).toHaveStyle({'margin-right': '52px'}, styler);
134+
expectEl(nodes[2]).not.toHaveStyle({'margin-right': '52px'}, styler);
135+
expectEl(nodes[2]).not.toHaveStyle({'margin-right': '0px'}, styler);
136+
});
137+
114138
it('should add gap styles in proper order when order style is applied', () => {
115139
let template = `
116140
<div fxLayoutAlign='center center' fxLayoutGap='13px'>
@@ -149,7 +173,7 @@ describe('layout-gap directive', () => {
149173
expectEl(nodes[3]).not.toHaveStyle({'margin-right': '0px'}, styler);
150174
});
151175

152-
it('should add update gap styles when row items are removed', async(() => {
176+
it('should add update gap styles when row items are removed', waitForAsync(() => {
153177
let template = `
154178
<div fxLayoutAlign='center center' fxLayoutGap='13px'>
155179
<div fxFlex *ngFor='let row of rows'></div>
@@ -181,7 +205,7 @@ describe('layout-gap directive', () => {
181205

182206
}));
183207

184-
it('should add update gap styles when only 1 row is remaining', async(() => {
208+
it('should add update gap styles when only 1 row is remaining', waitForAsync(() => {
185209
let template = `
186210
<div fxLayoutAlign='center center' fxLayoutGap='13px'>
187211
<div fxFlex *ngFor='let row of rows'></div>
@@ -509,6 +533,27 @@ describe('layout-gap directive', () => {
509533
expectNativeEl(fixture).toHaveStyle(expectedMargin, styler);
510534
});
511535

536+
it('should add gap styles correctly w/ multiplier', () => {
537+
let template = `
538+
<div fxLayoutGap='13x grid'>
539+
<div fxFlex></div>
540+
<div fxFlex></div>
541+
<div fxFlex></div>
542+
</div>
543+
`;
544+
createTestComponent(template);
545+
fixture.detectChanges();
546+
547+
let nodes = queryFor(fixture, '[fxFlex]');
548+
let expectedMargin = {'margin': '0px -52px -52px 0px'};
549+
let expectedPadding = {'padding': '0px 52px 52px 0px'};
550+
expect(nodes.length).toEqual(3);
551+
expectEl(nodes[0]).toHaveStyle(expectedPadding, styler);
552+
expectEl(nodes[1]).toHaveStyle(expectedPadding, styler);
553+
expectEl(nodes[2]).toHaveStyle(expectedPadding, styler);
554+
expectNativeEl(fixture).toHaveStyle(expectedMargin, styler);
555+
});
556+
512557
it('should add gap styles correctly between option', () => {
513558
let template = `
514559
<div fxLayoutGap='13px 12px grid'>

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
NgZone,
1313
Injectable,
1414
AfterContentInit,
15+
Inject,
1516
} from '@angular/core';
1617
import {Directionality} from '@angular/cdk/bidi';
1718
import {
@@ -21,11 +22,14 @@ import {
2122
StyleUtils,
2223
MediaMarshaller,
2324
ElementMatcher,
25+
LAYOUT_CONFIG,
26+
LayoutConfigOptions,
27+
ɵmultiply as multiply,
2428
} from '@angular/flex-layout/core';
29+
import {LAYOUT_VALUES} from '@angular/flex-layout/_private-utils';
2530
import {Subject} from 'rxjs';
2631
import {takeUntil} from 'rxjs/operators';
2732

28-
import {LAYOUT_VALUES} from '@angular/flex-layout/_private-utils';
2933

3034
export interface LayoutGapParent {
3135
directionality: string;
@@ -42,13 +46,15 @@ const CLEAR_MARGIN_CSS = {
4246

4347
@Injectable({providedIn: 'root'})
4448
export class LayoutGapStyleBuilder extends StyleBuilder {
45-
constructor(private _styler: StyleUtils) {
49+
constructor(private _styler: StyleUtils,
50+
@Inject(LAYOUT_CONFIG) private _config: LayoutConfigOptions) {
4651
super();
4752
}
4853

4954
buildStyles(gapValue: string, parent: LayoutGapParent) {
5055
if (gapValue.endsWith(GRID_SPECIFIER)) {
5156
gapValue = gapValue.slice(0, gapValue.indexOf(GRID_SPECIFIER));
57+
gapValue = multiply(gapValue, this._config.multiplier);
5258

5359
// Add the margin to the host element
5460
return buildGridMargin(gapValue, parent.directionality);
@@ -61,10 +67,12 @@ export class LayoutGapStyleBuilder extends StyleBuilder {
6167
const items = parent.items;
6268
if (gapValue.endsWith(GRID_SPECIFIER)) {
6369
gapValue = gapValue.slice(0, gapValue.indexOf(GRID_SPECIFIER));
70+
gapValue = multiply(gapValue, this._config.multiplier);
6471
// For each `element` children, set the padding
6572
const paddingStyles = buildGridPadding(gapValue, parent.directionality);
6673
this._styler.applyStyleToElements(paddingStyles, parent.items);
6774
} else {
75+
gapValue = multiply(gapValue, this._config.multiplier);
6876
const lastItem = items.pop()!;
6977

7078
// For each `element` children EXCEPT the last,
@@ -253,7 +261,7 @@ const GRID_SPECIFIER = ' grid';
253261

254262
function buildGridPadding(value: string, directionality: string): StyleDefinition {
255263
const [between, below] = value.split(' ');
256-
const bottom = below || between;
264+
const bottom = below ?? between;
257265
let paddingRight = '0px', paddingBottom = bottom, paddingLeft = '0px';
258266

259267
if (directionality === 'rtl') {
@@ -267,7 +275,7 @@ function buildGridPadding(value: string, directionality: string): StyleDefinitio
267275

268276
function buildGridMargin(value: string, directionality: string): StyleDefinition {
269277
const [between, below] = value.split(' ');
270-
const bottom = below || between;
278+
const bottom = below ?? between;
271279
const minus = (str: string) => `-${str}`;
272280
let marginRight = '0px', marginBottom = minus(bottom), marginLeft = '0px';
273281

0 commit comments

Comments
 (0)