Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 40b4c9a

Browse filesBrowse files
committed
refactor(grid): signal inputs, host bindings, cleanup, tests
1 parent fe69aa8 commit 40b4c9a
Copy full SHA for 40b4c9a

File tree

Expand file treeCollapse file tree

9 files changed

+247
-138
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+247
-138
lines changed
+55-3Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,60 @@
1-
import { ColDirective } from './col.directive';
1+
import { Component, DebugElement } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
import { ColDirective, ColOffsetType, ColOrderType } from './col.directive';
5+
6+
@Component({
7+
imports: [ColDirective],
8+
template: `
9+
<div id="col0" cCol lg="auto" [xl]="true"></div>
10+
<div id="col1" cCol [order]="1" [offset]="1" xs="6" sm="5" md="4" lg="3" xl="2" xxl="1"></div>
11+
<div id="col2" [cCol]="col" [order]="order" [offset]="offset"></div>
12+
`
13+
})
14+
export class TestComponent {
15+
col!: number;
16+
offset: ColOffsetType = { md: 2, xs: 1 };
17+
order: ColOrderType = { xl: 'first', xxl: 'last', md: 1, xs: 1 };
18+
}
219

320
describe('ColDirective', () => {
21+
let fixture: ComponentFixture<TestComponent>;
22+
let debugElement: DebugElement;
23+
24+
beforeEach(() => {
25+
TestBed.configureTestingModule({
26+
imports: [TestComponent]
27+
});
28+
fixture = TestBed.createComponent(TestComponent);
29+
fixture.detectChanges();
30+
});
31+
432
it('should create an instance', () => {
5-
const directive = new ColDirective();
6-
expect(directive).toBeTruthy();
33+
TestBed.runInInjectionContext(() => {
34+
const directive = new ColDirective();
35+
expect(directive).toBeTruthy();
36+
});
37+
});
38+
39+
it('should have css class', () => {
40+
debugElement = fixture.debugElement.query(By.css('#col0'));
41+
expect(debugElement.nativeElement).toHaveClass('col');
42+
expect(debugElement.nativeElement).toHaveClass('col-lg-auto');
43+
expect(debugElement.nativeElement).toHaveClass('col-xl');
44+
debugElement = fixture.debugElement.query(By.css('#col1'));
45+
expect(debugElement.nativeElement).toHaveClass('col-6');
46+
expect(debugElement.nativeElement).toHaveClass('order-1');
47+
expect(debugElement.nativeElement).toHaveClass('offset-1');
48+
expect(debugElement.nativeElement).toHaveClass('col-sm-5');
49+
expect(debugElement.nativeElement).toHaveClass('col-md-4');
50+
expect(debugElement.nativeElement).toHaveClass('col-lg-3');
51+
expect(debugElement.nativeElement).toHaveClass('col-xl-2');
52+
expect(debugElement.nativeElement).toHaveClass('col-xxl-1');
53+
debugElement = fixture.debugElement.query(By.css('#col2'));
54+
expect(debugElement.nativeElement).toHaveClass('col');
55+
expect(debugElement.nativeElement).toHaveClass('offset-md-2');
56+
expect(debugElement.nativeElement).toHaveClass('order-md-1');
57+
expect(debugElement.nativeElement).toHaveClass('order-xl-first');
58+
expect(debugElement.nativeElement).toHaveClass('order-xxl-last');
759
});
860
});
+61-85Lines changed: 61 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
import { Directive, HostBinding, Input } from '@angular/core';
2-
import { BooleanInput, coerceBooleanProperty, coerceNumberProperty, NumberInput } from '@angular/cdk/coercion';
3-
4-
import { ColOrder, ICol } from './col.type';
1+
import { booleanAttribute, computed, Directive, input, numberAttribute } from '@angular/core';
2+
import { BooleanInput, NumberInput } from '@angular/cdk/coercion';
53
import { BreakpointInfix } from '../coreui.types';
4+
import { ColOrder } from './col.type';
5+
6+
export type ColOffsetType = number | { xs?: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number };
7+
export type ColOrderType =
8+
| ColOrder
9+
| { xs?: ColOrder; sm?: ColOrder; md?: ColOrder; lg?: ColOrder; xl?: ColOrder; xxl?: ColOrder };
610

