• 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

5.05
/src/input/input-group.component.ts
1
import {
2
    Component,
3
    TemplateRef,
4
    ViewEncapsulation,
5
    ChangeDetectionStrategy,
6
    OnInit,
7
    OnDestroy,
8
    NgZone,
9
    inject,
10
    DestroyRef,
11
    input,
12
    computed,
13
    effect,
1✔
14
    contentChild,
15
    signal
16
} from '@angular/core';
17
import { ThyTranslate, useHostFocusControl } from 'ngx-tethys/core';
18
import { useHostRenderer } from '@tethys/cdk/dom';
19
import { ThyInputDirective } from './input.directive';
20
import { NgTemplateOutlet } from '@angular/common';
21
import { throttleTime } from 'rxjs/operators';
22
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
import { Observable, of } from 'rxjs';
1✔
24
import { FocusOrigin } from '@angular/cdk/a11y';
UNCOV
25
import { MutationObserverFactory } from '@angular/cdk/observers';
×
UNCOV
26

×
UNCOV
27
export type InputGroupSize = 'sm' | 'lg' | 'md' | '';
×
UNCOV
28

×
UNCOV
29
const inputGroupSizeMap = {
×
UNCOV
30
    sm: ['input-group-sm'],
×
UNCOV
31
    lg: ['input-group-lg'],
×
UNCOV
32
    md: ['input-group-md']
×
UNCOV
33
};
×
UNCOV
34

×
UNCOV
35
/**
×
UNCOV
36
 * 输入框分组
×
UNCOV
37
 * @name thy-input-group
×
UNCOV
38
 * @order 20
×
UNCOV
39
 */
