• 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

10.43
/src/popover/popover.service.ts
1
import { ComponentTypeOrTemplateRef, getFlexiblePositions, ThyAbstractOverlayRef, ThyAbstractOverlayService } from 'ngx-tethys/core';
2
import { FunctionProp, isFunction } from 'ngx-tethys/util';
3
import { of, Subject } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5

6
import { Directionality } from '@angular/cdk/bidi';
7
import { coerceArray, coerceElement } from '@angular/cdk/coercion';
8
import {
9
    FlexibleConnectedPositionStrategy,
10
    FlexibleConnectedPositionStrategyOrigin,
11
    Overlay,
12
    OverlayConfig,
13
    OverlayContainer,
14
    OverlayRef,
15
    PositionStrategy,
16
    ScrollStrategy,
17
    ViewportRuler
18
} from '@angular/cdk/overlay';
19
import { Platform } from '@angular/cdk/platform';
20
import { ComponentPortal } from '@angular/cdk/portal';
21
import { DOCUMENT } from '@angular/common';
1✔
22
import { ElementRef, Inject, Injectable, Injector, NgZone, OnDestroy, Optional, StaticProvider, TemplateRef } from '@angular/core';
23

×
24
import { ThyPopoverContainerComponent } from './popover-container.component';
25
import { ThyInternalPopoverRef, ThyPopoverRef } from './popover-ref';
×
26
import {
×
27
    THY_POPOVER_DEFAULT_CONFIG,
×
28
    THY_POPOVER_DEFAULT_CONFIG_VALUE,
×
29
    THY_POPOVER_SCROLL_STRATEGY,
×
30
    ThyPopoverConfig
×
31
} from './popover.config';
32
import { popoverAbstractOverlayOptions } from './popover.options';
33
import { SafeAny } from 'ngx-tethys/types';
×
34

35
/**
36
 * @public
×
37
 * @order 10
38
 */