711
@Directive({
8-
selector: '[cCol]'
12+
selector: '[cCol]',
13+
host: {
14+
'[class]': 'hostClasses()'
15+
}
916
})
10-
export class ColDirective implements ICol {
17+
export class ColDirective {
1118
static ngAcceptInputType_cCol: BooleanInput | NumberInput;
1219
static ngAcceptInputType_xs: BooleanInput | NumberInput;
1320
static ngAcceptInputType_sm: BooleanInput | NumberInput;
@@ -18,142 +25,111 @@ export class ColDirective implements ICol {
1825

1926
/**
2027
* The number of columns/offset/order on extra small devices (<576px).
21-
* @type { 'auto' | number | boolean }
28+
* @return { 'auto' | number | boolean }
2229
*/
23-
@Input()
24-
set cCol(value: BooleanInput | NumberInput) {
25-
this.xs = this.xs || this.coerceInput(value);
26-
}
27-
@Input()
28-
set xs(value) {
29-
this._xs = this.coerceInput(value);
30-
}
31-
get xs(): BooleanInput | NumberInput {
32-
return this._xs;
33-
}
34-
private _xs: BooleanInput | NumberInput = false;
30+
readonly cCol = input(false, { transform: this.coerceInput });
31+
readonly xs = input(false, { transform: this.coerceInput });
3532

3633
/**
3734
* The number of columns/offset/order on small devices (<768px).
38-
* @type { 'auto' | number | boolean }
35+
* @return { 'auto' | number | boolean }
3936
*/
40-
@Input()
41-
set sm(value) {
42-
this._sm = this.coerceInput(value);
43-
}
44-
get sm(): BooleanInput | NumberInput {
45-
return this._sm;
46-
}
47-
private _sm: BooleanInput | NumberInput = false;
37+
readonly sm = input(false, { transform: this.coerceInput });
4838

4939
/**
5040
* The number of columns/offset/order on medium devices (<992px).
51-
* @type { 'auto' | number | boolean }
41+
* @return { 'auto' | number | boolean }
5242
*/
53-
@Input()
54-
set md(value) {
55-
this._md = this.coerceInput(value);
56-
}
57-
get md(): BooleanInput | NumberInput {
58-
return this._md;
59-
}
60-
private _md: BooleanInput | NumberInput = false;
43+
readonly md = input(false, { transform: this.coerceInput });
6144

6245
/**
6346
* The number of columns/offset/order on large devices (<1200px).
64-
* @type { 'auto' | number | boolean }
47+
* @return { 'auto' | number | boolean }
6548
*/
66-
@Input()
67-
set lg(value) {
68-
this._lg = this.coerceInput(value);
69-
}
70-
get lg(): BooleanInput | NumberInput {
71-
return this._lg;
72-
}
73-
private _lg: BooleanInput | NumberInput = false;
49+
readonly lg = input(false, { transform: this.coerceInput });
7450

7551
/**
7652
* The number of columns/offset/order on X-Large devices (<1400px).
77-
* @type { 'auto' | number | boolean }
53+
* @return { 'auto' | number | boolean }
7854
*/
79-
@Input()
80-
set xl(value) {
81-
this._xl = this.coerceInput(value);
82-
}
83-
get xl(): BooleanInput | NumberInput {
84-
return this._xl;
85-
}
86-
private _xl: BooleanInput | NumberInput = false;
55+
readonly xl = input(false, { transform: this.coerceInput });
8756

8857
/**
8958
* The number of columns/offset/order on XX-Large devices (≥1400px).
90-
* @type { 'auto' | number | boolean }
59+
* @return { 'auto' | number | boolean }
9160
*/
92-
@Input()
93-
set xxl(value) {
94-
this._xxl = this.coerceInput(value);
95-
}
96-
get xxl(): BooleanInput | NumberInput {
97-
return this._xxl;
98-
}
99-
private _xxl: BooleanInput | NumberInput = false;
61+
readonly xxl = input(false, { transform: this.coerceInput });
62+
63+
readonly breakpoints = computed(() => {
64+
return {
65+
xs: this.xs() || this.cCol(),
66+
sm: this.sm(),
67+
md: this.md(),
68+
lg: this.lg(),
69+
xl: this.xl(),
70+
xxl: this.xxl()
71+
} as Record<string, any>;
72+
});
10073

101-
@Input() offset?: number | { xs?: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number };
102-
@Input() order?:
103-
| ColOrder
104-
| { xs?: ColOrder; sm?: ColOrder; md?: ColOrder; lg?: ColOrder; xl?: ColOrder; xxl?: ColOrder };
74+
readonly offset = input<ColOffsetType>();
75+
readonly order = input<ColOrderType>();
10576

106-
@HostBinding('class')
107-
get hostClasses(): any {
108-
const classes: any = {
77+
readonly hostClasses = computed(() => {
78+
const classes: Record<string, boolean> = {
10979
col: true
11080
};
11181

82+
const breakpoints = this.breakpoints();
83+
const offsetInput = this.offset();
84+
const orderInput = this.order();
85+
11286
Object.keys(BreakpointInfix).forEach((breakpoint) => {
113-
// @ts-ignore
114-
const value: number | string | boolean = this[breakpoint];
87+
const value = breakpoints[breakpoint];
11588
const infix = breakpoint === 'xs' ? '' : `-${breakpoint}`;
11689
classes[`col${infix}`] = value === true;
11790
classes[`col${infix}-${value}`] = typeof value === 'number' || typeof value === 'string';
11891
});
11992

120-
if (typeof this.offset === 'object') {
121-
const offset = { ...this.offset };
93+
if (typeof offsetInput === 'object') {
94+
const offset = { ...offsetInput };
12295
Object.entries(offset).forEach((entry) => {
12396
const [breakpoint, value] = [...entry];
12497
const infix = breakpoint === 'xs' ? '' : `-${breakpoint}`;
12598
classes[`offset${infix}-${value}`] = value >= 0 && value <= 11;
12699
});
127100
} else {
128-
classes[`offset-${this.offset}`] = typeof this.offset === 'number' && this.offset > 0 && this.offset <= 11;
101+
const offset = numberAttribute(offsetInput);
102+
classes[`offset-${offset}`] = typeof offset === 'number' && offset > 0 && offset <= 11;
129103
}
130104

131-
if (typeof this.order === 'object') {
132-
const order = { ...this.order };
105+
if (typeof orderInput === 'object') {
106+
const order = { ...orderInput };
133107
Object.entries(order).forEach((entry) => {
134108
const [breakpoint, value] = [...entry];
135109
const infix = breakpoint === 'xs' ? '' : `-${breakpoint}`;
136-
classes[`order${infix}-${value}`] = value;
110+
classes[`order${infix}-${value}`] = !!value;
137111
});
138112
} else {
139-
classes[`order-${this.order}`] = !!this.order;
113+
const order = orderInput;
114+
classes[`order-${order}`] = !!order;
140115
}
141116

142117
// if there is no 'col' class, add one
143-
classes.col = !Object.entries(classes).filter((i) => i[0].startsWith('col-') && i[1]).length || this.xs === true;
144-
return classes;
145-
}
118+
classes['col'] =
119+
!Object.entries(classes).filter((i) => i[0].startsWith('col-') && i[1]).length || breakpoints['xs'] === true;
120+
return classes as Record<string, boolean>;
121+
});
146122

147123
coerceInput(value: BooleanInput | NumberInput) {
148124
if (value === 'auto') {
149125
return value;
150126
}
151127
if (value === '' || value === undefined || value === null) {
152-
return coerceBooleanProperty(value);
128+
return booleanAttribute(value);
153129
}
154130
if (typeof value === 'boolean') {
155131
return value;
156132
}
157-
return coerceNumberProperty(value);
133+
return numberAttribute(value);
158134
}
159135
}
+10-2Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
22

33
import { ContainerComponent } from './container.component';
4+
import { ComponentRef } from '@angular/core';
45

56
describe('ContainerComponent', () => {
67
let component: ContainerComponent;
8+
let componentRef: ComponentRef<ContainerComponent>;
79
let fixture: ComponentFixture<ContainerComponent>;
810

911
beforeEach(waitForAsync(() => {
1012
TestBed.configureTestingModule({
1113
imports: [ContainerComponent]
12-
})
13-
.compileComponents();
14+
}).compileComponents();
1415
}));
1516

1617
beforeEach(() => {
1718
fixture = TestBed.createComponent(ContainerComponent);
1819
component = fixture.componentInstance;
20+
componentRef = fixture.componentRef;
1921
fixture.detectChanges();
2022
});
2123

@@ -25,5 +27,11 @@ describe('ContainerComponent', () => {
2527

2628
it('should have css classes', () => {
2729
expect(fixture.nativeElement).toHaveClass('container');
30+
expect(fixture.nativeElement).not.toHaveClass('container-fluid');
31+
expect(fixture.nativeElement).not.toHaveClass('container-xl');
32+
componentRef.setInput('fluid', true);
33+
componentRef.setInput('breakpoint', 'xl');
34+
fixture.detectChanges();
35+
expect(fixture.nativeElement).toHaveClass('container-xl');
2836
});
2937
});

‎projects/coreui-angular/src/lib/grid/container.component.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/grid/container.component.ts
+5-9Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { booleanAttribute, Component, computed, input, InputSignal, InputSignalWithTransform } from '@angular/core';
2-
3-
import { IContainer } from './container.type';
1+
import { booleanAttribute, Component, computed, input } from '@angular/core';
42
import { Breakpoints } from '../coreui.types';
53

64
@Component({
@@ -9,19 +7,17 @@ import { Breakpoints } from '../coreui.types';
97
styleUrls: ['./container.component.scss'],
108
host: { '[class]': 'hostClasses()' }
119
})
12-
export class ContainerComponent implements IContainer {
10+
export class ContainerComponent {
1311
/**
1412
* Set container 100% wide until a breakpoint.
1513
*/
16-
readonly breakpoint: InputSignal<Exclude<Breakpoints, 'xs'>> = input<Exclude<Breakpoints, 'xs'>>('');
14+
readonly breakpoint = input<Exclude<Breakpoints, 'xs'>>('');
1715

1816
/**
1917
* Set container 100% wide, spanning the entire width of the viewport.
20-
* @type InputSignalWithTransform<unknown, boolean>
18+
* @return boolean
2119
*/
22-
readonly fluid: InputSignalWithTransform<unknown, boolean> = input<unknown, boolean>(false, {
23-
transform: booleanAttribute
24-
});
20+
readonly fluid = input(false, { transform: booleanAttribute });
2521

2622
readonly hostClasses = computed(() => {
2723
const breakpoint = this.breakpoint();
+39-2Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
1+
import { Component, DebugElement } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
14
import { GutterDirective } from './gutter.directive';
5+
import { GutterBreakpoints, Gutters, IGutterObject } from './gutter.type';
6+
7+
@Component({
8+
imports: [GutterDirective],
9+
template: '<div [gutter]="gutter"></div>'
10+
})
11+
export class TestComponent {
12+
gutter: IGutterObject | GutterBreakpoints | Gutters = 5;
13+
}
214

315
describe('GutterDirective', () => {
16+
let fixture: ComponentFixture<TestComponent>;
17+
let debugElement: DebugElement;
18+
19+
beforeEach(() => {
20+
TestBed.configureTestingModule({
21+
imports: [TestComponent]
22+
});
23+
fixture = TestBed.createComponent(TestComponent);
24+
debugElement = fixture.debugElement.query(By.directive(GutterDirective));
25+
fixture.detectChanges();
26+
});
27+
428
it('should create an instance', () => {
5-
const directive = new GutterDirective();
6-
expect(directive).toBeTruthy();
29+
TestBed.runInInjectionContext(() => {
30+
const directive = new GutterDirective();
31+
expect(directive).toBeTruthy();
32+
});
33+
});
34+
35+
it('should have css class', () => {
36+
expect(debugElement.nativeElement).toHaveClass('g-5');
37+
fixture.componentInstance.gutter = { gx: 2, gy: 1 };
38+
fixture.detectChanges();
39+
expect(debugElement.nativeElement).toHaveClass('gx-2');
40+
expect(debugElement.nativeElement).toHaveClass('gy-1');
41+
fixture.componentInstance.gutter = { md: { g: 3 } };
42+
fixture.detectChanges();
43+
expect(debugElement.nativeElement).toHaveClass('g-md-3');
744
});
845
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.