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

atinc / ngx-tethys / c572ad56-6796-461c-8809-6e2d7ed05a21

16 Apr 2025 06:23AM UTC coverage: 90.271% (+0.001%) from 90.27%
c572ad56-6796-461c-8809-6e2d7ed05a21

Pull #3341

circleci

minlovehua
refactor(all): resolve 30 circular denpendencies TINFR-1830
Pull Request #3341: refactor(all): resolve 30 circular denpendencies TINFR-1830

5614 of 6878 branches covered (81.62%)

Branch coverage included in aggregate %.

89 of 94 new or added lines in 38 files covered. (94.68%)

64 existing lines in 12 files now uncovered.

13370 of 14152 relevant lines covered (94.47%)

922.06 hits per line

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

89.15
/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,
11
    OnChanges,
12
    OnInit,
13
    QueryList,
14
    SimpleChanges,
15
    inject
16
} from '@angular/core';
1✔
17
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
18
import { Observable, Subject } from 'rxjs';
19✔
19
import { throttleTime } from 'rxjs/operators';
19✔
20
import { ThyGridToken, THY_GRID_COMPONENT } from './grid.token';
19✔
21
import { ThyGridItem } from './thy-grid-item.component';
19✔
22
import { useHostRenderer } from '@tethys/cdk/dom';
19✔
23
import {
19✔
24
    ThyGridResponsiveMode,
19✔
25
    ThyGridResponsiveDescription,
19✔
26
    THY_GRID_DEFAULT_COLUMNS,
19✔
27
    THY_GRID_ITEM_DEFAULT_SPAN,
19✔
28
    screenBreakpointsMap
19✔
29
} from './grid.type';
19✔
30

31
/**
32
 * 栅格组件
32✔
33
 * @name thy-grid, [thyGrid]
32✔
34
 * @order 10
28✔
35
 */
