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

atinc / ngx-tethys / c0ef8457-a839-451f-8b72-80fd73106231

02 Apr 2024 02:27PM UTC coverage: 90.524% (-0.06%) from 90.585%
c0ef8457-a839-451f-8b72-80fd73106231

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @Input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

4987 of 6108 branches covered (81.65%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

202 existing lines in 53 files now uncovered.

12246 of 12929 relevant lines covered (94.72%)

1055.59 hits per line

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

97.4
/src/property/property-item.component.ts
1
import { ThyClickDispatcher } from 'ngx-tethys/core';
2
import { ThyFlexibleText } from 'ngx-tethys/flexible-text';
3
import { combineLatest, fromEvent, Subject, Subscription, timer } from 'rxjs';
4
import { delay, filter, take, takeUntil } from 'rxjs/operators';
5

6
import { OverlayOutsideClickDispatcher, OverlayRef } from '@angular/cdk/overlay';
7
import { NgIf, NgTemplateOutlet } from '@angular/common';
8
import {
9
    booleanAttribute,
10
    ChangeDetectionStrategy,
11
    ChangeDetectorRef,
12
    Component,
13
    ContentChild,
14
    ElementRef,
15
    EventEmitter,
1✔
16
    HostBinding,
17
    Input,
270✔
18
    NgZone,
19
    numberAttribute,
20
    OnChanges,
47✔
21
    OnDestroy,
47✔
22
    OnInit,
47✔
23
    Output,
47✔
24
    SimpleChanges,
47✔
25
    TemplateRef,
47✔
26
    ViewChild
47✔
27
} from '@angular/core';
47✔
28

47✔
29
import { ThyProperties } from './properties.component';
47✔
30

47✔
31
export type ThyPropertyItemOperationTrigger = 'hover' | 'always';
47✔
32

47✔
33
/**
47✔
34
 * 属性组件
35
 * @name thy-property-item
36
 */
47✔
37
@Component({
47✔
38
    selector: 'thy-property-item',
51✔
39
    templateUrl: './property-item.component.html',
51✔
40
    host: {
41
        class: 'thy-property-item',
42
        '[class.thy-property-item-operational]': '!!operation',
43
        '[class.thy-property-item-operational-hover]': "thyOperationTrigger === 'hover'"
52✔
44
    },
22✔
45
    changeDetection: ChangeDetectionStrategy.OnPush,
46
    standalone: true,
47
    imports: [NgIf, ThyFlexibleText, NgTemplateOutlet]
30✔
48
})
30✔
49
export class ThyPropertyItem implements OnInit, OnChanges, OnDestroy {
30✔
50
    /**
30✔
51
     * 属性名称
1✔
52
     * @type sting
1✔
53
     * @default thyLabelText
54
     */
55
    @Input() thyLabelText: string;
56

57
    /**
40✔
58
     * 设置属性是否是可编辑的
40✔
59
     * @type sting
11✔
60
     * @default false
61
     */
40✔
62
    @Input({ transform: booleanAttribute }) thyEditable: boolean;
40✔
63

64
    /**
65
     * 设置跨列的数量
66
     * @type number
67
     */
68
    @Input({ transform: numberAttribute }) thySpan: number = 1;
UNCOV
69

×
70
    /**
71
     * 设置属性操作现实触发方式,默认 always 一直显示
72
     * @type 'hover' | 'always'
6✔
73
     */
74
    @Input() thyOperationTrigger: ThyPropertyItemOperationTrigger = 'always';
75

69✔
76
    @Output() thyEditingChange: EventEmitter<boolean> = new EventEmitter<boolean>();
44✔
77

44✔
78
    /**
22✔
79
     * 属性名称自定义模板
80
     * @type TemplateRef
22✔
81
     */
82
    @ContentChild('label', { static: true }) label!: TemplateRef<void>;
83

6✔
84
    /**
6✔
85
     * 属性内容编辑模板,只有在 thyEditable 为 true 时生效
86
     * @type TemplateRef
87
     */
88
    @ContentChild('editor', { static: true }) editor!: TemplateRef<void>;
89

90
    /**
1✔
91
     * 操作区模板
1✔
92
     * @type TemplateRef
1!
93
     */
1✔
94
    @ContentChild('operation', { static: true }) operation!: TemplateRef<void>;
95

96
    /**
1✔
97
     * @private
98
     */
99
    @ViewChild('contentTemplate', { static: true }) content!: TemplateRef<void>;
100

101
    /**
5✔
102
     * @private
103
     */
104
    @ViewChild('item', { static: true }) itemContent: ElementRef<HTMLElement>;
2✔
105

106
    editing: boolean;
107

2✔
108
    changes$ = new Subject<SimpleChanges>();
109

110
    private destroy$ = new Subject();
111

6✔
112
    private eventDestroy$ = new Subject();
6✔
113

1✔
114
    private originOverlays: OverlayRef[] = [];
115

116
    private clickEventSubscription: Subscription;
5✔
117

118
    @HostBinding('style.grid-column')
119
    get gridColumn() {
120
        return `span ${Math.min(this.thySpan, this.parent.thyColumn)}`;
121
    }
47✔
122

47✔
123
    isVertical = false;
47✔
124

47✔
125
    constructor(
126
        private cdr: ChangeDetectorRef,
1✔
127
        private clickDispatcher: ThyClickDispatcher,
128
        private ngZone: NgZone,
129
        private overlayOutsideClickDispatcher: OverlayOutsideClickDispatcher,
130
        private parent: ThyProperties
131
    ) {
132
        this.originOverlays = [...this.overlayOutsideClickDispatcher._attachedOverlays] as OverlayRef[];
133
    }
1✔
134

135
    ngOnInit() {
136
        this.subscribeClick();
137
        this.parent.layout$.pipe(takeUntil(this.destroy$)).subscribe(layout => {
138
            this.isVertical = layout === 'vertical';
139
            this.cdr.markForCheck();
140
        });
141
    }
142

143
    ngOnChanges(changes: SimpleChanges): void {
144
        if (changes.thyEditable && changes.thyEditable.currentValue) {
145
            this.subscribeClick();
146
        } else {
147
            this.setEditing(false);
1✔
148
            this.eventDestroy$.next();
149
            this.eventDestroy$.complete();
150

151
            if (this.clickEventSubscription) {
152
                this.clickEventSubscription.unsubscribe();
153
                this.clickEventSubscription = null;
154
            }
155
        }
156
    }
157

158
    setEditing(editing: boolean) {
159
        this.ngZone.run(() => {
160
            if (!!this.editing !== !!editing) {
161
                this.thyEditingChange.emit(editing);
162
            }
163
            this.editing = editing;
164
            this.cdr.markForCheck();
165
        });
166
    }
167

168
    /**
169
     * @deprecated please use setEditing(editing: boolean)
170
     */
171
    setKeepEditing(keep: boolean) {
172
        this.setEditing(keep);
173
    }
174

175
    private hasOverlay() {
176
        return this.overlayOutsideClickDispatcher._attachedOverlays.length > this.originOverlays.length;
177
    }
178

179
    private subscribeClick() {
180
        if (this.thyEditable === true) {
181
            this.ngZone.runOutsideAngular(() => {
182
                if (this.clickEventSubscription) {
183
                    return;
184
                }
185
                this.clickEventSubscription = fromEvent(this.itemContent.nativeElement, 'click')
186
                    .pipe(takeUntil(this.eventDestroy$))
187
                    .subscribe(() => {
188
                        this.setEditing(true);
189
                        this.bindEditorBlurEvent(this.itemContent.nativeElement);
190
                    });
191
            });
192
        }
193
    }
194

195
    private subscribeOverlayDetach() {
196
        const openedOverlays = this.overlayOutsideClickDispatcher._attachedOverlays.slice(this.originOverlays.length);
197
        const overlaysDetachments$ = openedOverlays.map(overlay => overlay.detachments());
198
        if (overlaysDetachments$.length) {
199
            combineLatest(overlaysDetachments$)
200
                .pipe(delay(50), take(1), takeUntil(this.destroy$))
201
                .subscribe(() => {
202
                    this.setEditing(false);
203
                });
204
        }
205
    }
206

207
    private subscribeDocumentClick(editorElement: HTMLElement) {
208
        this.clickDispatcher
209
            .clicked(0)
210
            .pipe(
211
                filter(event => {
212
                    return !editorElement.contains(event.target as HTMLElement);
213
                }),
214
                take(1),
215
                takeUntil(this.destroy$)
216
            )
217
            .subscribe(() => {
218
                this.setEditing(false);
219
            });
220
    }
221

222
    private bindEditorBlurEvent(editorElement: HTMLElement) {
223
        timer(0).subscribe(() => {
224
            if (this.hasOverlay()) {
225
                this.subscribeOverlayDetach();
226
            } else {
227
                this.subscribeDocumentClick(editorElement);
228
            }
229
        });
230
    }
231

232
    ngOnDestroy(): void {
233
        this.destroy$.next();
234
        this.destroy$.complete();
235

236
        this.eventDestroy$.next();
237
        this.eventDestroy$.complete();
238
    }
239
}
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