• 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

8.43
/src/mention/mention.directive.ts
1
import { ThyPopover, ThyPopoverConfig, ThyPopoverRef } from 'ngx-tethys/popover';
2
import { EMPTY, fromEvent, Subject } from 'rxjs';
3
import { switchMap, takeUntil } from 'rxjs/operators';
4

5
import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Optional, Output, Self } from '@angular/core';
6
import { NgControl } from '@angular/forms';
7

8
import { createMentionAdapter, MatchedMention, MentionAdapter, MentionInputorElement } from './adapter';
9
import { CaretPositioner } from './caret-positioner';
10
import { Mention, MentionDefaultDataItem, MentionSuggestionSelectEvent } from './interfaces';
11
import { ThyMentionSuggestionsComponent } from './suggestions/suggestions.component';
1✔
12
import { isInputOrTextarea } from 'ngx-tethys/util';
1✔
13

14
const SUGGESTION_BACKDROP_CLASS = 'thy-mention-suggestions-backdrop';
15

16
const POPOVER_DEFAULT_CONFIG = {
1✔
17
    backdropClass: SUGGESTION_BACKDROP_CLASS,
18
    placement: 'bottomLeft'
19
};
20

×
21
const DEFAULT_MENTION_CONFIG: Partial<Mention> = {
×
22
    autoClose: true,
23
    emptyText: '无匹配数据,按空格完成输入',
24
    search: (term: string, data: MentionDefaultDataItem[]) => {
25
        return data.filter(item => {
26
            return !item.name || item.name.toLowerCase().includes(term.toLowerCase());
27
        });
28
    }
29
};
1✔
30

31
/**
×
32
 * @name thyMention
33
 * @order 10
34
 */
×
35
@Directive({
×
36
    selector: '[thyMention]',
×
37
    standalone: true
×
38
})
×
39
export class ThyMentionDirective implements OnInit, OnDestroy {
40
    private adapter: MentionAdapter = null;
×
41

42
    public openedSuggestionsRef: ThyPopoverRef<ThyMentionSuggestionsComponent>;
43

44
    private _mentions: Mention<any>[];
45
    get mentions() {
×
46
        return this._mentions;
47
    }
48

×
49
    /**
×
50
     * 提及输入配置参数,同时支持多个提及规则
×
51
     * @type Mention<any>[]
×
52
     */
×
53
    @Input('thyMention') set mentions(value: Mention<any>[]) {
×
54
        this._mentions = value;
×
55
        if (this._mentions) {
×
56
            this._mentions = this._mentions.map(mention => {
57
                if ((typeof ngDevMode === 'undefined' || ngDevMode) && !mention.trigger) {
58
                    throw new Error(`mention trigger is required`);
×
59
                }
60
                return Object.assign({}, DEFAULT_MENTION_CONFIG, mention);
×
61
            });
×
62
        }
63
    }
×
64

×
65
    /**
66
     * Popover 弹出层参数配置
67
     */
68
    @Input('thyPopoverConfig') popoverConfig: ThyPopoverConfig;
×
69

70
    /**
×
71
     * 选择后的回调函数
×
72
     */
×
73
    @Output('thySelectSuggestion') select = new EventEmitter<MentionSuggestionSelectEvent>();
×
74

75
    get isOpened() {
76
        return !!this.openedSuggestionsRef;
77
    }
×
78

79
    private destroy$ = new Subject<void>();
×
80

×
81
    private openedSuggestionsRef$ = new Subject<ThyPopoverRef<ThyMentionSuggestionsComponent> | null>();
82

83
    constructor(
84
        private elementRef: ElementRef<HTMLElement>,
×
85
        private thyPopover: ThyPopover,
86
        @Optional() @Self() private ngControl: NgControl
87
    ) {
×
88
        this.adapter = createMentionAdapter(elementRef.nativeElement as MentionInputorElement);
89
    }
90

×
91
    ngOnInit() {
92
        fromEvent(this.elementRef.nativeElement, 'input')
93
            .pipe(takeUntil(this.destroy$))
×
94
            .subscribe(event => this.onInput(event));
×
95

×
96
        fromEvent(this.elementRef.nativeElement, 'click')
97
            .pipe(takeUntil(this.destroy$))
98
            .subscribe(event => this.onClick(event));
×
99

100
        this.openedSuggestionsRef$
101
            .pipe(
102
                switchMap(openedSuggestionsRef =>
×
103
                    // Re-subscribe to `suggestionSelect$` every time the suggestions component is re-created,
×
104
                    // otherwise, unsubscribe, if it gets closed.
×
105
                    openedSuggestionsRef ? openedSuggestionsRef.componentInstance.suggestionSelect$ : EMPTY
×
106
                ),
×
107
                takeUntil(this.destroy$)
108
            )
109
            .subscribe(event => {
110
                const newValue = this.adapter.insertMention(event.item);
111
                if (isInputOrTextarea(this.elementRef.nativeElement)) {
112
                    if (this.ngControl && this.ngControl.control) {
113
                        this.ngControl.control.setValue(newValue);
114
                    }
115
                } else {
116
                    this.elementRef.nativeElement.innerText = newValue;
117
                }
118
                this.openedSuggestionsRef.close();
×
119
                this.select.emit(event);
×
120
            });
×
121
    }
122

×
123
    ngOnDestroy(): void {
124
        this.destroy$.next();
×
125
    }
×
126

127
    onClick(event: Event) {
128
        this.lookup(event);
129
    }
×
130

×
131
    onInput(event: Event) {
132
        this.lookup(event);
133
    }
1✔
134

135
    private lookup(event: Event) {
136
        const matched = this.adapter.lookup(event, this.mentions);
137
        if (matched) {
138
            this.openSuggestions(matched);
1✔
139
        } else {
140
            this.closeSuggestions();
141
        }
142
    }
143

144
    private openSuggestions(matched: MatchedMention) {
1✔
145
        if (!this.openedSuggestionsRef) {
146
            const inputElement = this.elementRef.nativeElement as HTMLInputElement;
147
            const position = CaretPositioner.getCaretPosition(inputElement, matched.query.start);
148
            const fontSize = parseInt(getComputedStyle(this.elementRef.nativeElement).fontSize, 10);
149
            this.openedSuggestionsRef = this.thyPopover.open(
150
                ThyMentionSuggestionsComponent,
151
                Object.assign({}, POPOVER_DEFAULT_CONFIG, this.popoverConfig, {
152
                    origin: this.elementRef,
153
                    originPosition: {
154
                        x: position.left,
155
                        y: position.top,
156
                        width: fontSize,
157
                        height: fontSize
158
                    },
159
                    initialState: {
160
                        mention: matched.mention
161
                    }
162
                })
163
            );
164
            this.openedSuggestionsRef.afterClosed().subscribe(() => {
165
                this.openedSuggestionsRef = null;
166
                this.openedSuggestionsRef$.next(null);
167
            });
168

169
            this.openedSuggestionsRef$.next(this.openedSuggestionsRef);
170
        }
171

172
        if (this.openedSuggestionsRef) {
173
            this.openedSuggestionsRef.componentInstance.search(matched.query);
174
        }
175
    }
176

177
    private closeSuggestions() {
178
        if (this.openedSuggestionsRef) {
179
            this.openedSuggestionsRef.close();
180
        }
181
    }
182
}
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