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

atinc / ngx-tethys / c572ad56-6796-461c-8809-6e2d7ed05a21

16 Apr 2025 06:23AM UTC coverage: 90.271% (+0.001%) from 90.27%
c572ad56-6796-461c-8809-6e2d7ed05a21

Pull #3341

circleci

minlovehua
refactor(all): resolve 30 circular denpendencies TINFR-1830
Pull Request #3341: refactor(all): resolve 30 circular denpendencies TINFR-1830

5614 of 6878 branches covered (81.62%)

Branch coverage included in aggregate %.

89 of 94 new or added lines in 38 files covered. (94.68%)

64 existing lines in 12 files now uncovered.

13370 of 14152 relevant lines covered (94.47%)

922.06 hits per line

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

84.38
/src/drag-drop/drag-ref.ts
1
import { NgZone, ElementRef, Renderer2 } from '@angular/core';
2
import { coerceElement } from '@angular/cdk/coercion';
3
import { Subject, fromEvent } from 'rxjs';
4
import { auditTime, takeUntil } from 'rxjs/operators';
5
import { ThyDragDropService } from './drag-drop.service';
6
import { ThyDragStartEvent, ThyDragEndEvent, ThyDragOverEvent, ThyDragDropEvent, ThyDropPosition } from './drag-drop.class';
1✔
7
import { IThyDragDirective, IThyDragHandleDirective, IThyDropContainerDirective } from './drag-drop.token';
8
import { coerceArray, isEmpty, isString } from 'ngx-tethys/util';
9

10
const dropPositionClass = {
11
    [ThyDropPosition.in]: 'thy-drop-position-in',
12
    [ThyDropPosition.before]: 'thy-drop-position-before',
13
    [ThyDropPosition.after]: 'thy-drop-position-after'
3✔
14
};
15

