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

atinc / ngx-tethys / 15c1f1b4-802c-40d6-8dfc-2fedb288c650

21 May 2025 03:00AM UTC coverage: 90.244% (+0.009%) from 90.235%
15c1f1b4-802c-40d6-8dfc-2fedb288c650

push

circleci

web-flow
refactor(input): migrate to signal for input #TINFR-1478 (#3419)

5569 of 6840 branches covered (81.42%)

Branch coverage included in aggregate %.

45 of 46 new or added lines in 5 files covered. (97.83%)

8 existing lines in 3 files now uncovered.

13708 of 14521 relevant lines covered (94.4%)

899.46 hits per line

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

90.28
/src/input/input-search.component.ts
1
import {
2
    AbstractControlValueAccessor,
3
    Constructor,
4
    mixinDisabled,
5
    mixinInitialized,
6
    mixinTabIndex,
7
    ThyCanDisable,
8
    ThyHasTabIndex,
9
    ThyInitialized,
10
    useHostFocusControl
11
} from 'ngx-tethys/core';
12

1✔
13
import {
14
    ChangeDetectionStrategy,
16✔
15
    ChangeDetectorRef,
16
    Component,
17
    ElementRef,
1✔
18
    forwardRef,
1✔
19
    OnDestroy,
20
    OnInit,
21
    ViewEncapsulation,
22
    inject,
23
    input,
24
    effect,
1✔
25
    signal,
26
    output,
16✔
27
    viewChild
16✔
28
} from '@angular/core';
16✔
29
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
16✔
30
import { useHostRenderer } from '@tethys/cdk/dom';
16✔
31
import { ThyIcon } from 'ngx-tethys/icon';
16✔
32
import { ThyAutofocusDirective } from 'ngx-tethys/shared';
16✔
33
import { ThyInputDirective, ThyInputSize } from './input.directive';
16✔
34

16✔
35
import { FocusOrigin } from '@angular/cdk/a11y';
16✔
36
import { coerceBooleanProperty } from 'ngx-tethys/util';
16✔
37

16✔
38
export type ThyInputSearchTheme = 'default' | 'ellipse' | 'transparent' | '';
16✔
39
export type ThyInputSearchIconPosition = 'before' | 'after';
40

13✔
41
export const CUSTOM_INPUT_SEARCH_CONTROL_VALUE_ACCESSOR: any = {
42
    provide: NG_VALUE_ACCESSOR,
16✔
43
    useExisting: forwardRef(() => ThyInputSearch),
16✔
44
    multi: true
16✔
45
};
16✔
46

16✔
47
const noop = () => {};
48

16✔
49
const _MixinBase: Constructor<ThyHasTabIndex> &
16✔
50
    Constructor<ThyInitialized> &
51
    Constructor<ThyCanDisable> &
52
    typeof AbstractControlValueAccessor = mixinInitialized(mixinTabIndex(mixinDisabled(AbstractControlValueAccessor)));
53

16✔
54
/**
16✔
55
 * 搜索输入框
16✔
56
 * @name thy-input-search
16!
UNCOV
57
 * @order 30
×
58
 */
