• 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

3.57
/src/color-picker/color-picker.component.ts
1
import { FocusMonitor } from '@angular/cdk/a11y';
2
import { OverlayRef } from '@angular/cdk/overlay';
3
import { Platform } from '@angular/cdk/platform';
4
import {
5
    AfterViewInit,
6
    Directive,
7
    effect,
8
    ElementRef,
9
    forwardRef,
10
    inject,
11
    Input,
12
    input,
13
    NgZone,
14
    numberAttribute,
15
    OnDestroy,
16
    OnInit,
UNCOV
17
    output
×
UNCOV
18
} from '@angular/core';
×
UNCOV
19
import { NG_VALUE_ACCESSOR } from '@angular/forms';
×
20
import { mixinDisabled, mixinTabIndex, ThyOverlayDirectiveBase, ThyOverlayTrigger, ThyPlacement } from 'ngx-tethys/core';
21
import { ThyPopover, ThyPopoverRef } from 'ngx-tethys/popover';
22
import { coerceBooleanProperty } from 'ngx-tethys/util';
23
import { fromEvent, Subject } from 'rxjs';
24
import { takeUntil } from 'rxjs/operators';
1✔
25
import { ThyColorPickerPanel } from './color-picker-panel.component';
26
import { DEFAULT_COLORS } from './constant';
27
import { ThyColor } from './helpers/color.class';
28

29
export class OverlayBase extends ThyOverlayDirectiveBase {
1✔
30
    constructor(
UNCOV
31
        protected zone: NgZone,
×
32
        protected elementRef: ElementRef<HTMLElement>,
33
        platform: Platform,
UNCOV
34
        focusMonitor: FocusMonitor
×
35
    ) {
36
        super(elementRef, platform, focusMonitor, zone, true);
UNCOV
37
    }
×
38

39
    show(): void {}
UNCOV
40

×
UNCOV
41
    hide() {}
×
UNCOV
42
}
×
UNCOV
43

×
UNCOV
44
const _BaseMixin = mixinTabIndex(mixinDisabled(OverlayBase));
×
UNCOV
45

×
UNCOV
46
/**
×
UNCOV
47
 * 颜色选择组件
×
UNCOV
48
 * @name thyColorPicker
×
UNCOV
49
 */
