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

atinc / ngx-tethys / aefe1666-7ec3-4957-aaeb-b7f919a65668

04 Sep 2023 10:13AM UTC coverage: 90.206% (+0.006%) from 90.2%
aefe1666-7ec3-4957-aaeb-b7f919a65668

Pull #2829

circleci

cmm-va
Merge branch 'cmm/#INFR-9451' of github.com:atinc/ngx-tethys into cmm/#INFR-9451
Pull Request #2829: fix: add tabIndex

5163 of 6383 branches covered (0.0%)

Branch coverage included in aggregate %.

60 of 60 new or added lines in 13 files covered. (100.0%)

13028 of 13783 relevant lines covered (94.52%)

971.37 hits per line

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

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

22
export class OverlayBase extends ThyOverlayDirectiveBase {
23
    constructor(protected zone: NgZone, protected elementRef: ElementRef<HTMLElement>, platform: Platform, focusMonitor: FocusMonitor) {
24
        super(elementRef, platform, focusMonitor, zone, true);
1✔
25
    }
26

27
    show(): void {}
28

29
    hide() {}
1✔
30
}
31

18✔
32
const _BaseMixin = mixinTabIndex(mixinDisabled(OverlayBase));
33

34
/**
18✔
35
 * 颜色选择组件
36
 * @name thyColorPicker
37
 */
