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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

5.34
/src/grid/thy-grid.component.ts
1
import { ViewportRuler } from '@angular/cdk/scrolling';
2
import {
3
    AfterContentInit,
4
    ChangeDetectionStrategy,
5
    Component,
6
    ContentChildren,
7
    ElementRef,
8
    Input,
9
    NgZone,
10
    OnDestroy,
11
    OnInit,
1✔
12
    QueryList
1✔
13
} from '@angular/core';
1✔
14
import { MixinBase, mixinUnsubscribe } from 'ngx-tethys/core';
15
import { Observable, Subject } from 'rxjs';
16
import { takeUntil, throttleTime } from 'rxjs/operators';
17
import { ThyGridToken, THY_GRID_COMPONENT } from './grid.token';
18
import { ThyGridItemComponent } from './thy-grid-item.component';
19
import { useHostRenderer } from '@tethys/cdk/dom';
20

21
export type ThyGridResponsiveMode = 'none' | 'self' | 'screen';
22

23
export type ThyGridResponsiveDescription = string;
24

1✔
25
export const THY_GRID_DEFAULT_COLUMNS = 24;
26

×
27
export const THY_GRID_ITEM_DEFAULT_SPAN = 1;
×
28

×
29
export const screenBreakpointsMap = {
×
30
    xs: 0,
×
31
    sm: 576,
×
32
    md: 768,
×
33
    lg: 992,
×
34
    xl: 1200
×
35
};
×
36

×
37
/**
×
38
 * 栅格组件
39
 * @name thy-grid
40
 */
×
41
@Component({
×
42
    selector: 'thy-grid',
×
43
    template: '<ng-content></ng-content>',
44
    changeDetection: ChangeDetectionStrategy.OnPush,
45
    providers: [
46
        {
×
47
            provide: THY_GRID_COMPONENT,
×
48
            useExisting: ThyGridComponent
×
49
        }
×
50
    ],
51
    host: {
52
        class: 'thy-grid'
53
    },
54
    standalone: true
×
55
})
×
56
export class ThyGridComponent extends mixinUnsubscribe(MixinBase) implements ThyGridToken, OnInit, AfterContentInit, OnDestroy {
×
57
    /**
×
58
     * @internal
59
     */
60
    @ContentChildren(ThyGridItemComponent) gridItems!: QueryList<ThyGridItemComponent>;
×
61

×
62
    /**
63
     * 栅格的列数
×
64
     * @default 24
×
65
     */
×
66
    @Input() thyCols: number | ThyGridResponsiveDescription = THY_GRID_DEFAULT_COLUMNS;
67

68
    /**
×
69
     * 栅格的水平间隔
×
70
     */
71
    @Input() thyXGap: number | ThyGridResponsiveDescription = 0;
72

73
    /**
×
74
     * 栅格的垂直间隔
×
75
     */
×
76
    @Input() thyYGap: number | ThyGridResponsiveDescription = 0;
77

78
    /**
79
     * 栅格的水平和垂直间隔
×
80
     */
×
81
    @Input() thyGap: number | ThyGridResponsiveDescription = 0;
82

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

91
    private hostRenderer = useHostRenderer();
×
92

×
93
    private cols: number;
×
94

×
95
    public xGap: number;
×
96

×
97
    private yGap: number;
98

×
99
    private numRegex = /^\d+$/;
100

101
    private responsiveContainerWidth: number;
×
102

×
103
    public gridItemPropValueChange$ = new Subject<void>();
104

105
    constructor(private elementRef: ElementRef, private viewportRuler: ViewportRuler, private ngZone: NgZone) {
×
106
        super();
×
107
    }
×
108

×
109
    ngOnInit(): void {
×
110
        this.setGridStyle();
111

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

117
    ngAfterContentInit(): void {
118
        this.handleGridItems();
119

120
        this.gridItems.changes.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(() => {
×
121
            Promise.resolve().then(() => {
×
122
                this.handleGridItems();
×
123
            });
124
        });
×
125
    }
×
126

×
127
    private setGridStyle() {
128
        this.cols = this.calculateActualValue(this.thyCols || THY_GRID_DEFAULT_COLUMNS, THY_GRID_DEFAULT_COLUMNS);
129
        if (!this.thyXGap && !this.thyYGap) {
130
            this.xGap = this.calculateActualValue(this.thyGap || 0);
×
131
            this.yGap = this.xGap;
×
132
        } else {
×
133
            this.xGap = this.calculateActualValue(this.thyXGap || this.thyGap);
×
134
            this.yGap = this.calculateActualValue(this.thyYGap || this.thyGap);
×
135
        }
136

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

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

165
    private handleGridItems() {
166
        this.gridItems.forEach((gridItem: ThyGridItemComponent) => {
1✔
167
            const rawSpan = getRawSpan(gridItem.thySpan);
168
            const span = this.calculateActualValue(rawSpan, THY_GRID_ITEM_DEFAULT_SPAN);
169
            const offset = this.calculateActualValue(gridItem.thyOffset || 0);
170

171
            gridItem.span = Math.min(span + offset, this.cols);
172
            gridItem.offset = offset;
173
        });
174

175
        this.gridItemPropValueChange$.next();
1✔
176
    }
177

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

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

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

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

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

237
    ngOnDestroy(): void {
238
        super.ngOnDestroy();
239
    }
240
}
241

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