• 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

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 { ThyGuiderManager } from './guider-manager';
8
import { ThyGuiderRef } from './guider-ref';
9
import { ThyGuiderStep } from './guider.class';
1✔
10
import { isPositionDataType } from './utils';
11

12
const pointContainerSize = 28;
13

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

×
21
    private lastPointerContainer: any;
×
22

×
23
    private lastTargetElement: Element;
×
24

25
    private targetElementObserver: Subscription;
26

×
27
    private lastTipContainer: any;
×
28

29
    private guiderRef: ThyGuiderRef;
30

×
31
    private lastPopoverRef: ThyPopoverRef<any>;
×
32

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

×
45
    public show(guiderRef: ThyGuiderRef) {
46
        this.guiderRef = guiderRef;
47
        this.createPoint(this.step, guiderRef);
48
    }
49

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

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

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

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

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

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

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

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

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

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

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

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

121
        return currentPointContainer;
122
    }
×
123

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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