• 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.25
/src/dropdown/dropdown.directive.ts
1
import { FocusMonitor } from '@angular/cdk/a11y';
2
import { ComponentType, OverlayRef } from '@angular/cdk/overlay';
3
import { Platform } from '@angular/cdk/platform';
4
import {
5
    ChangeDetectorRef,
6
    Directive,
7
    ElementRef,
8
    EventEmitter,
9
    NgZone,
10
    OnInit,
11
    Output,
12
    Signal,
13
    TemplateRef,
14
    ViewContainerRef,
15
    computed,
1✔
16
    effect,
UNCOV
17
    inject,
×
UNCOV
18
    input,
×
UNCOV
19
    numberAttribute
×
UNCOV
20
} from '@angular/core';
×
UNCOV
21
import { ComponentTypeOrTemplateRef, ThyOverlayDirectiveBase, ThyOverlayTrigger, ThyPlacement } from 'ngx-tethys/core';
×
UNCOV
22
import { ThyPopover, ThyPopoverConfig, ThyPopoverRef } from 'ngx-tethys/popover';
×
UNCOV
23
import { SafeAny } from 'ngx-tethys/types';
×
UNCOV
24
import { coerceArray, coerceBooleanProperty, helpers, isFunction, isTemplateRef, isUndefinedOrNull } from 'ngx-tethys/util';
×
UNCOV
25
import { ThyDropdownMenuComponent } from './dropdown-menu.component';
×
UNCOV
26

×
27
export type ThyDropdownTrigger = 'click' | 'hover';
UNCOV
28

×
UNCOV
29
type ThyDropdownMenu = ThyDropdownMenuComponent | TemplateRef<SafeAny> | ComponentType<SafeAny>;
×
UNCOV
30

×
UNCOV
31
/**
×
UNCOV
32
 * thyDropdown 触发下拉菜单指令
×
UNCOV
33
 * @name thyDropdown
×
UNCOV
34
 * @order 10
×
UNCOV
35
 */