39
@Injectable()
×
40
export class ThyPopover extends ThyAbstractOverlayService<ThyPopoverConfig, ThyPopoverContainerComponent> implements OnDestroy {
×
41
    private readonly ngUnsubscribe$ = new Subject();
42

×
43
    private originInstancesMap = new Map<
×
44
        ElementRef | HTMLElement,
45
        {
46
            config: ThyPopoverConfig;
×
47
            popoverRef: ThyPopoverRef<any, any>;
48
        }
49
    >();
50

×
51
    private buildPositionStrategy<TData>(config: ThyPopoverConfig<TData>): PositionStrategy {
×
52
        const origin: FlexibleConnectedPositionStrategyOrigin = config.originPosition ? config.originPosition : config.origin;
×
53
        // const positionStrategy = this.overlay.position().flexibleConnectedTo(origin);
×
54
        const positionStrategy = new FlexibleConnectedPositionStrategy(
×
55
            origin,
56
            this._viewportRuler,
57
            this._document,
×
58
            this._platform,
×
59
            this._overlayContainer
×
60
        );
61
        const positions = getFlexiblePositions(config.placement, config.offset, 'thy-popover');
62
        positionStrategy.withPositions(positions);
×
63
        positionStrategy.withGrowAfterOpen(true);
×
64
        positionStrategy.positionChanges.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(change => {
×
65
            if (change.scrollableViewProperties.isOverlayClipped) {
66
                // After position changes occur and the overlay is clipped by
67
                // a parent scrollable then close the tooltip.
×
68
                this.ngZone.run(() => this.close());
69
            }
70
        });
×
71
        return positionStrategy;
×
72
    }
73

74
    private buildScrollStrategy(config: ThyPopoverConfig): ScrollStrategy {
75
        if (config.scrollStrategy) {
76
            return config.scrollStrategy;
77
        } else if (this.scrollStrategy && isFunction(this.scrollStrategy)) {
78
            return this.scrollStrategy();
×
79
        } else {
×
80
            this.overlay.scrollStrategies.block();
81
        }
82
    }
83

84
    protected buildOverlayConfig<TData>(config: ThyPopoverConfig<TData>): OverlayConfig {
85
        const positionStrategy = this.buildPositionStrategy(config);
86
        const overlayConfig = this.buildBaseOverlayConfig(config, 'thy-popover-panel');
87
        overlayConfig.positionStrategy = positionStrategy;
×
88
        overlayConfig.scrollStrategy = this.buildScrollStrategy(config);
89
        return overlayConfig;
90
    }
×
91

×
92
    protected attachOverlayContainer(overlay: OverlayRef, config: ThyPopoverConfig<any>): ThyPopoverContainerComponent {
93
        const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
94
        const injector = Injector.create({
95
            parent: userInjector || this.injector,
×
96
            providers: [{ provide: ThyPopoverConfig, useValue: config }]
×
97
        });
98
        const containerPortal = new ComponentPortal(ThyPopoverContainerComponent, config.viewContainerRef, injector);
99
        const containerRef = overlay.attach<ThyPopoverContainerComponent>(containerPortal);
100
        return containerRef.instance;
1✔
101
    }
102

103
    protected createAbstractOverlayRef<T, TResult = unknown>(
104
        overlayRef: OverlayRef,
1✔
105
        containerInstance: ThyPopoverContainerComponent,
1✔
106
        config: ThyPopoverConfig
1✔
107
    ): ThyAbstractOverlayRef<T, ThyPopoverContainerComponent, TResult> {
1✔
108
        return new ThyInternalPopoverRef(overlayRef, containerInstance, config);
1✔
109
    }
1✔
110

1✔
111
    protected createInjector<T>(
112
        config: ThyPopoverConfig,
113
        popoverRef: ThyPopoverRef<T>,
×
114
        popoverContainer: ThyPopoverContainerComponent
×
115
    ): Injector {
×
116
        const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
×
117

×
118
        const injectionTokens: StaticProvider[] = [
×
119
            { provide: ThyPopoverContainerComponent, useValue: popoverContainer },
120
            {
121
                provide: ThyPopoverRef,
122
                useValue: popoverRef
×
123
            }
×
124
        ];
125

×
126
        if (config.direction && (!userInjector || !userInjector.get<Directionality | null>(Directionality, null))) {
127
            injectionTokens.push({
128
                provide: Directionality,
×
129
                useValue: {
130
                    value: config.direction,
131
                    change: of()
132
                }
133
            });
134
        }
×
135

136
        return Injector.create({ parent: userInjector || this.injector, providers: injectionTokens });
137
    }
138

×
139
    private originElementAddActiveClass(config: ThyPopoverConfig) {
×
140
        if (config.originActiveClass) {
141
            coerceElement<HTMLElement>(config.origin).classList.add(...coerceArray(config.originActiveClass));
×
142
        }
×
143
    }
×
144

×
145
    private originElementRemoveActiveClass(config: ThyPopoverConfig) {
×
146
        if (config.originActiveClass) {
147
            coerceElement<HTMLElement>(config.origin).classList.remove(...coerceArray(config.originActiveClass));
×
148
        }
×
149
    }
150

151
    constructor(
152
        overlay: Overlay,
×
153
        injector: Injector,
154
        @Optional()
155
        @Inject(THY_POPOVER_DEFAULT_CONFIG)
156
        defaultConfig: ThyPopoverConfig,
157
        @Inject(THY_POPOVER_SCROLL_STRATEGY)
158
        scrollStrategy: FunctionProp<ScrollStrategy>,
×
159
        private ngZone: NgZone,
160
        private _viewportRuler: ViewportRuler,
161
        @Optional() @Inject(DOCUMENT) private _document: any,
162
        private _platform: Platform,
163
        private _overlayContainer: OverlayContainer
164
    ) {
×
165
        super(
166
            popoverAbstractOverlayOptions,
167
            overlay,
168
            injector,
169
            {
170
                ...THY_POPOVER_DEFAULT_CONFIG_VALUE,
171
                ...defaultConfig
×
172
            },
×
173
            scrollStrategy
×
174
        );
175
    }
×
176

177
    private ensureCloseClosest(origin: HTMLElement) {
178
        let closeAndEnd = false;
179
        this.originInstancesMap.forEach((value, key) => {
180
            if (value.config.manualClosure) {
181
                if (key === origin) {
×
182
                    value.popoverRef.close();
183
                    closeAndEnd = true;
184
                }
185
            } else {
186
                if (key === origin) {
187
                    closeAndEnd = true;
1✔
188
                }
189
                value.popoverRef.close();
1✔
190
            }
191
        });
192
        return closeAndEnd;
193
    }
194

195
    /**
196
     * 打开悬浮层
197
     */
198
    open<T, TData = unknown, TResult = unknown>(
199
        componentOrTemplateRef: ComponentTypeOrTemplateRef<T>,
200
        config?: ThyPopoverConfig<TData>
201
    ): ThyPopoverRef<T, TResult> {
1✔
202
        const originElement = coerceElement(config.origin);
203
        // 默认关闭之前的弹出框
204
        // 1. 当之前的 Popover 设置 manualClosure 为 true 时, 弹出其他 Popover 时不自动关闭 manualClosure 为 true 的 Popover
205
        // 2. 当前的 Origin 对应的 Popover 已经弹出,不管 manualClosure 设置为何,直接关闭并返回
206
        if (this.ensureCloseClosest(originElement)) {
207
            return;
208
        }
209

210
        const popoverRef = this.openOverlay<T, TResult>(componentOrTemplateRef, config) as ThyPopoverRef<T, TResult, TData>;
211
        config = popoverRef.containerInstance.config;
212
        popoverRef.afterClosed().subscribe(() => {
213
            this.originElementRemoveActiveClass(config);
214
            this.originInstancesMap.delete(originElement);
215
        });
216

217
        this.originElementAddActiveClass(config);
218
        this.originInstancesMap.set(originElement, {
219
            config,
220
            popoverRef
221
        });
222

223
        return popoverRef;
224
    }
225

226
    /**
227
     * 根据Id获取打开的悬浮层
228
     */
229
    getPopoverById(id: string): ThyPopoverRef<any> | undefined {
230
        return this.getAbstractOverlayById(id) as ThyPopoverRef<any> | undefined;
231
    }
232

233
    /**
234
     * 获取已经打开的悬浮层
235
     */
236
    getOpenedPopovers(): ThyPopoverRef<SafeAny>[] {
237
        return this.getAbstractOverlays();
238
    }
239

240
    /**
241
     * @description.en-us Finds the closest ThyPopoverRef to an element by looking at the DOM.
242
     * @description 通过 Dom 元素查找最近弹出的悬浮层
243
     */
244
    getClosestPopover(element: HTMLElement): ThyPopoverRef<any> | undefined {
245
        const parent = element.closest('.thy-popover-container');
246

247
        if (parent && parent.id) {
248
            return this.getPopoverById(parent.id);
249
        }
250
        return null;
251
    }
252

253
    /**
254
     * 关闭悬浮层
255
     */
256
    close<T>(result?: T, force?: boolean) {
257
        super.close(result, force);
258
    }
259

260
    /**
261
     * @internal
262
     */
263
    ngOnDestroy() {
264
        this.dispose();
265
    }
266
}
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