59
@Component({
16✔
60
    selector: 'thy-input-search',
14✔
61
    templateUrl: './input-search.component.html',
6✔
62
    providers: [CUSTOM_INPUT_SEARCH_CONTROL_VALUE_ACCESSOR],
63
    encapsulation: ViewEncapsulation.None,
64
    changeDetection: ChangeDetectionStrategy.OnPush,
65
    host: {
2!
66
        class: 'thy-input form-control thy-input-search',
2✔
67
        '[class.thy-input-search-ellipse]': 'thyTheme() === "ellipse"',
2✔
68
        '[class.thy-input-search-transparent]': 'thyTheme() === "transparent"',
69
        '[class.thy-input-search-before-with-clear]': 'searchText() && iconPosition() === "before"',
70
        '[class.form-control-active]': 'focused()',
71
        '[attr.tabindex]': 'tabIndex'
72
    },
16✔
73
    imports: [ThyIcon, ThyInputDirective, ThyAutofocusDirective, FormsModule]
32!
74
})
32✔
75
export class ThyInputSearch extends _MixinBase implements ControlValueAccessor, OnInit, OnDestroy {
76
    private elementRef = inject(ElementRef);
77

78
    readonly inputElement = viewChild<ElementRef<any>>('input');
34✔
79

80
    private hostRenderer = useHostRenderer();
81

17✔
82
    private hostFocusControl = useHostFocusControl();
83

84
    public disabled = signal(false);
1✔
85

86
    searchText = signal<string>('');
87

2✔
88
    focused = signal(false);
2✔
89

2✔
90
    /**
2!
UNCOV
91
     * 搜索框 name 属性
×
92
     */
93
    readonly name = input('');
2✔
94

2✔
95
    /**
2✔
96
     * 搜索框 Placeholder
2✔
97
     */
98
    readonly placeholder = input('');
99

16✔
100
    /**
101
     * 搜索框风格
1✔
102
     * @type 'default' | 'ellipse' | 'transparent'
1✔
103
     * @default default
104
     */
105
    readonly thyTheme = input<ThyInputSearchTheme>();
106

107
    /**
108
     * 是否自动聚焦
109
     * @default false
110
     */
111
    readonly autoFocus = input(false, { alias: 'thySearchFocus', transform: coerceBooleanProperty });
112

113
    /**
114
     * 搜索图标位置,当传入 after 时,搜索图标在输入框后方显示,有内容时显示为关闭按钮
1✔
115
     * @type ThyInputSearchIconPosition
116
     */
117
    readonly iconPosition = input('before', {
118
        alias: 'thyIconPosition',
119
        transform: (value: ThyInputSearchIconPosition) => value || 'before'
120
    });
121

122
    /**
123
     * 输入框大小
124
     * @type 'xs' | 'sm' | 'md' | 'default' | 'lg'
125
     */
126
    readonly thySize = input<ThyInputSize>();
127

128
    /**
129
     * @deprecated please use thyClear
130
     */
131
    readonly clear = output<Event>();
132

133
    /**
134
     * 清除搜索事件
135
     */
136
    readonly thyClear = output<Event>();
137

138
    constructor() {
139
        super();
140

141
        effect(() => {
142
            this.focused.set(this.autoFocus());
143
        });
144

145
        effect(() => {
146
            this.updateClasses();
147
        });
148
    }
149

150
    ngOnInit(): void {
151
        super.ngOnInit();
152
        this.updateClasses(true);
153

154
        this.hostFocusControl.focusChanged = (origin: FocusOrigin) => {
155
            if (this.disabled()) {
156
                return;
157
            }
158

159
            if (origin) {
160
                if (!this.focused()) {
161
                    this.inputElement().nativeElement.focus();
162
                }
163
            } else {
164
                if (this.focused()) {
165
                    this.focused.set(false);
166
                    this.onTouchedFn();
167
                }
168
            }
169
        };
170
    }
171

172
    updateClasses(forceUpdate = false) {
173
        if (this.initialized || forceUpdate) {
174
            this.hostRenderer.updateClass([`thy-input-search-${this.iconPosition()}`]);
175
        }
176
    }
177

178
    writeValue(value: any): void {
179
        this.searchText.set(value);
180
    }
181

182
    setDisabledState?(isDisabled: boolean): void {
183
        this.disabled.set(isDisabled);
184
    }
185

186
    searchModelChange() {
187
        this.onChangeFn(this.searchText());
188
    }
189

190
    clearSearchText(event: Event) {
191
        const element = this.elementRef.nativeElement.querySelector('.input-search-control');
192
        element.focus();
193
        event.stopPropagation();
194
        if (this.disabled()) {
195
            return;
196
        }
197
        this.searchText.set('');
198
        this.onChangeFn(this.searchText());
199
        this.clear.emit(event);
200
        this.thyClear.emit(event);
201
    }
202

203
    ngOnDestroy(): void {
204
        this.hostFocusControl.destroy();
205
    }
206
}
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

© 2026 Coveralls, Inc