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

atinc / ngx-tethys / e6dbd7df-f07a-4665-a528-c9d6114f46b9

10 Apr 2025 09:48AM UTC coverage: 90.231% (-0.005%) from 90.236%
e6dbd7df-f07a-4665-a528-c9d6114f46b9

push

circleci

minlovehua
fix: specify the Overlay type returned by the function TINFR-1816

5598 of 6865 branches covered (81.54%)

Branch coverage included in aggregate %.

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

4 existing lines in 2 files now uncovered.

13356 of 14141 relevant lines covered (94.45%)

992.74 hits per line

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

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

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

18✔
37
    show(): void {}
38

39
    hide() {}
19✔
40
}
41

42
const _BaseMixin = mixinTabIndex(mixinDisabled(OverlayBase));
124✔
43

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

67
    /**
18✔
68
     * 弹框偏移量
18✔
69
     * @type  number
3✔
70
     */
3✔
71
    @Input({ transform: numberAttribute }) thyOffset: number = 0;
72

73
    /**
4!
74
     * 颜色选择面板是否有幕布
4✔
75
     */
76
    @Input({ transform: coerceBooleanProperty }) thyHasBackdrop: boolean = true;
77

78
    /**
2✔
79
     * 设置颜色选择面板的默认颜色选项值
80
     */
81
    @Input() thyDefaultColor: string;
2✔
82

2✔
83
    /**
84
     * 是否显示'无填充色'选项
85
     */
86
    @Input({ transform: coerceBooleanProperty }) thyTransparentColorSelectable: boolean = true;
87

88
    /**
89
     * 预设的快捷选择颜色
90
     * @type string[]
15✔
91
     */
15✔
92
    @Input() thyPresetColors: string[] = DEFAULT_COLORS;
93

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

100
    /**
101
     * panel 展开后触发
10✔
102
     */
4✔
103
    @Output() thyPanelOpen: EventEmitter<ThyPopoverRef<ThyColorPickerPanel>> = new EventEmitter<ThyPopoverRef<ThyColorPickerPanel>>();
104

6✔
105
    /**
106
     * panel 关闭后触发
107
     */
108
    @Output() thyPanelClose: EventEmitter<ThyPopoverRef<ThyColorPickerPanel>> = new EventEmitter<ThyPopoverRef<ThyColorPickerPanel>>();
109

110
    /**
111
     * 弹出悬浮层的触发方式
UNCOV
112
     * @type 'hover' | 'click'
×
113
     * @default click
×
114
     */
115
    @Input() set thyTrigger(trigger: ThyOverlayTrigger) {
116
        this.trigger = trigger;
117
    }
15!
118

15✔
119
    /**
12✔
120
     * 显示延迟时间
121
     */
15✔
122
    @Input({ transform: numberAttribute })
15✔
123
    set thyShowDelay(value: number) {
15✔
124
        this.showDelay = value;
125
    }
126

15✔
127
    /**
4✔
128
     * 隐藏延迟时间
129
     */
130
    @Input({ transform: numberAttribute })
131
    set thyHideDelay(value: number) {
2✔
132
        this.hideDelay = value;
1✔
133
    }
134

1!
135
    /**
1✔
136
     * 是否属于禁用状态
137
     */
138
    @Input({ transform: coerceBooleanProperty })
139
    override set thyDisabled(value: boolean) {
15✔
140
        this.disabled = value;
141
    }
16✔
142
    override get thyDisabled(): boolean {
16!
UNCOV
143
        return this.disabled;
×
144
    }
×
145

146
    protected onChangeFn: (value: number | string) => void = () => {};
16✔
147

1✔
148
    private onTouchFn: () => void = () => {};
149

15✔
150
    color: string;
12✔
151

152
    private popoverRef: ThyPopoverRef<ThyColorPickerPanel>;
15✔
153

15✔
154
    private closePanel = true;
15✔
155

15✔
156
    private destroy$ = new Subject<void>();
157

158
    public get backgroundColor(): string {
21✔
159
        return this.color;
21!
UNCOV
160
    }
×
161

×
162
    constructor() {
163
        const zone = inject(NgZone);
21✔
164
        const elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
2!
165
        const platform = inject(Platform);
2✔
166
        const focusMonitor = inject(FocusMonitor);
167

2✔
168
        super(zone, elementRef, platform, focusMonitor);
169
    }
170

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

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

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

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

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

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

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

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

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

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

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

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

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