• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / cd64db52-e563-41a3-85f3-a0adb87ce135

30 Oct 2024 08:03AM UTC coverage: 90.402% (-0.04%) from 90.438%
cd64db52-e563-41a3-85f3-a0adb87ce135

push

circleci

web-flow
refactor: refactor constructor to the inject function (#3222)

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

422 of 429 new or added lines in 170 files covered. (98.37%)

344 existing lines in 81 files now uncovered.

13184 of 13941 relevant lines covered (94.57%)

997.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.39
/src/grid/thy-grid.component.ts
1
import { ViewportRuler } from '@angular/cdk/scrolling';
2
import {
3
    AfterContentInit,
4
    ChangeDetectionStrategy,
5
    Component,
6
    ContentChildren,
7
    Directive,
8
    ElementRef,
9
    Input,
10
    NgZone,
1✔
11
    OnChanges,
1✔
12
    OnInit,
1✔
13
    QueryList,
14
    SimpleChanges,
15
    inject
16
} from '@angular/core';
17
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
18
import { Observable, Subject } from 'rxjs';
19
import { throttleTime } from 'rxjs/operators';
20
import { ThyGridToken, THY_GRID_COMPONENT } from './grid.token';
21
import { ThyGridItem } from './thy-grid-item.component';
22
import { useHostRenderer } from '@tethys/cdk/dom';
23
import { hasLaterChange } from 'ngx-tethys/util';
24

1✔
25
export type ThyGridResponsiveMode = 'none' | 'self' | 'screen';
26

19✔
27
export type ThyGridResponsiveDescription = string;
19✔
28

19✔
29
export const THY_GRID_DEFAULT_COLUMNS = 24;
19✔
30

19✔
31
export const THY_GRID_ITEM_DEFAULT_SPAN = 1;
19✔
32

19✔
33
export const screenBreakpointsMap = {
19✔
34
    xs: 0,
19✔
35
    sm: 576,
19✔
36
    md: 768,
19✔
37
    lg: 992,
19✔
38
    xl: 1200
39
};
40

32✔
41
/**
32✔
42
 * 栅格组件
28✔
43
 * @name thy-grid, [thyGrid]
44
 * @order 10
45
 */
46
@Directive({
47
    selector: '[thyGrid]',
27✔
48
    providers: [
27✔
49
        {
1✔
50
            provide: THY_GRID_COMPONENT,
1✔
51
            useExisting: ThyGrid
52
        }
53
    ],
54
    host: {
55
        class: 'thy-grid'
52✔
56
    },
52✔
57
    standalone: true
49✔
58
})
49✔
59
// eslint-disable-next-line @angular-eslint/directive-class-suffix
60
export class ThyGrid implements ThyGridToken, OnInit, OnChanges, AfterContentInit {
61
    private elementRef = inject(ElementRef);
3✔
62
    private viewportRuler = inject(ViewportRuler);
3✔
63
    private ngZone = inject(NgZone);
64

52✔
65
    /**
52✔
66
     * @internal
52✔
67
     */
68
    @ContentChildren(ThyGridItem) gridItems!: QueryList<ThyGridItem>;
69

28✔
70
    /**
5✔
71
     * 栅格的列数
72
     * @default 24
73
     */
74
    @Input() thyCols: number | ThyGridResponsiveDescription = THY_GRID_DEFAULT_COLUMNS;
20✔
75

20✔
76
    /**
20✔
77
     * 栅格的水平间隔
78
     */
79
    @Input() thyXGap: number | ThyGridResponsiveDescription = 0;
80

23✔
81
    /**
23✔
82
     * 栅格的垂直间隔
83
     */
UNCOV
84
    @Input() thyYGap: number | ThyGridResponsiveDescription = 0;
×
UNCOV
85

×
UNCOV
86
    /**
×
87
     * 栅格的水平和垂直间隔
88
     */
89
    @Input() thyGap: number | ThyGridResponsiveDescription = 0;
90

91
    /**
92
     * 响应式栅格列数<br/>
48✔
93
     * none: 不进行响应式布局。<br/>
185✔
94
     * self:根据grid的自身宽度进行响应式布局。<br/>
185✔
95
     * screen:根据屏幕断点进行响应式布局,目前预设了5种响应式尺寸:`xs: 0, sm: 576, md: 768, lg: 992, xl: 1200`。
185✔
96
     */
185✔
97
    @Input() thyResponsive: ThyGridResponsiveMode = 'none';
185✔
98

99
    private hostRenderer = useHostRenderer();
48✔
100

101
    private cols: number;
102

477✔
103
    public xGap: number;
452✔
104

105
    private yGap: number;
106

25✔
107
    private numRegex = /^\d+$/;
25✔
108

25✔
109
    private responsiveContainerWidth: number;
25✔
110

18✔
111
    public gridItemPropValueChange$ = new Subject<void>();
112

7✔
113
    private takeUntilDestroyed = takeUntilDestroyed();
5✔
114

115
    ngOnInit(): void {
116
        this.setGridStyle();
2✔
117

118
        if (this.thyResponsive !== 'none') {
119
            this.listenResizeEvent();
120
        }
121
    }
25✔
122

105✔
123
    ngOnChanges(changes: SimpleChanges): void {}
22✔
124

125
    ngAfterContentInit(): void {
105✔
126
        this.handleGridItems();
105✔
127

105✔
128
        this.gridItems.changes.pipe(this.takeUntilDestroyed).subscribe(() => {
129
            Promise.resolve().then(() => {
130
                this.handleGridItems();
131
            });
25!
132
        });
25✔
133
    }
25✔
134

83✔
135
    private setGridStyle() {
103✔
136
        this.cols = this.calculateActualValue(this.thyCols || THY_GRID_DEFAULT_COLUMNS, THY_GRID_DEFAULT_COLUMNS);
137
        if (!this.thyXGap && !this.thyYGap) {
138
            this.xGap = this.calculateActualValue(this.thyGap || 0);
139
            this.yGap = this.xGap;
140
        } else {
×
UNCOV
141
            this.xGap = this.calculateActualValue(this.thyXGap || this.thyGap);
×
UNCOV
142
            this.yGap = this.calculateActualValue(this.thyYGap || this.thyGap);
×
143
        }
×
144

145
        this.hostRenderer.setStyle('display', 'grid');
146
        this.hostRenderer.setStyle('grid-template-columns', `repeat(${this.cols}, minmax(0, 1fr))`);
147
        this.hostRenderer.setStyle('gap', `${this.yGap}px ${this.xGap}px`);
148
    }
149

23✔
150
    private listenResizeEvent() {
23✔
UNCOV
151
        if (this.thyResponsive === 'screen') {
×
152
            this.viewportRuler
153
                .change(100)
23✔
154
                .pipe(this.takeUntilDestroyed)
23✔
155
                .subscribe(() => {
23✔
156
                    this.responsiveContainerWidth = this.viewportRuler.getViewportSize().width;
157
                    this.setGridStyle();
158
                    this.handleGridItems();
159
                });
1✔
160
        } else {
161
            this.ngZone.runOutsideAngular(() => {
162
                this.gridResizeObserver(this.elementRef.nativeElement)
163
                    .pipe(throttleTime(100), this.takeUntilDestroyed)
164
                    .subscribe(data => {
165
                        this.responsiveContainerWidth = data[0]?.contentRect?.width;
166
                        this.setGridStyle();
167
                        this.handleGridItems();
168
                    });
1✔
169
            });
170
        }
171
    }
172

173
    private handleGridItems() {
174
        this.gridItems.forEach((gridItem: ThyGridItem) => {
175
            const rawSpan = getRawSpan(gridItem.thySpan);
176
            const span = this.calculateActualValue(rawSpan, THY_GRID_ITEM_DEFAULT_SPAN);
177
            const offset = this.calculateActualValue(gridItem.thyOffset || 0);
178

179
            gridItem.span = Math.min(span + offset, this.cols);
180
            gridItem.offset = offset;
181
        });
182

183
        this.gridItemPropValueChange$.next();
184
    }
185

186
    private calculateActualValue(rawValue: number | ThyGridResponsiveDescription, defaultValue?: number): number {
187
        if (this.numRegex.test(rawValue.toString().trim())) {
188
            return Number(rawValue);
1✔
189
        } else {
190
            const responsiveValueMap = this.getResponsiveValueMap(rawValue as ThyGridResponsiveDescription);
19✔
191
            const breakpointKeys = Object.keys(responsiveValueMap);
192
            const breakpoint = this.calculateBreakPoint(breakpointKeys);
193

1✔
194
            if (this.thyResponsive !== 'none' && breakpoint) {
195
                return responsiveValueMap[breakpoint];
196
            } else if (breakpointKeys.includes('0')) {
197
                return responsiveValueMap['0'];
198
            } else {
199
                return defaultValue || 0;
200
            }
201
        }
202
    }
203

204
    private getResponsiveValueMap(responsiveValue: string): { [key: string]: number } {
205
        return responsiveValue.split(' ').reduce((map: { [key: string]: number }, item: string) => {
206
            if (this.numRegex.test(item.toString())) {
207
                item = `0:${item}`;
208
            }
209
            const [key, value] = item.split(':');
210
            map[key] = Number(value);
211
            return map;
212
        }, {});
213
    }
214

215
    private calculateBreakPoint(breakpointKeys: string[]): string {
216
        if (this.thyResponsive === 'screen') {
185✔
217
            const width = this.responsiveContainerWidth || this.viewportRuler.getViewportSize().width;
218
            return breakpointKeys.find((key: string, index: number) => {
219
                return index < breakpointKeys.length - 1
220
                    ? width >= screenBreakpointsMap[key] && width < screenBreakpointsMap[breakpointKeys[index + 1]]
221
                    : width >= screenBreakpointsMap[key];
222
            });
223
        } else {
224
            const width = this.responsiveContainerWidth || this.elementRef.nativeElement.getBoundingClientRect().width;
225
            return breakpointKeys.find((key: string, index: number) => {
226
                return index < breakpointKeys.length - 1
227
                    ? width >= Number(key) && width < Number(breakpointKeys[index + 1])
228
                    : width >= Number(key);
229
            });
230
        }
231
    }
232

233
    private gridResizeObserver(element: HTMLElement): Observable<ResizeObserverEntry[]> {
234
        return new Observable(observer => {
235
            const resize = new ResizeObserver((entries: ResizeObserverEntry[]) => {
236
                observer.next(entries);
237
            });
238
            resize.observe(element);
239
            return () => {
240
                resize.disconnect();
241
            };
242
        });
243
    }
244
}
245

246
/**
247
 * @internal
248
 */
249
@Component({
250
    selector: 'thy-grid',
251
    template: '<ng-content></ng-content>',
252
    changeDetection: ChangeDetectionStrategy.OnPush,
253
    standalone: true,
254
    imports: [ThyGrid],
255
    providers: [
256
        {
257
            provide: THY_GRID_COMPONENT,
258
            useExisting: ThyGrid
259
        }
260
    ],
261
    hostDirectives: [
262
        {
263
            directive: ThyGrid,
264
            inputs: ['thyCols', 'thyXGap', 'thyYGap', 'thyGap', 'thyResponsive']
265
        }
266
    ]
267
})
268
export class ThyGridComponent {
269
    grid = inject(ThyGrid);
270
}
271

272
function getRawSpan(span: number | ThyGridResponsiveDescription | undefined | null): number | ThyGridResponsiveDescription {
273
    return span === undefined || span === null ? THY_GRID_ITEM_DEFAULT_SPAN : span;
274
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc