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

atinc / ngx-tethys / 3033f133-0f0d-43eb-a07d-e1848354018a

07 Mar 2024 01:58AM UTC coverage: 90.58% (-0.02%) from 90.604%
3033f133-0f0d-43eb-a07d-e1848354018a

Pull #3022

circleci

web-flow
feat(schematics): improve schematics for select and custom-select in template #INFR-11735 (#3047)
Pull Request #3022: feat: upgrade ng to 17 #INFR-11427 (#3021)

5422 of 6642 branches covered (81.63%)

Branch coverage included in aggregate %.

328 of 338 new or added lines in 193 files covered. (97.04%)

141 existing lines in 29 files now uncovered.

13502 of 14250 relevant lines covered (94.75%)

982.04 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
     * 栅格的水平和垂直间隔
UNCOV
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(private elementRef: ElementRef, private viewportRuler: ViewportRuler, private ngZone: NgZone) {}
112

7✔
113
    ngOnInit(): void {
5✔
114
        this.setGridStyle();
115

116
        if (this.thyResponsive !== 'none') {
2✔
117
            this.listenResizeEvent();
118
        }
119
    }
120

121
    ngOnChanges(changes: SimpleChanges): void {}
25✔
122

105✔
123
    ngAfterContentInit(): void {
22✔
124
        this.handleGridItems();
125

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

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

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

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

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

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

181
        this.gridItemPropValueChange$.next();
182
    }
183

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

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

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

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

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

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

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