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

atinc / ngx-tethys / 0bbb2cec-209e-4d8a-b1b3-6bc54e05daa6

04 Sep 2023 08:40AM UTC coverage: 15.616% (-74.6%) from 90.2%
0bbb2cec-209e-4d8a-b1b3-6bc54e05daa6

Pull #2829

circleci

cmm-va
fix: add test
Pull Request #2829: fix: add tabIndex

300 of 6386 branches covered (0.0%)

Branch coverage included in aggregate %.

78 of 78 new or added lines in 26 files covered. (100.0%)

2849 of 13779 relevant lines covered (20.68%)

83.41 hits per line

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

6.77
/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,
1✔
12
    OnInit,
1✔
13
    QueryList,
1✔
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 { ThyGridItemComponent } from './thy-grid-item.component';
22
import { useHostRenderer } from '@tethys/cdk/dom';
23
import { hasLaterChange } from 'ngx-tethys/util';
24

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

27
export type ThyGridResponsiveDescription = string;
×
28

×
29
export const THY_GRID_DEFAULT_COLUMNS = 24;
×
30

×
31
export const THY_GRID_ITEM_DEFAULT_SPAN = 1;
×
32

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

41
/**
×
42
 * 栅格组件
×
43
 * @name thy-grid, [thyGrid]
×
44
 * @order 10
45
 */
46
@Directive({
47
    selector: '[thyGrid]',
48
    providers: [
×
49
        {
×
50
            provide: THY_GRID_COMPONENT,
×
51
            useExisting: ThyGrid
×
52
        }
53
    ],
54
    host: {
55
        class: 'thy-grid'
56
    },
×
57
    standalone: true
×
58
})
×
59
// eslint-disable-next-line @angular-eslint/directive-class-suffix
×
60
export class ThyGrid implements ThyGridToken, OnInit, OnChanges, AfterContentInit {
61
    /**
62
     * @internal
×
63
     */
×
64
    @ContentChildren(ThyGridItemComponent) gridItems!: QueryList<ThyGridItemComponent>;
65

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

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

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

×
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
     */
93
    @Input() thyResponsive: ThyGridResponsiveMode = 'none';
×
94

×
95
    private hostRenderer = useHostRenderer();
×
96

×
97
    private cols: number;
×
98

×
99
    public xGap: number;
100

×
101
    private yGap: number;
102

103
    private numRegex = /^\d+$/;
×
104

×
105
    private responsiveContainerWidth: number;
106

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

×
109
    private takeUntilDestroyed = takeUntilDestroyed();
×
110

×
111
    constructor(private elementRef: ElementRef, private viewportRuler: ViewportRuler, private ngZone: NgZone) {}
×
112

113
    ngOnInit(): void {
×
114
        this.setGridStyle();
×
115

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

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

×
123
    ngAfterContentInit(): void {
×
124
        this.handleGridItems();
×
125

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

×
133
    private setGridStyle() {
×
134
        this.cols = this.calculateActualValue(this.thyCols || THY_GRID_DEFAULT_COLUMNS, THY_GRID_DEFAULT_COLUMNS);
×
135
        if (!this.thyXGap && !this.thyYGap) {
×
136
            this.xGap = this.calculateActualValue(this.thyGap || 0);
×
137
            this.yGap = this.xGap;
138
        } else {
139
            this.xGap = this.calculateActualValue(this.thyXGap || this.thyGap);
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') {
150
            this.viewportRuler
×
151
                .change(100)
×
152
                .pipe(this.takeUntilDestroyed)
×
153
                .subscribe(() => {
154
                    this.responsiveContainerWidth = this.viewportRuler.getViewportSize().width;
×
155
                    this.setGridStyle();
×
156
                    this.handleGridItems();
×
157
                });
158
        } else {
159
            this.ngZone.runOutsideAngular(() => {
160
                this.gridResizeObserver(this.elementRef.nativeElement)
1✔
161
                    .pipe(throttleTime(100), this.takeUntilDestroyed)
162
                    .subscribe(data => {
163
                        this.responsiveContainerWidth = data[0]?.contentRect?.width;
164
                        this.setGridStyle();
165
                        this.handleGridItems();
1✔
166
                    });
167
            });
168
        }
169
    }
170

171
    private handleGridItems() {
172
        this.gridItems.forEach((gridItem: ThyGridItemComponent) => {
173
            const rawSpan = getRawSpan(gridItem.thySpan);
174
            const span = this.calculateActualValue(rawSpan, THY_GRID_ITEM_DEFAULT_SPAN);
1✔
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'];
196
            } else {
1✔
197
                return defaultValue || 0;
198
            }
×
199
        }
200
    }
201

1✔
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) => {
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

© 2026 Coveralls, Inc