18✔
38
@Directive({
39
    selector: '[thyColorPicker]',
40
    host: {
124✔
41
        class: 'thy-color-picker',
42
        '[attr.tabindex]': `tabIndex`,
43
        '[class.thy-color-picker-disabled]': 'disabled'
19✔
44
    },
45
    providers: [
46
        {
1✔
47
            provide: NG_VALUE_ACCESSOR,
48
            multi: true,
49
            useExisting: forwardRef(() => ThyColorPickerDirective)
19✔
50
        }
19✔
51
    ],
19✔
52
    standalone: true
19✔
53
})
19✔
54
export class ThyColorPickerDirective extends _BaseMixin implements OnInit, OnDestroy {
19✔
55
    /**
19✔
56
     * 弹框偏移量
19✔
57
     * @type  number
19✔
58
     */
19✔
59
    @Input() @InputNumber() thyOffset: number = 0;
19✔
60

19✔
61
    /**
19✔
62
     * 颜色选择面板是否有幕布
19✔
63
     */
19✔
64
    @Input() @InputBoolean() thyHasBackdrop: boolean = true;
65

66
    /**
18✔
67
     * 设置颜色选择面板的默认颜色选项值
18✔
68
     */
3✔
69
    @Input() thyDefaultColor: string;
3✔
70

71
    /**
72
     * 是否显示'无填充色'选项
4!
73
     */
4✔
74
    @Input() @InputBoolean() thyTransparentColorSelectable: boolean = true;
75

76
    /**
77
     * 预设的快捷选择颜色
2✔
78
     * @type string[]
79
     */
80
    @Input() thyPresetColors: string[] = DEFAULT_COLORS;
2✔
81

2✔
82
    /**
83
     * 颜色面板弹出位置 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom'
84
     * @type ThyPlacement
85
     */
86
    @Input() thyPlacement: ThyPlacement = 'bottom';
87

88
    /**
89
     * panel 展开后触发
15✔
90
     */
15✔
91
    @Output() thyPanelOpen: EventEmitter<ThyPopoverRef<ThyColorPickerPanelComponent>> = new EventEmitter<
92
        ThyPopoverRef<ThyColorPickerPanelComponent>
93
    >();
94

95
    /**
96
     * panel 关闭后触发
97
     */
98
    @Output() thyPanelClose: EventEmitter<ThyPopoverRef<ThyColorPickerPanelComponent>> = new EventEmitter<
99
        ThyPopoverRef<ThyColorPickerPanelComponent>
100
    >();
10✔
101

4✔
102
    /**
103
     * 弹出悬浮层的触发方式
6✔
104
     * @type 'hover' | 'click'
105
     * @default click
106
     */
107
    @Input() set thyTrigger(trigger: ThyOverlayTrigger) {
108
        this.trigger = trigger;
109
    }
110

111
    /**
×
112
     * 显示延迟时间
×
113
     */
114
    @Input('thyShowDelay')
115
    @InputNumber()
116
    set thyShowDelay(value: number) {
15!
117
        this.showDelay = value;
15✔
118
    }
12✔
119

120
    /**
15✔
121
     * 隐藏延迟时间
15✔
122
     */
123
    @Input('thyHideDelay')
124
    @InputNumber()
15✔
125
    set thyHideDelay(value: number) {
4✔
126
        this.hideDelay = value;
127
    }
128

129
    /**
2✔
130
     * 是否属于禁用状态
1✔
131
     */
132
    @Input()
1!
133
    override get thyDisabled(): boolean {
1✔
134
        return this.disabled;
135
    }
136

137
    override set thyDisabled(value: boolean) {
15✔
138
        this.disabled = coerceBooleanProperty(value);
139
    }
16✔
140

16!
141
    protected onChangeFn: (value: number | string) => void = () => {};
×
142

×
143
    private onTouchFn: () => void = () => {};
144

16✔
145
    color: string;
1✔
146

147
    private popoverRef: ThyPopoverRef<ThyColorPickerPanelComponent>;
15✔
148

12✔
149
    private closePanel = true;
150

15✔
151
    private destroy$ = new Subject<void>();
15✔
152

15✔
153
    public get backgroundColor(): string {
15✔
154
        return this.color;
155
    }
156

21✔
157
    constructor(
21!
158
        private thyPopover: ThyPopover,
×
159
        protected zone: NgZone,
×
160
        protected elementRef: ElementRef<HTMLElement>,
161
        platform: Platform,
21✔
162
        focusMonitor: FocusMonitor
2!
163
    ) {
2✔
164
        super(zone, elementRef, platform, focusMonitor);
165
    }
2✔
166

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

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

244
    override show(delay: number = this.showDelay): void {
245
        if (this.hideTimeoutId) {
246
            clearTimeout(this.hideTimeoutId);
247
            this.hideTimeoutId = null;
19✔
248
        }
249

250
        if (this.disabled || (this.overlayRef && this.overlayRef.hasAttached())) {
251
            return;
252
        }
253
        if (this.trigger !== 'hover') {
254
            delay = 0;
255
        }
256

257
        this.showTimeoutId = setTimeout(() => {
258
            const overlayRef = this.togglePanel();
259
            this.overlayRef = overlayRef;
260
            this.showTimeoutId = null;
261
        }, delay);
262
    }
263

264
    override hide(delay: number = this.hideDelay) {
265
        if (this.showTimeoutId) {
266
            clearTimeout(this.showTimeoutId);
267
            this.showTimeoutId = null;
268
        }
269

270
        this.hideTimeoutId = setTimeout(() => {
271
            if (this.popoverRef) {
272
                this.popoverRef?.getOverlayRef()?.hasAttached() && this.popoverRef.close();
273
            }
274
            this.hideTimeoutId = null;
275
        }, delay);
276
    }
277

278
    writeValue(value: string): void {
279
        this.color = value;
280
    }
281

282
    registerOnChange(fn: any): void {
283
        this.onChangeFn = fn;
284
    }
285

286
    registerOnTouched(fn: any): void {
287
        this.onTouchFn = fn;
288
    }
289

290
    setDisabledState?(isDisabled: boolean): void {
291
        this.disabled = isDisabled;
292
    }
293

294
    onModelChange(value: string): void {
295
        this.color = value;
296
        this.onChangeFn(value);
297
    }
298

299
    ngOnDestroy(): void {
300
        this.destroy$.next();
301
        this.destroy$.complete();
302
        this.hide();
303
        this.dispose();
304
        this.popoverRef = null;
305
    }
306
}
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