• 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

1.1
/src/guider/guider-step-ref.ts
1
import { Overlay } from '@angular/cdk/overlay';
2
import { DOCUMENT } from '@angular/common';
3
import { Inject, Renderer2, RendererFactory2 } from '@angular/core';
4
import { ThyPopover, ThyPopoverConfig, ThyPopoverRef } from 'ngx-tethys/popover';
5
import { coerceArray, isArray, isNull, isString, isUndefinedOrNull } from 'ngx-tethys/util';
6
import { fromEvent, Subscription } from 'rxjs';
7
import { IThyGuiderManager, IThyGuiderRef } from './guider.interface';
8
import { ThyGuiderStep } from './guider.class';
1✔
9
import { isPositionDataType } from './utils';
10

11
const pointContainerSize = 28;
12

13
/**
14
 * @public
UNCOV
15
 * @order 50
×
UNCOV
16
 */
×
UNCOV
17
export class ThyGuiderStepRef {
×
UNCOV
18
    private renderer: Renderer2;
×
UNCOV
19

×
UNCOV
20
    private lastPointerContainer: any;
×
UNCOV
21

×
UNCOV
22
    private lastTargetElement: Element;
×
23

24
    private targetElementObserver: Subscription;
UNCOV
25

×
UNCOV
26
    private lastTipContainer: any;
×
27

28
    private guiderRef: IThyGuiderRef;
UNCOV
29

×
UNCOV
30
    private lastPopoverRef: ThyPopoverRef<any>;
×
31

32
    constructor(
33
        public step: ThyGuiderStep,
UNCOV
34
        public stepIndex: number,
×
UNCOV
35
        private readonly rendererFactory: RendererFactory2,
×
UNCOV
36
        private popover: ThyPopover,
×
UNCOV
37
        private guiderManager: IThyGuiderManager,
×
38
        private overlay: Overlay,
39
        @Inject(DOCUMENT) private document: Document
40
    ) {
UNCOV
41
        this.renderer = this.rendererFactory.createRenderer(null, null);
×
42
    }
UNCOV
43

×
44
    public show(guiderRef: IThyGuiderRef): void {
45
        this.guiderRef = guiderRef;
46
        this.createPoint(this.step, guiderRef);
47
    }
UNCOV
48

×
UNCOV
49
    public dispose() {
×
UNCOV
50
        this.removeLastPointContainer();
×
51
        this.removeTip();
UNCOV
52
    }
×
UNCOV
53

×
54
    private getTargetElement(step: ThyGuiderStep) {
×
55
        let targetElement: HTMLElement;
UNCOV
56

×
UNCOV
57
        if (step.target && !isPositionDataType(step.target)) {
×
58
            const target = [...coerceArray(step.target)];
UNCOV
59

×
UNCOV
60
            while (target.length && isUndefinedOrNull(targetElement)) {
×
UNCOV
61
                targetElement = this.document.querySelector(target.shift());
×
62
            }
UNCOV
63
        } else {
×
64
            targetElement = this.guiderManager.getActiveTarget(step.key);
65
        }
UNCOV
66
        return targetElement;
×
UNCOV
67
    }
×
UNCOV
68

×
69
    private createPoint(step: ThyGuiderStep, guiderRef: IThyGuiderRef) {
70
        // target 为空并且 guiderManager 中的 targetMap 也没有此step 的 key,或者 target 直接为 坐标数组
UNCOV
71
        // 则执行无 target 的显示
×
UNCOV
72
        if (!this.isTipHasTarget(step)) {
×
UNCOV
73
            this.createTip(this.step);
×
74
            return;
75
        }
UNCOV
76

×
77
        const targetElement = this.getTargetElement(step);
78

UNCOV
79
        if ((typeof ngDevMode === 'undefined' || ngDevMode) && isNull(targetElement)) {
×
UNCOV
80
            throw new Error(`there is no target called ${coerceArray(step.target).join(' or ')}`);
×
UNCOV
81
        }
×
UNCOV
82
        this.targetElementObserver = fromEvent(targetElement, 'click').subscribe(() => {
×
83
            guiderRef.targetClicked().next(step);
UNCOV
84
        });
×
UNCOV
85
        const positionValue = targetElement?.style?.position;
×
UNCOV
86
        if (!positionValue || positionValue === 'static') {
×
UNCOV
87
            this.renderer.setStyle(targetElement, 'position', 'relative');
×
UNCOV
88
        }
×
89
        this.setStyleForPointContainer(step, targetElement);
90
    }
UNCOV
91

×
UNCOV
92
    private setStyleForPointContainer(step: ThyGuiderStep, targetElement: Element) {
×
93
        const pointPosition = this.getPointPosition(step, targetElement);
UNCOV
94
        const pointContainer = this.setPointPosition(pointPosition);
×
UNCOV
95

×
UNCOV
96
        this.renderPoint(targetElement, pointContainer);
×
97
    }
98

99
    private getPointPosition(step: ThyGuiderStep, targetElement: Element): [number, number] {
100
        const targetElementClientRect = targetElement.getBoundingClientRect();
UNCOV
101
        const { width: targetElementWidth, height: targetElementHeight } = targetElementClientRect;
×
UNCOV
102
        const pointOffset = step.pointOffset;
×
UNCOV
103
        // 只通过 pointOffset 控制 point 的位置,默认在 target 的右下角,
×
UNCOV
104
        // offset 的基点也为默认位置
×
105
        return [targetElementWidth + pointOffset[0], targetElementHeight + pointOffset[1]];
106
    }
UNCOV
107

×
UNCOV
108
    private setPointPosition(pointPosition: [number, number]) {
×
UNCOV
109
        const currentPointContainer = this.renderer.createElement('div');
×
110

111
        this.renderer.addClass(currentPointContainer, 'thy-guider-highlight-container');
112
        if (this.guiderRef.config.pointClass) {
UNCOV
113
            this.addPointClass(currentPointContainer, this.guiderRef.config.pointClass);
×
UNCOV
114
        }
×
115
        this.renderer.setStyle(currentPointContainer, 'position', 'absolute');
116
        this.renderer.setStyle(currentPointContainer, 'left', pointPosition[0] + 'px');
UNCOV
117
        this.renderer.setStyle(currentPointContainer, 'top', pointPosition[1] + 'px');
×
118
        this.renderer.setStyle(currentPointContainer, 'transform', 'translate(-100%,-100%)');
119

120
        return currentPointContainer;
UNCOV
121
    }
×
UNCOV
122

×
123
    private addPointClass(el: any, pointClass: string | string[]) {
124
        if (isString(pointClass)) {
125
            this.renderer.addClass(el, pointClass);
126
        }
×
127
        if (isArray(pointClass)) {
128
            pointClass.forEach(classItem => {
129
                this.renderer.addClass(el, classItem);
130
            });
131
        }
132
    }
133

134
    private renderPoint(targetElement: Element, pointContainer: any) {
135
        this.renderer.appendChild(targetElement, pointContainer);
136
        this.lastPointerContainer = pointContainer;
137
        this.lastTargetElement = targetElement;
UNCOV
138
        this.createTip(this.step);
×
UNCOV
139
    }
×
140

UNCOV
141
    private removeLastPointContainer() {
×
142
        if (this.lastPointerContainer) {
143
            this.renderer.removeChild(this.document.body, this.lastPointerContainer);
UNCOV
144
            this.lastPointerContainer = undefined;
×
UNCOV
145
        }
×
UNCOV
146
    }
×
UNCOV
147

×
UNCOV
148
    private createTip(step: ThyGuiderStep) {
×
UNCOV
149
        if (this.isTipHasTarget(step)) {
×
UNCOV
150
            this.tipWithTarget(step);
×
UNCOV
151
        } else {
×
152
            this.tipWithoutTarget(step);
153
        }
UNCOV
154
    }
×
UNCOV
155

×
UNCOV
156
    private tipWithoutTarget(step: ThyGuiderStep) {
×
UNCOV
157
        const position = this.getTipPosition(step);
×
UNCOV
158
        this.lastPopoverRef = this.popover.open(this.guiderRef.config.hintComponent, {
×
159
            origin: null,
160
            originPosition: position,
161
            originActiveClass: '',
×
162
            panelClass: this.guiderRef.config.hintClass || '',
163
            backdropClosable: false,
164
            hasBackdrop: false,
165
            manualClosure: true,
166
            initialState: {
167
                guiderRef: this.guiderRef,
168
                stepRef: this
169
            },
170
            scrollStrategy: this.overlay.scrollStrategies.block()
UNCOV
171
        });
×
UNCOV
172
    }
×
UNCOV
173

×
UNCOV
174
    private getTipPosition(step: ThyGuiderStep): { x: number; y: number } {
×
175
        if (isPositionDataType(step.target)) {
UNCOV
176
            return step.target;
×
177
        }
178
        return this.guiderRef.config.defaultPosition;
UNCOV
179
    }
×
UNCOV
180

×
UNCOV
181
    private createTipContainer() {
×
UNCOV
182
        const tipContainer = this.renderer.createElement('div');
×
UNCOV
183
        this.renderer.addClass(tipContainer, 'thy-guider-content-container');
×
UNCOV
184
        this.renderer.setStyle(tipContainer, 'position', 'absolute');
×
UNCOV
185
        this.renderer.setStyle(tipContainer, 'top', '0px');
×
186
        this.renderer.setStyle(tipContainer, 'right', '0px');
×
187
        this.renderer.setStyle(tipContainer, 'bottom', '0px');
×
188
        this.renderer.setStyle(tipContainer, 'left', '0px');
189
        return tipContainer;
UNCOV
190
    }
×
191

×
192
    private tipWithTarget(step: ThyGuiderStep) {
×
193
        const targetElement = this.getTargetElement(step);
194
        const hintContainer = this.createTipContainer();
UNCOV
195

×
196
        this.renderer.appendChild(targetElement, hintContainer);
×
197
        this.lastTipContainer = hintContainer;
×
198

199
        const popoverConfig = {
UNCOV
200
            origin: hintContainer,
×
UNCOV
201
            placement: step.hintPlacement,
×
202
            panelClass: this.guiderRef.config.hintClass || '',
×
203
            backdropClosable: false,
204
            hasBackdrop: false,
UNCOV
205
            manualClosure: true,
×
206
            initialState: {
207
                guiderRef: this.guiderRef,
UNCOV
208
                stepRef: this
×
UNCOV
209
            },
×
UNCOV
210
            scrollStrategy: this.overlay.scrollStrategies.block()
×
211
        } as ThyPopoverConfig<any>;
UNCOV
212

×
UNCOV
213
        const pointPosition = this.getPointPosition(step, targetElement);
×
UNCOV
214
        const hintOffset = this.getTipOffset(step, pointPosition, targetElement);
×
215
        if (hintOffset) {
UNCOV
216
            popoverConfig.offset = hintOffset;
×
UNCOV
217
        }
×
UNCOV
218
        this.lastPopoverRef = this.popover.open(this.guiderRef.config.hintComponent, popoverConfig);
×
UNCOV
219
    }
×
220

221
    private getTipOffset(step: ThyGuiderStep, pointPosition: [number, number], targetElement: Element): number {
222
        const hintPlacement = step.hintPlacement;
UNCOV
223
        const targetElementClientRect = targetElement.getBoundingClientRect();
×
UNCOV
224
        const { width: targetElementWidth, height: targetElementHeight } = targetElementClientRect;
×
225
        let hintOffset: number = step.hintOffset || 0;
226
        const pointXAxisOffset = pointPosition[0];
UNCOV
227
        const pointYAxisOffset = pointPosition[1];
×
228

229
        if (hintPlacement.startsWith('top')) {
230
            if (pointYAxisOffset < pointContainerSize) {
1✔
231
                hintOffset = hintOffset + Math.abs(pointYAxisOffset) + pointContainerSize;
232
            }
233
        } else if (hintPlacement.startsWith('bottom')) {
234
            if (pointYAxisOffset > targetElementHeight) {
235
                hintOffset = hintOffset + (pointYAxisOffset - targetElementHeight) + 10; // 10 为空隙量
236
            }
237
        } else if (hintPlacement.startsWith('left')) {
238
            if (pointXAxisOffset < 0) {
239
                hintOffset = hintOffset + Math.abs(pointXAxisOffset) + pointContainerSize;
240
            }
241
        } else if (hintPlacement.startsWith('right')) {
242
            if (pointXAxisOffset > targetElementWidth) {
243
                hintOffset = hintOffset + (pointXAxisOffset - targetElementWidth) + 10; // 10 为空隙量
244
            }
245
        }
246
        return hintOffset;
247
    }
248

249
    private removeTip() {
250
        if (this.lastPopoverRef) {
251
            this.lastPopoverRef.close();
252
            this.lastPopoverRef = undefined;
253
        }
254
        if (this.lastTipContainer) {
255
            this.renderer.removeChild(this.document.body, this.lastTipContainer);
256
            this.lastTipContainer = undefined;
257
        }
258
        if (this.lastTargetElement && this.targetElementObserver) {
259
            this.targetElementObserver.unsubscribe();
260
            this.lastTargetElement = undefined;
261
            this.targetElementObserver = undefined;
262
        }
263
    }
264

265
    private isTipHasTarget(step: ThyGuiderStep): boolean {
266
        if (step.target) {
267
            return !isPositionDataType(step.target);
268
        } else {
269
            return !!this.guiderManager.getActiveTarget(step.key);
270
        }
271
    }
272
}
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