×
36
@Directive({
UNCOV
37
    selector: `[thyDropdown]`,
×
UNCOV
38
    host: {
×
UNCOV
39
        class: 'thy-dropdown'
×
UNCOV
40
    }
×
UNCOV
41
})
×
42
export class ThyDropdownDirective extends ThyOverlayDirectiveBase implements OnInit {
UNCOV
43
    private viewContainerRef = inject(ViewContainerRef);
×
44
    private popover = inject(ThyPopover);
UNCOV
45

×
UNCOV
46
    readonly menu: Signal<ThyDropdownMenu> = computed(() => {
×
47
        return (this.thyDropdownMenu() || this.thyDropdown())!;
UNCOV
48
    });
×
UNCOV
49

×
50
    private popoverRef: ThyPopoverRef<unknown>;
UNCOV
51

×
UNCOV
52
    popoverOpened = false;
×
53

54
    /**
55
     * Dropdown 下拉菜单,支持 thy-dropdown-menu 组件、TemplateRef 和自定义菜单组件
UNCOV
56
     */
×
57
    readonly thyDropdownMenu = input<ThyDropdownMenu>();
58

59
    /**
UNCOV
60
     * Dropdown 下拉菜单组件,和 thyDropdownMenu 参与相同,快捷传下拉菜单组件参数
×
UNCOV
61
     */
×
UNCOV
62
    readonly thyDropdown = input<ThyDropdownMenu>();
×
63

UNCOV
64
    /**
×
UNCOV
65
     * 下拉菜单触发方式
×
66
     * @type 'hover' | 'focus' | 'click' | string
UNCOV
67
     * @default click
×
UNCOV
68
     */
×
UNCOV
69
    readonly thyTrigger = input<ThyOverlayTrigger | string>('click');
×
70

71
    /**
UNCOV
72
     * 打开延迟毫秒
×
UNCOV
73
     */
×
UNCOV
74
    readonly thyShowDelay = input(100, { transform: numberAttribute });
×
UNCOV
75

×
76
    /**
77
     * 关闭延迟毫秒
78
     */
79
    readonly thyHideDelay = input(100, { transform: numberAttribute });
80

81
    /**
×
82
     * 弹出菜单后的当前触发元素的激活样式类
83
     */
84
    readonly thyActiveClass = input<string, string>('thy-dropdown-origin-active', {
×
85
        transform: (value: string) => value || 'thy-dropdown-origin-active'
86
    });
87

UNCOV
88
    /**
×
UNCOV
89
     * 弹出框的参数,底层使用 Popover 组件, 默认为`{ placement: "bottomLeft", insideClosable: true, minWidth: "240px", outsideClosable: true }`
×
UNCOV
90
     */
×
91
    readonly thyPopoverOptions =
UNCOV
92
        input<Pick<ThyPopoverConfig, 'placement' | 'height' | 'insideClosable' | 'minWidth' | 'outsideClosable'>>();
×
UNCOV
93

×
UNCOV
94
    /**
×
95
     * 弹出框的显示位置,会覆盖 thyPopoverOptions 中的 placement,`top` | `topLeft` | `topRight` | `bottom` | `bottomLeft` | `bottomRight` | `left` | `leftTop` | `leftBottom` | `right` | `rightTop` | `rightBottom`
UNCOV
96
     */
×
97
    readonly thyPlacement = input<ThyPlacement, ThyPlacement>('bottomLeft', { transform: (value: ThyPlacement) => value || 'bottomLeft' });
98

×
UNCOV
99
    /**
×
UNCOV
100
     * 点击 dropdown-menu 内部是否关闭弹出框,会覆盖 thyPopoverOptions 中的 insideClosable
×
UNCOV
101
     */
×
102
    readonly thyMenuInsideClosable = input(true, { transform: coerceBooleanProperty });
UNCOV
103

×
UNCOV
104
    /**
×
105
     * 弹出框 overlay panel 的类名
UNCOV
106
     * @type string | string[]
×
UNCOV
107
     */
×
108
    readonly thyPanelClass = input<string | string[], string | string[]>(['thy-dropdown-pane'], {
UNCOV
109
        transform: (value: string | string[]) =>
×
UNCOV
110
            (!isUndefinedOrNull(value) && ['thy-dropdown-pane'].concat(coerceArray(value))) || ['thy-dropdown-pane']
×
UNCOV
111
    });
×
UNCOV
112

×
UNCOV
113
    /**
×
UNCOV
114
     * 菜单 Active 事件,打开菜单返回 true,关闭返回 false
×
115
     */
116
    @Output() thyActiveChange = new EventEmitter<boolean>();
117

×
UNCOV
118
    constructor() {
×
UNCOV
119
        const elementRef = inject(ElementRef);
×
UNCOV
120
        const platform = inject(Platform);
×
121
        const focusMonitor = inject(FocusMonitor);
UNCOV
122
        const ngZone = inject(NgZone);
×
UNCOV
123
        const changeDetectorRef = inject(ChangeDetectorRef);
×
UNCOV
124

×
125
        super(elementRef, platform, focusMonitor, ngZone, true, changeDetectorRef);
UNCOV
126

×
UNCOV
127
        // TODO: 以下为 overlay 基类中参数,之后需统一修改
×
128
        effect(() => {
129
            this.trigger = (this.thyTrigger() || 'click') as ThyOverlayTrigger;
130
        });
1✔
131
        effect(() => {
1✔
132
            this.hideDelay = this.thyHideDelay() ?? 100;
133
        });
134
        effect(() => {
135
            this.showDelay = this.thyShowDelay() ?? 100;
136
        });
137
    }
138

139
    ngOnInit() {
140
        this.initialize();
141
    }
142

143
    createOverlay(): OverlayRef {
144
        let componentTypeOrTemplateRef: ComponentTypeOrTemplateRef<SafeAny>;
145
        const menu = this.menu();
1✔
146
        if (menu && menu instanceof ThyDropdownMenuComponent) {
147
            componentTypeOrTemplateRef = menu?.templateRef();
148
        } else if (isFunction(menu) || isTemplateRef(menu)) {
149
            componentTypeOrTemplateRef = menu as ComponentTypeOrTemplateRef<SafeAny>;
150
        }
151
        if (typeof ngDevMode === 'undefined' || ngDevMode) {
152
            if (!componentTypeOrTemplateRef) {
153
                throw new Error(`thyDropdownMenu is required`);
154
            }
155
        }
156

157
        const { placement, height, insideClosable, outsideClosable, minWidth } = Object.assign(
158
            { placement: 'bottomLeft', insideClosable: true, outsideClosable: true },
159
            this.thyPopoverOptions()
160
        );
161
        const thyPlacement = this.thyPlacement();
162
        const thyMenuInsideClosable = this.thyMenuInsideClosable();
163
        const config: ThyPopoverConfig = {
164
            origin: this.elementRef.nativeElement,
165
            hasBackdrop: false,
166
            viewContainerRef: this.viewContainerRef,
167
            offset: 0,
168
            panelClass: this.thyPanelClass(),
169
            placement: thyPlacement ? thyPlacement : placement,
170
            height,
171
            outsideClosable,
172
            insideClosable: helpers.isUndefined(thyMenuInsideClosable) ? insideClosable : thyMenuInsideClosable,
173
            minWidth,
174
            originActiveClass: this.thyActiveClass()
175
        };
176
        this.popoverRef = this.popover.open(componentTypeOrTemplateRef, config);
177
        this.popoverRef.afterOpened().subscribe(() => {
178
            this.thyActiveChange.emit(true);
179
        });
180
        this.popoverRef.afterClosed().subscribe(() => {
181
            this.popoverOpened = false;
182
            this.thyActiveChange.emit(false);
183
        });
184

185
        return this.popoverRef.getOverlayRef();
186
    }
187

188
    show(delay: number = this.showDelay) {
189
        if (this.hideTimeoutId) {
190
            clearTimeout(this.hideTimeoutId);
191
            this.hideTimeoutId = null;
192
        }
193

194
        if (this.disabled || (this.overlayRef && this.overlayRef.hasAttached())) {
195
            return;
196
        }
197
        if (this.trigger !== 'hover') {
198
            delay = 0;
199
        }
200

201
        this.showTimeoutId = setTimeout(() => {
202
            const overlayRef = this.createOverlay();
203
            this.overlayRef = overlayRef;
204
            this.popoverOpened = true;
205
            this.showTimeoutId = null;
206
            this.markForCheck();
207
        }, delay);
208
    }
209

210
    hide(delay: number = this.hideDelay) {
211
        if (this.showTimeoutId) {
212
            clearTimeout(this.showTimeoutId);
213
            this.showTimeoutId = null;
214
        }
215

216
        this.hideTimeoutId = setTimeout(() => {
217
            if (this.popoverRef) {
218
                this.popoverRef.close();
219
            }
220
            this.hideTimeoutId = null;
221
            this.markForCheck();
222
        }, delay);
223
    }
224
}
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