36
@Directive({
37
    selector: '[thyGrid]',
38
    providers: [
39
        {
27✔
40
            provide: THY_GRID_COMPONENT,
27✔
41
            useExisting: ThyGrid
1✔
42
        }
1✔
43
    ],
44
    host: {
45
        class: 'thy-grid'
46
    }
47
})
52✔
48
// eslint-disable-next-line @angular-eslint/directive-class-suffix
52✔
49
export class ThyGrid implements ThyGridToken, OnInit, OnChanges, AfterContentInit {
49✔
50
    private elementRef = inject(ElementRef);
49✔
51
    private viewportRuler = inject(ViewportRuler);
52
    private ngZone = inject(NgZone);
53

3✔
54
    /**
3✔
55
     * @internal
56
     */
52✔
57
    @ContentChildren(ThyGridItem) gridItems!: QueryList<ThyGridItem>;
52✔
58

52✔
59
    /**
60
     * 栅格的列数
61
     * @default 24
28✔
62
     */
5✔
63
    @Input() thyCols: number | ThyGridResponsiveDescription = THY_GRID_DEFAULT_COLUMNS;
64

65
    /**
66
     * 栅格的水平间隔
20✔
67
     */
20✔
68
    @Input() thyXGap: number | ThyGridResponsiveDescription = 0;
20✔
69

70
    /**
71
     * 栅格的垂直间隔
72
     */
23✔
73
    @Input() thyYGap: number | ThyGridResponsiveDescription = 0;
23✔
74

75
    /**
76
     * 栅格的水平和垂直间隔
×
UNCOV
77
     */
×
UNCOV
78
    @Input() thyGap: number | ThyGridResponsiveDescription = 0;
×
79

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

185✔
88
    private hostRenderer = useHostRenderer();
185✔
89

185✔
90
    private cols: number;
91

48✔
92
    public xGap: number;
93

94
    private yGap: number;
477✔
95

452✔
96
    private numRegex = /^\d+$/;
97

98
    private responsiveContainerWidth: number;
25✔
99

25✔
100
    public gridItemPropValueChange$ = new Subject<void>();
25✔
101

25✔
102
    private takeUntilDestroyed = takeUntilDestroyed();
18✔
103

104
    ngOnInit(): void {
7✔
105
        this.setGridStyle();
5✔
106

107
        if (this.thyResponsive !== 'none') {
108
            this.listenResizeEvent();
2✔
109
        }
110
    }
111

112
    ngOnChanges(changes: SimpleChanges): void {}
113

25✔
114
    ngAfterContentInit(): void {
105✔
115
        this.handleGridItems();
22✔
116

117
        this.gridItems.changes.pipe(this.takeUntilDestroyed).subscribe(() => {
105✔
118
            Promise.resolve().then(() => {
105✔
119
                this.handleGridItems();
105✔
120
            });
121
        });
122
    }
123

25!
124
    private setGridStyle() {
25✔
125
        this.cols = this.calculateActualValue(this.thyCols || THY_GRID_DEFAULT_COLUMNS, THY_GRID_DEFAULT_COLUMNS);
25✔
126
        if (!this.thyXGap && !this.thyYGap) {
83✔
127
            this.xGap = this.calculateActualValue(this.thyGap || 0);
103✔
128
            this.yGap = this.xGap;
129
        } else {
130
            this.xGap = this.calculateActualValue(this.thyXGap || this.thyGap);
131
            this.yGap = this.calculateActualValue(this.thyYGap || this.thyGap);
132
        }
×
UNCOV
133

×
UNCOV
134
        this.hostRenderer.setStyle('display', 'grid');
×
135
        this.hostRenderer.setStyle('grid-template-columns', `repeat(${this.cols}, minmax(0, 1fr))`);
×
136
        this.hostRenderer.setStyle('gap', `${this.yGap}px ${this.xGap}px`);
137
    }
138

139
    private listenResizeEvent() {
140
        if (this.thyResponsive === 'screen') {
141
            this.viewportRuler
23✔
142
                .change(100)
23✔
UNCOV
143
                .pipe(this.takeUntilDestroyed)
×
144
                .subscribe(() => {
145
                    this.responsiveContainerWidth = this.viewportRuler.getViewportSize().width;
23✔
146
                    this.setGridStyle();
23✔
147
                    this.handleGridItems();
23✔
148
                });
149
        } else {
150
            this.ngZone.runOutsideAngular(() => {
151
                this.gridResizeObserver(this.elementRef.nativeElement)
1✔
152
                    .pipe(throttleTime(100), this.takeUntilDestroyed)
153
                    .subscribe((data: ResizeObserverEntry[]) => {
154
                        this.responsiveContainerWidth = data[0]?.contentRect?.width;
155
                        this.setGridStyle();
156
                        this.handleGridItems();
157
                    });
158
            });
159
        }
160
    }
1✔
161

162
    private handleGridItems() {
163
        this.gridItems.forEach((gridItem: ThyGridItem) => {
164
            const rawSpan = getRawSpan(gridItem.thySpan);
165
            const span = this.calculateActualValue(rawSpan, THY_GRID_ITEM_DEFAULT_SPAN);
166
            const offset = this.calculateActualValue(gridItem.thyOffset || 0);
167

168
            gridItem.span = Math.min(span + offset, this.cols);
169
            gridItem.offset = offset;
170
        });
171

172
        this.gridItemPropValueChange$.next();
173
    }
174

175
    private calculateActualValue(rawValue: number | ThyGridResponsiveDescription, defaultValue?: number): number {
176
        if (this.numRegex.test(rawValue.toString().trim())) {
177
            return Number(rawValue);
178
        } else {
179
            const responsiveValueMap = this.getResponsiveValueMap(rawValue as ThyGridResponsiveDescription);
1✔
180
            const breakpointKeys = Object.keys(responsiveValueMap);
181
            const breakpoint = this.calculateBreakPoint(breakpointKeys);
19✔
182

183
            if (this.thyResponsive !== 'none' && breakpoint) {
184
                return responsiveValueMap[breakpoint];
1✔
185
            } else if (breakpointKeys.includes('0')) {
186
                return responsiveValueMap['0'];
187
            } else {
188
                return defaultValue || 0;
189
            }
190
        }
191
    }
192

193
    private getResponsiveValueMap(responsiveValue: string): { [key: string]: number } {
194
        return responsiveValue.split(' ').reduce((map: { [key: string]: number }, item: string) => {
195
            if (this.numRegex.test(item.toString())) {
196
                item = `0:${item}`;
197
            }
198
            const [key, value] = item.split(':');
199
            map[key] = Number(value);
200
            return map;
201
        }, {});
202
    }
203

204
    private calculateBreakPoint(breakpointKeys: string[]): string {
205
        if (this.thyResponsive === 'screen') {
206
            const width = this.responsiveContainerWidth || this.viewportRuler.getViewportSize().width;
185✔
207
            return breakpointKeys.find((key: string, index: number) => {
208
                return index < breakpointKeys.length - 1
209
                    ? width >= screenBreakpointsMap[key] && width < screenBreakpointsMap[breakpointKeys[index + 1]]
210
                    : width >= screenBreakpointsMap[key];
211
            });
212
        } else {
213
            const width = this.responsiveContainerWidth || this.elementRef.nativeElement.getBoundingClientRect().width;
214
            return breakpointKeys.find((key: string, index: number) => {
215
                return index < breakpointKeys.length - 1
216
                    ? width >= Number(key) && width < Number(breakpointKeys[index + 1])
217
                    : width >= Number(key);
218
            });
219
        }
220
    }
221

222
    private gridResizeObserver(element: HTMLElement): Observable<ResizeObserverEntry[]> {
223
        return new Observable(observer => {
224
            const resize = new ResizeObserver((entries: ResizeObserverEntry[]) => {
225
                observer.next(entries);
226
            });
227
            resize.observe(element);
228
            return () => {
229
                resize.disconnect();
230
            };
231
        });
232
    }
233
}
234

235
/**
236
 * @internal
237
 */
238
@Component({
239
    selector: 'thy-grid',
240
    template: '<ng-content></ng-content>',
241
    changeDetection: ChangeDetectionStrategy.OnPush,
242
    imports: [],
243
    providers: [
244
        {
245
            provide: THY_GRID_COMPONENT,
246
            useExisting: ThyGrid
247
        }
248
    ],
249
    hostDirectives: [
250
        {
251
            directive: ThyGrid,
252
            inputs: ['thyCols', 'thyXGap', 'thyYGap', 'thyGap', 'thyResponsive']
253
        }
254
    ]
255
})
256
export class ThyGridComponent {
257
    grid = inject(ThyGrid);
258
}
259

260
function getRawSpan(span: number | ThyGridResponsiveDescription | undefined | null): number | ThyGridResponsiveDescription {
261
    return span === undefined || span === null ? THY_GRID_ITEM_DEFAULT_SPAN : span;
262
}
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

© 2025 Coveralls, Inc