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

atinc / ngx-tethys / 82f575a8-2f12-4689-80e6-398f6f1685a3

15 Aug 2024 08:19AM UTC coverage: 90.473%. Remained the same
82f575a8-2f12-4689-80e6-398f6f1685a3

push

circleci

web-flow
build: bump prettier to 3.3.3 and other deps, refactor notify use @if (#3152)

5502 of 6726 branches covered (81.8%)

Branch coverage included in aggregate %.

112 of 116 new or added lines in 57 files covered. (96.55%)

59 existing lines in 15 files now uncovered.

13254 of 14005 relevant lines covered (94.64%)

997.41 hits per line

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

89.47
/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
    /**
3✔
62
     * @internal
3✔
63
     */
64
    @ContentChildren(ThyGridItem) gridItems!: QueryList<ThyGridItem>;
52✔
65

52✔
66
    /**
52✔
67
     * 栅格的列数
68
     * @default 24
69
     */
28✔
70
    @Input() thyCols: number | ThyGridResponsiveDescription = THY_GRID_DEFAULT_COLUMNS;
5✔
71

72
    /**
73
     * 栅格的水平间隔
74
     */
20✔
75
    @Input() thyXGap: number | ThyGridResponsiveDescription = 0;
20✔
76

20✔
77
    /**
78
     * 栅格的垂直间隔
79
     */
80
    @Input() thyYGap: number | ThyGridResponsiveDescription = 0;
23✔
81

23✔
82
    /**
83
     * 栅格的水平和垂直间隔
84
     */
×
85
    @Input() thyGap: number | ThyGridResponsiveDescription = 0;
×
86

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

185✔
95
    private hostRenderer = useHostRenderer();
185✔
96

185✔
97
    private cols: number;
185✔
98

99
    public xGap: number;
48✔
100

101
    private yGap: number;
102

477✔
103
    private numRegex = /^\d+$/;
452✔
104

105
    private responsiveContainerWidth: number;
106

25✔
107
    public gridItemPropValueChange$ = new Subject<void>();
25✔
108

25✔
109
    private takeUntilDestroyed = takeUntilDestroyed();
25✔
110

18✔
111
    constructor(
112
        private elementRef: ElementRef,
7✔
113
        private viewportRuler: ViewportRuler,
5✔
114
        private ngZone: NgZone
115
    ) {}
116

2✔
117
    ngOnInit(): void {
118
        this.setGridStyle();
119

120
        if (this.thyResponsive !== 'none') {
121
            this.listenResizeEvent();
25✔
122
        }
105✔
123
    }
22✔
124

125
    ngOnChanges(changes: SimpleChanges): void {}
105✔
126

105✔
127
    ngAfterContentInit(): void {
105✔
128
        this.handleGridItems();
129

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

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

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

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

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

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

185
        this.gridItemPropValueChange$.next();
186
    }
187

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

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

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

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

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

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

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