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

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

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

4.55
/src/grid/thy-grid.component.ts
1
import { ViewportRuler } from '@angular/cdk/scrolling';
2
import {
3
    AfterContentInit,
4
    ChangeDetectionStrategy,
5
    Component,
6
    Directive,
7
    ElementRef,
8
    NgZone,
9
    OnChanges,
10
    OnInit,
11
    SimpleChanges,
12
    inject,
13
    input,
14
    contentChildren,
15
    effect
16
} from '@angular/core';
17
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
1✔
18
import { Observable, Subject } from 'rxjs';
UNCOV
19
import { throttleTime } from 'rxjs/operators';
×
UNCOV
20
import { ThyGridToken, THY_GRID_COMPONENT } from './grid.token';
×
UNCOV
21
import { ThyGridItem } from './thy-grid-item.component';
×
UNCOV
22
import { useHostRenderer } from '@tethys/cdk/dom';
×
UNCOV
23
import {
×
UNCOV
24
    ThyGridResponsiveMode,
×
UNCOV
25
    ThyGridResponsiveDescription,
×
UNCOV
26
    THY_GRID_DEFAULT_COLUMNS,
×
UNCOV
27
    THY_GRID_ITEM_DEFAULT_SPAN,
×
UNCOV
28
    screenBreakpointsMap
×
UNCOV
29
} from './grid.type';
×
UNCOV
30

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

×
54
    /**
UNCOV
55
     * @internal
×
UNCOV
56
     */
×
UNCOV
57
    readonly gridItems = contentChildren(ThyGridItem);
×
58

59
    /**
UNCOV
60
     * 栅格的列数
×
UNCOV
61
     * @default 24
×
62
     */
63
    readonly thyCols = input<number | ThyGridResponsiveDescription>(THY_GRID_DEFAULT_COLUMNS);
64

UNCOV
65
    /**
×
UNCOV
66
     * 栅格的水平间隔
×
UNCOV
67
     */
×
68
    readonly thyXGap = input<number | ThyGridResponsiveDescription>(0);
69

70
    /**
UNCOV
71
     * 栅格的垂直间隔
×
UNCOV
72
     */
×
73
    readonly thyYGap = input<number | ThyGridResponsiveDescription>(0);
74

75
    /**
×
76
     * 栅格的水平和垂直间隔
×
77
     */
×
78
    readonly thyGap = input<number | ThyGridResponsiveDescription>(0);
79

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

×
UNCOV
88
    private hostRenderer = useHostRenderer();
×
89

UNCOV
90
    private cols: number;
×
91

92
    public xGap: number;
UNCOV
93

×
UNCOV
94
    private yGap: number;
×
95

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

×
UNCOV
98
    private responsiveContainerWidth: number;
×
UNCOV
99

×
UNCOV
100
    public gridItemPropValueChange$ = new Subject<void>();
×
UNCOV
101

×
102
    private takeUntilDestroyed = takeUntilDestroyed();
UNCOV
103

×
UNCOV
104
    constructor() {
×
105
        effect(() => {
106
            this.handleGridItems();
UNCOV
107
        });
×
108
    }
109

110
    ngOnInit(): void {
111
        this.setGridStyle();
UNCOV
112

×
UNCOV
113
        if (this.thyResponsive() !== 'none') {
×
UNCOV
114
            this.listenResizeEvent();
×
115
        }
UNCOV
116
    }
×
UNCOV
117

×
UNCOV
118
    private setGridStyle() {
×
119
        this.cols = this.calculateActualValue(this.thyCols() || THY_GRID_DEFAULT_COLUMNS, THY_GRID_DEFAULT_COLUMNS);
120
        const xGap = this.thyXGap();
121
        const yGap = this.thyYGap();
UNCOV
122
        const gap = this.thyGap();
×
UNCOV
123
        if (!xGap && !yGap) {
×
UNCOV
124
            this.xGap = this.calculateActualValue(gap || 0);
×
UNCOV
125
            this.yGap = this.xGap;
×
126
        } else {
×
127
            this.xGap = this.calculateActualValue(xGap || gap);
128
            this.yGap = this.calculateActualValue(yGap || gap);
129
        }
130

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

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

159
    private handleGridItems() {
160
        this.gridItems().forEach((gridItem: ThyGridItem) => {
1✔
161
            const rawSpan = getRawSpan(gridItem.thySpan());
162
            const span = this.calculateActualValue(rawSpan, THY_GRID_ITEM_DEFAULT_SPAN);
163
            const offset = this.calculateActualValue(gridItem.thyOffset() || 0);
164

165
            gridItem.span = Math.min(span + offset, this.cols);
166
            gridItem.offset = offset;
167
        });
168

169
        this.gridItemPropValueChange$.next();
170
    }
171

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

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

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

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

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

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

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