×
UNCOV
50
@Directive({
×
UNCOV
51
    selector: '[thyColorPicker]',
×
UNCOV
52
    host: {
×
UNCOV
53
        class: 'thy-color-picker',
×
UNCOV
54
        '[attr.tabindex]': `tabIndex`,
×
UNCOV
55
        '[class.thy-color-picker-disabled]': 'disabled'
×
UNCOV
56
    },
×
UNCOV
57
    providers: [
×
UNCOV
58
        {
×
UNCOV
59
            provide: NG_VALUE_ACCESSOR,
×
UNCOV
60
            multi: true,
×
UNCOV
61
            useExisting: forwardRef(() => ThyColorPickerDirective)
×
62
        }
UNCOV
63
    ]
×
UNCOV
64
})
×
UNCOV
65
export class ThyColorPickerDirective extends _BaseMixin implements OnInit, OnDestroy, AfterViewInit {
×
66
    private thyPopover = inject(ThyPopover);
67

68
    /**
69
     * 弹框偏移量
UNCOV
70
     * @type  number
×
UNCOV
71
     */
×
UNCOV
72
    readonly thyOffset = input<number, unknown>(0, { transform: numberAttribute });
×
UNCOV
73

×
74
    /**
75
     * 颜色选择面板是否有幕布
UNCOV
76
     */
×
UNCOV
77
    readonly thyHasBackdrop = input(true, { transform: coerceBooleanProperty });
×
78

79
    /**
80
     * 设置颜色选择面板的默认颜色选项值
UNCOV
81
     */
×
82
    readonly thyDefaultColor = input<string>();
83

UNCOV
84
    /**
×
UNCOV
85
     * 是否显示'无填充色'选项
×
86
     */
87
    readonly thyTransparentColorSelectable = input(true, { transform: coerceBooleanProperty });
88

89
    /**
90
     * 预设的快捷选择颜色
91
     * @type string[]
92
     */
UNCOV
93
    readonly thyPresetColors = input<string[]>(DEFAULT_COLORS);
×
UNCOV
94

×
95
    /**
96
     * 颜色面板弹出位置 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom'
97
     * @type ThyPlacement
98
     */
99
    readonly thyPlacement = input<ThyPlacement>('bottom');
100

101
    /**
102
     * panel 展开后触发
103
     */
UNCOV
104
    readonly thyPanelOpen = output<ThyPopoverRef<ThyColorPickerPanel>>();
×
UNCOV
105

×
106
    /**
UNCOV
107
     * panel 关闭后触发
×
108
     */
109
    readonly thyPanelClose = output<ThyPopoverRef<ThyColorPickerPanel>>();
110

111
    /**
112
     * 弹出悬浮层的触发方式
113
     * @type 'hover' | 'click'
114
     */
115
    readonly thyTrigger = input<ThyOverlayTrigger>('click');
×
116

×
117
    /**
118
     * 显示延迟时间
119
     */
UNCOV
120
    readonly thyShowDelay = input<number, unknown>(100, { transform: numberAttribute });
×
UNCOV
121

×
UNCOV
122
    /**
×
123
     * 隐藏延迟时间
UNCOV
124
     */
×
UNCOV
125
    readonly thyHideDelay = input<number, unknown>(100, { transform: numberAttribute });
×
UNCOV
126

×
127
    /**
128
     * 是否属于禁用状态
UNCOV
129
     */
×
UNCOV
130
    @Input({ transform: coerceBooleanProperty })
×
131
    override set thyDisabled(value: boolean) {
132
        this.disabled = value;
133
    }
UNCOV
134
    override get thyDisabled(): boolean {
×
UNCOV
135
        return this.disabled;
×
136
    }
UNCOV
137

×
UNCOV
138
    protected onChangeFn: (value: number | string) => void = () => {};
×
139

140
    private onTouchFn: () => void = () => {};
141

UNCOV
142
    color: string;
×
143

144
    private popoverRef: ThyPopoverRef<ThyColorPickerPanel>;
×
UNCOV
145

×
146
    private closePanel = true;
×
147

×
148
    private destroy$ = new Subject<void>();
UNCOV
149

×
UNCOV
150
    public get backgroundColor(): string {
×
151
        return this.color;
UNCOV
152
    }
×
UNCOV
153

×
154
    constructor() {
UNCOV
155
        const zone = inject(NgZone);
×
UNCOV
156
        const elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
×
UNCOV
157
        const platform = inject(Platform);
×
UNCOV
158
        const focusMonitor = inject(FocusMonitor);
×
159

160
        super(zone, elementRef, platform, focusMonitor);
161

×
UNCOV
162
        effect(() => {
×
163
            // TODO: 基类参数
×
164
            this.showDelay = this.thyShowDelay() ?? 100;
×
165
            this.hideDelay = this.thyHideDelay() ?? 100;
UNCOV
166
            this.trigger = this.thyTrigger() || 'click';
×
UNCOV
167
        });
×
UNCOV
168
    }
×
169

UNCOV
170
    ngOnInit(): void {}
×
171

172
    ngAfterViewInit() {
173
        this.initialize();
UNCOV
174
        if (this.trigger === 'hover') {
×
175
            this.ngZone.runOutsideAngular(() => {
176
                return fromEvent(document, 'mousemove')
UNCOV
177
                    .pipe(takeUntil(this.destroy$))
×
178
                    .subscribe(event => {
179
                        if (this.popoverRef?.getOverlayRef()?.hasAttached()) {
UNCOV
180
                            if (
×
181
                                this.elementRef.nativeElement.contains(event.target as HTMLElement) ||
182
                                (event.target as HTMLElement).closest('.thy-color-picker-custom-panel') ||
UNCOV
183
                                !!(event.target as HTMLElement).querySelector('.thy-color-picker-custom-panel') ||
×
184
                                this.popoverRef.getOverlayRef()?.hostElement?.contains(event.target as HTMLElement)
185
                            ) {
UNCOV
186
                                this.closePanel = false;
×
UNCOV
187
                            } else {
×
188
                                this.closePanel = true;
189
                                this.popoverRef.close();
UNCOV
190
                            }
×
UNCOV
191
                        }
×
UNCOV
192
                    });
×
UNCOV
193
            });
×
UNCOV
194
        }
×
195
    }
196

1✔
197
    togglePanel(): OverlayRef {
1✔
198
        this.closePanel = false;
199
        this.popoverRef = this.thyPopover.open(ThyColorPickerPanel, {
200
            origin: this.elementRef.nativeElement as HTMLElement,
201
            offset: this.thyOffset(),
202
            manualClosure: true,
203
            width: '286px',
204
            placement: this.thyPlacement(),
205
            originActiveClass: 'thy-default-picker-active',
206
            hasBackdrop: this.thyHasBackdrop(),
207
            outsideClosable: false,
208
            canClose: () => {
209
                if (this.trigger === 'hover') {
210
                    return this.closePanel;
211
                }
212
                return true;
1✔
213
            },
214
            initialState: {
215
                color: new ThyColor(this.color).toHexString(true),
216
                defaultColor: this.thyDefaultColor(),
217
                transparentColorSelectable: this.thyTransparentColorSelectable(),
218
                defaultColors: this.thyPresetColors(),
219
                colorChange: (value: string) => {
220
                    this.closePanel = true;
221
                    this.onModelChange(value);
222
                }
223
            }
UNCOV
224
        });
×
225
        if (this.popoverRef) {
226
            this.popoverRef.afterOpened().subscribe(() => {
227
                this.thyPanelOpen.emit(this.popoverRef);
228
            });
229
            this.popoverRef.afterClosed().subscribe(() => {
230
                this.thyPanelClose.emit(this.popoverRef);
231
                this.elementRef.nativeElement.focus();
232
            });
233
        }
234
        if (this.popoverRef && !this.thyHasBackdrop()) {
235
            this.popoverRef
236
                .getOverlayRef()
237
                .outsidePointerEvents()
238
                .subscribe(event => {
239
                    if ((event.target as HTMLElement).closest('.thy-color-picker-custom-panel')) {
240
                        return;
241
                    }
242
                    if (!this.popoverRef.getOverlayRef().hostElement.contains(event.target as HTMLElement)) {
243
                        this.popoverRef.close();
244
                    }
245
                });
246
        }
247
        return this.popoverRef.getOverlayRef();
248
    }
249

250
    override show(delay: number = this.showDelay): void {
251
        if (this.hideTimeoutId) {
252
            clearTimeout(this.hideTimeoutId);
253
            this.hideTimeoutId = null;
254
        }
255

256
        if (this.disabled || (this.overlayRef && this.overlayRef.hasAttached())) {
257
            return;
258
        }
259
        if (this.trigger !== 'hover') {
260
            delay = 0;
261
        }
262

263
        this.showTimeoutId = setTimeout(() => {
264
            const overlayRef = this.togglePanel();
265
            this.overlayRef = overlayRef;
266
            this.showTimeoutId = null;
267
        }, delay);
268
    }
269

270
    override hide(delay: number = this.hideDelay) {
271
        if (this.showTimeoutId) {
272
            clearTimeout(this.showTimeoutId);
273
            this.showTimeoutId = null;
274
        }
275

276
        this.hideTimeoutId = setTimeout(() => {
277
            if (this.popoverRef) {
278
                this.popoverRef?.getOverlayRef()?.hasAttached() && this.popoverRef.close();
279
            }
280
            this.hideTimeoutId = null;
281
        }, delay);
282
    }
283

284
    writeValue(value: string): void {
285
        this.color = value;
286
    }
287

288
    registerOnChange(fn: any): void {
289
        this.onChangeFn = fn;
290
    }
291

292
    registerOnTouched(fn: any): void {
293
        this.onTouchFn = fn;
294
    }
295

296
    setDisabledState?(isDisabled: boolean): void {
297
        this.disabled = isDisabled;
298
    }
299

300
    onModelChange(value: string): void {
301
        this.color = value;
302
        this.onChangeFn(value);
303
    }
304

305
    ngOnDestroy(): void {
306
        this.destroy$.next();
307
        this.destroy$.complete();
308
        this.hide();
309
        this.dispose();
310
        this.popoverRef = null;
311
    }
312
}
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