×
UNCOV
40
@Component({
×
41
    selector: 'thy-input-group',
UNCOV
42
    templateUrl: './input-group.component.html',
×
43
    changeDetection: ChangeDetectionStrategy.OnPush,
UNCOV
44
    encapsulation: ViewEncapsulation.None,
×
UNCOV
45
    host: {
×
UNCOV
46
        class: 'thy-input-group',
×
UNCOV
47
        '[class.form-control]': 'prefixTemplate() || suffixTemplate()',
×
48
        '[class.thy-input-group-with-prefix]': 'prefixTemplate()',
UNCOV
49
        '[class.thy-input-group-with-suffix]': 'suffixTemplate()',
×
50
        '[class.thy-input-group-with-textarea-suffix]': 'isTextareaSuffix()',
UNCOV
51
        '[class.thy-input-group-with-scroll-bar]': 'isTextareaSuffix() && hasScrollbar()',
×
UNCOV
52
        '[class.disabled]': 'disabled()'
×
UNCOV
53
    },
×
UNCOV
54
    imports: [NgTemplateOutlet]
×
UNCOV
55
})
×
UNCOV
56
export class ThyInputGroup implements OnInit, OnDestroy {
×
UNCOV
57
    private thyTranslate = inject(ThyTranslate);
×
UNCOV
58
    private ngZone = inject(NgZone);
×
UNCOV
59

×
UNCOV
60
    private hostRenderer = useHostRenderer();
×
61

62
    private hostFocusControl = useHostFocusControl();
UNCOV
63

×
64
    private readonly destroyRef = inject(DestroyRef);
65

UNCOV
66
    public isTextareaSuffix = signal(false);
×
UNCOV
67

×
UNCOV
68
    public hasScrollbar = signal(false);
×
UNCOV
69

×
UNCOV
70
    disabled = signal(false);
×
UNCOV
71

×
72
    /**
73
     * 输入框上添加的后置文本
74
     */
UNCOV
75
    readonly thyAppendText = input<string>();
×
UNCOV
76

×
UNCOV
77
    /**
×
UNCOV
78
     * 输入框上添加的后置文本多语言 Key
×
UNCOV
79
     */
×
UNCOV
80
    readonly thyAppendTextTranslateKey = input<string>();
×
UNCOV
81

×
UNCOV
82
    /**
×
83
     * 输入框上添加的前置文本
84
     */
85
    readonly thyPrependText = input<string>();
UNCOV
86

×
UNCOV
87
    /**
×
88
     * 输入框上添加的前置文本多语言 Key
89
     */
90
    readonly thyPrependTextTranslateKey = input<string>();
91

92
    protected readonly prependText = computed(() => {
93
        const prependTextTranslateKey = this.thyPrependTextTranslateKey();
94
        if (prependTextTranslateKey) {
95
            return this.thyTranslate.instant(prependTextTranslateKey);
UNCOV
96
        }
×
UNCOV
97
        return this.thyPrependText();
×
UNCOV
98
    });
×
99

100
    protected readonly appendText = computed(() => {
UNCOV
101
        const appendTextTranslateKey = this.thyAppendTextTranslateKey();
×
102
        if (appendTextTranslateKey) {
103
            return this.thyTranslate.instant(appendTextTranslateKey);
104
        }
105
        return this.thyAppendText();
UNCOV
106
    });
×
UNCOV
107

×
108
    /**
109
     * 输入框分组大小
110
     * @type 'sm' | 'lg' | 'md' | ''
×
111
     * @default ''
×
112
     */
×
113
    readonly thySize = input<InputGroupSize>();
×
114

115
    /**
116
     * 后置模板
117
     */
118
    readonly appendTemplate = contentChild<TemplateRef<unknown>>('append');
119

UNCOV
120
    /**
×
121
     * 前置模板
122
     */
UNCOV
123
    readonly prependTemplate = contentChild<TemplateRef<unknown>>('prepend');
×
124

×
125
    /**
UNCOV
126
     * 前缀
×
UNCOV
127
     */
×
UNCOV
128
    readonly prefixTemplate = contentChild<TemplateRef<unknown>>('prefix');
×
129

130
    /**
131
     * 后缀
132
     */
UNCOV
133
    readonly suffixTemplate = contentChild<TemplateRef<unknown>>('suffix');
×
134

135
    /**
1✔
136
     * @private
1✔
137
     */
138
    readonly inputDirective = contentChild(ThyInputDirective);
139

140
    private disabledObservable: MutationObserver;
141

142
    constructor() {
143
        effect(() => {
144
            const size = this.thySize();
145
            if (size && inputGroupSizeMap[size]) {
146
                this.hostRenderer.updateClass(inputGroupSizeMap[size]);
147
            } else {
148
                this.hostRenderer.updateClass([]);
149
            }
1✔
150
        });
151

152
        effect(() => {
153
            const inputDirective = this.inputDirective();
154
            if (inputDirective?.nativeElement) {
155
                this.isTextareaSuffix.set(inputDirective?.nativeElement?.tagName === 'TEXTAREA');
156
                if (this.isTextareaSuffix()) {
157
                    this.determineHasScrollbar();
158
                }
159
            }
160
        });
161

162
        effect(() => {
163
            const inputDirective = this.inputDirective();
164
            this.disabledObservable?.disconnect();
165
            if (inputDirective?.nativeElement) {
166
                this.disabledObservable = new MutationObserverFactory().create(mutations => {
167
                    for (const mutation of mutations) {
168
                        if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') {
169
                            this.disabled.set(!!inputDirective.nativeElement.hasAttribute('disabled'));
170
                        }
171
                    }
172
                });
173
                if (this.disabledObservable) {
174
                    this.disabledObservable.observe(inputDirective.nativeElement, {
175
                        attributes: true,
176
                        attributeFilter: ['disabled']
177
                    });
178
                }
179
            }
180
        });
181
    }
182

183
    ngOnInit() {
184
        this.hostFocusControl.focusChanged = (origin: FocusOrigin) => {
185
            if (origin) {
186
                this.hostRenderer.addClass('form-control-active');
187
            } else {
188
                this.hostRenderer.removeClass('form-control-active');
189
            }
190
        };
191
    }
192

193
    private determineHasScrollbar() {
194
        this.ngZone.runOutsideAngular(() => {
195
            this.resizeObserver(this.inputDirective().nativeElement)
196
                .pipe(throttleTime(100), takeUntilDestroyed(this.destroyRef))
197
                .subscribe(() => {
198
                    const hasScrollbar =
199
                        this.inputDirective().nativeElement.scrollHeight > this.inputDirective().nativeElement.clientHeight;
200
                    if (this.hasScrollbar() !== hasScrollbar) {
201
                        this.ngZone.run(() => {
202
                            this.hasScrollbar.set(hasScrollbar);
203
                        });
204
                    }
205
                });
206
        });
207
    }
208

209
    private resizeObserver(element: HTMLElement): Observable<ResizeObserverEntry[]> {
210
        return typeof ResizeObserver === 'undefined' || !ResizeObserver
211
            ? of(null)
212
            : new Observable(observer => {
213
                  const resize = new ResizeObserver((entries: ResizeObserverEntry[]) => {
214
                      observer.next(entries);
215
                  });
216
                  resize.observe(element);
217
                  return () => {
218
                      resize.disconnect();
219
                  };
220
              });
221
    }
222

223
    ngOnDestroy() {
224
        this.hostFocusControl.destroy();
225
    }
226
}
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