16
export class DragRef<T = any> {
1✔
17
    private rootElement: HTMLElement;
18

19
    private contentElement: HTMLElement;
38✔
20

38✔
21
    private target: HTMLElement;
38✔
22

38✔
23
    private handles: IThyDragHandleDirective[];
38✔
24

38✔
25
    private ngUnsubscribe$ = new Subject();
38✔
26

38✔
27
    started = new Subject<ThyDragStartEvent>();
38✔
28

38✔
29
    ended = new Subject<ThyDragEndEvent>();
38✔
30

38✔
31
    overed = new Subject<ThyDragOverEvent>();
38✔
32

38✔
33
    dropped = new Subject<ThyDragDropEvent>();
38✔
34

38✔
35
    entered = new Subject<DragEvent>();
38✔
36

37
    leaved = new Subject<DragEvent>();
38

38✔
39
    private _disabled = false;
1✔
40

1✔
41
    get disabled(): boolean {
1✔
42
        return (this.container && this.container.disabled) || this._disabled;
43
    }
44
    set disabled(value: boolean) {
45
        this._disabled = value;
38✔
46
    }
38✔
47

38✔
48
    private addPositionClass$ = new Subject<{ element: HTMLElement; class: string }>();
38✔
49

50
    constructor(
51
        element: ElementRef<HTMLElement> | HTMLElement,
8✔
52
        private drag: IThyDragDirective,
8✔
53
        private container: IThyDropContainerDirective<T>,
54
        private dragDropService: ThyDragDropService<T>,
55
        private document: any,
8✔
56
        private ngZone: NgZone,
8✔
57
        private renderer: Renderer2
58
    ) {
59
        this.withRootElement(element);
38✔
60
        this.subAddPositionClass();
61
    }
62

63
    subAddPositionClass() {
64
        this.addPositionClass$.pipe(auditTime(30), takeUntil(this.ngUnsubscribe$)).subscribe(data => {
65
            this.removeExistClassByMap(this.dragDropService.classMap);
66
            this.dragDropService.classMap.set(data.element, data.class);
1✔
67
            this.renderer.addClass(data.element, data.class);
68
        });
69
    }
1✔
70

71
    withRootElement(rootElement: ElementRef<HTMLElement> | HTMLElement): this {
72
        const element = coerceElement(rootElement);
38✔
73
        this.rootElement = element;
38✔
74
        this.registerDragDropEvents();
266!
75
        return this;
266✔
76
    }
77

78
    withContentElement(contentElement: ElementRef<HTMLElement> | HTMLElement): this {
79
        this.contentElement = coerceElement(contentElement);
80
        return this;
81
    }
3✔
82

3✔
83
    withHandles(handleOrHandles: IThyDragHandleDirective | IThyDragHandleDirective[]): this {
84
        this.handles = coerceArray(handleOrHandles);
85
        return this;
86
    }
87

88
    private registerDragDropEvents() {
3✔
89
        const events = {
2✔
90
            dragstart: this.dragStart,
2✔
91
            dragover: this.dragOver,
92
            dragend: this.dragEnd,
1✔
93
            drop: this.dragDrop,
1✔
94
            dragleave: this.dragLeave,
1✔
95
            dragenter: (event: DragEvent) => {
96
                this.entered.next(event);
97
            },
98
            mouseover: (event: MouseEvent) => {
1!
UNCOV
99
                this.target = event.target as HTMLElement;
×
100
            }
×
101
        };
UNCOV
102
        this.ngZone.runOutsideAngular(() => {
×
103
            for (const name in events) {
104
                if (events.hasOwnProperty(name)) {
105
                    fromEvent(this.rootElement, name).pipe(takeUntil(this.ngUnsubscribe$)).subscribe(events[name].bind(this));
1✔
106
                }
107
            }
108
        });
109
    }
4✔
110

4✔
111
    private dragStart(event: DragEvent) {
4✔
112
        event.stopPropagation();
113
        const dragStartEvent: ThyDragStartEvent = {
114
            event: event,
4✔
115
            item: this.drag.data,
116
            containerItems: this.container.data,
117
            currentIndex: this.container.data.indexOf(this.drag.data)
118
        };
1!
UNCOV
119
        if (this.disabled || !this.isTriggerHandle() || (this.container.beforeStart && !this.container.beforeStart(dragStartEvent))) {
×
120
            event.preventDefault();
121
            return false;
1!
122
        }
1✔
123
        this.dragDropService.previousDrag = this.drag;
UNCOV
124
        this.ngZone.run(() => {
×
125
            this.started.next(dragStartEvent);
126
        });
127
    }
2✔
128

2✔
129
    private isTriggerHandle() {
2✔
130
        if (this.handles && this.handles.length > 0) {
2✔
131
            const targetHandle = this.handles.find(handle => {
2✔
132
                return (
1✔
133
                    !handle.disabled && (handle.element.nativeElement === this.target || handle.element.nativeElement.contains(this.target))
134
                );
1✔
135
            });
136
            return !!targetHandle;
137
        } else {
138
            return true;
139
        }
140
    }
141

142
    private getPreviousEventData() {
1!
143
        const previousItem = this.dragDropService.previousDrag?.data;
1✔
144
        const previousContainerItems = this.dragDropService.previousDrag?.container?.data;
1✔
145
        return {
146
            previousItem: previousItem,
147
            previousContainerItems,
148
            previousIndex: isEmpty(previousContainerItems) ? -1 : previousContainerItems.indexOf(previousItem)
1✔
149
        };
1✔
150
    }
1✔
151

152
    private isContinueDragOver(event: ThyDragOverEvent, container: IThyDropContainerDirective<T>) {
153
        if (event.item === event.previousItem && event.position === ThyDropPosition.in) {
2✔
154
            return false;
2✔
155
        }
2✔
156
        if (container && container.beforeOver) {
2✔
157
            return container.beforeOver(event);
1✔
158
        }
159
        return true;
1✔
160
    }
161

162
    private dragOver(event: DragEvent) {
163
        event.stopPropagation();
164
        event.preventDefault();
165

166
        const dropPosition = this.calcDropPosition(event);
167
        const previousEventData = this.getPreviousEventData();
1!
UNCOV
168
        if (previousEventData.previousIndex < 0) {
×
UNCOV
169
            return;
×
170
        }
171
        const dragOverEvent: ThyDragOverEvent<T> = {
1✔
172
            event: event,
1✔
173
            item: this.drag.data,
174
            containerItems: this.drag.container.data,
175
            currentIndex: this.container.data.indexOf(this.drag.data),
176
            position: dropPosition,
1✔
177
            ...previousEventData
1✔
178
        };
1✔
179

180
        if (this.isContinueDragOver(dragOverEvent, this.container)) {
181
            this.dragOverHandler(dropPosition);
182
            this.overed.next(dragOverEvent);
183
        }
184
    }
1✔
185

186
    private dragOverHandler(position: ThyDropPosition) {
187
        const element = this.contentElement || this.rootElement;
1✔
188
        this.addPositionClass$.next({ element, class: dropPositionClass[position] });
1✔
189
        this.dragDropService.dropPosition = position;
1✔
190
    }
191

192
    private dragDrop(event: DragEvent) {
4✔
193
        event.stopPropagation();
4✔
194
        this.clearDragPositionClass();
4✔
195
        const previousEventData = this.getPreviousEventData();
4✔
196
        if (previousEventData.previousIndex < 0) {
197
            return;
198
        }
199
        const dragDropEvent: ThyDragDropEvent<T> = {
5✔
200
            event: event,
1!
201
            item: this.drag.data,
1✔
202
            containerItems: this.drag.container.data,
203
            currentIndex: this.container.data.indexOf(this.drag.data),
204
            position: this.calcDropPosition(event),
205
            ...previousEventData
206
        };
3✔
207
        if (this.dragDropService.previousDrag === this.drag || (this.container.beforeDrop && !this.container.beforeDrop(dragDropEvent))) {
3✔
208
            event.preventDefault();
3✔
209
            return;
3!
210
        }
211
        this.ngZone.run(() => {
212
            this.dropped.next(dragDropEvent);
3✔
213
        });
3!
214
    }
3✔
215

UNCOV
216
    private dragEnd(event: DragEvent) {
×
217
        this.clearDragPositionClass();
×
218
        this.ngZone.run(() => {
UNCOV
219
            this.ended.next({
×
220
                event: event,
221
                item: this.drag.data,
222
                containerItems: this.container.data
38✔
223
            });
224
        });
225
        this.dragDropService.previousDrag = undefined;
226
    }
227

228
    private dragLeave(event: DragEvent) {
229
        event.stopPropagation();
230
        this.clearDragPositionClass();
231
        this.leaved.next(event);
232
    }
233

234
    private clearDragPositionClass() {
235
        setTimeout(() => {
236
            const classMap = this.dragDropService.classMap;
237
            this.removeExistClassByMap(classMap);
238
            classMap.clear();
239
        }, 30);
240
    }
241

242
    private removeExistClassByMap(classMap: Map<Element, string>) {
243
        classMap.forEach((value, key) => {
244
            if (isString(value) && key) {
245
                this.renderer.removeClass(key, value);
246
            }
247
        });
248
    }
249

250
    private calcDropPosition(event: DragEvent): ThyDropPosition {
251
        const sideRange = 0.25;
252
        const minGap = 2;
253
        const { clientY } = event;
254
        const { top, bottom, height } = event.srcElement
255
            ? (event.srcElement as Element).getBoundingClientRect()
256
            : (event.target as Element).getBoundingClientRect();
257
        const des = Math.max(height * sideRange, minGap);
258
        if (clientY <= top + des) {
259
            return ThyDropPosition.before;
260
        } else if (clientY >= bottom - des) {
261
            return ThyDropPosition.after;
262
        }
263
        return ThyDropPosition.in;
264
    }
265

266
    dispose() {
267
        this.ngUnsubscribe$.complete();
268
    }
269
}
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