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

atinc / ngx-tethys / #102

26 May 2026 08:11AM UTC coverage: 91.111% (+0.7%) from 90.407%
#102

push

web-flow
build: bump docgeni to 2.8.0-next.5 (#3809)

4571 of 5491 branches covered (83.25%)

Branch coverage included in aggregate %.

13141 of 13949 relevant lines covered (94.21%)

966.75 hits per line

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

93.1
/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

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

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

37
export type ThyInputSearchTheme = 'default' | 'ellipse' | 'transparent' | '';
38
export type ThyInputSearchIconPosition = 'before' | 'after';
39

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

46
const noop = () => {};
1✔
47

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

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

77
    readonly inputElement = viewChild.required<ElementRef<any>>('input');
16✔
78

79
    private hostRenderer = useHostRenderer();
16✔
80

81
    private hostFocusControl = useHostFocusControl();
16✔
82

83
    public disabled = signal(false);
16✔
84

85
    searchText = signal<string>('');
16✔
86

87
    focused = signal(false);
16✔
88

89
    /**
90
     * 搜索框 name 属性
91
     */
92
    readonly name = input('');
16✔
93

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

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

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

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

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

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

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

137
    constructor() {
138
        super();
16✔
139

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

144
        effect(() => {
16✔
145
            const iconPosition = this.iconPosition();
16✔
146
            this.hostRenderer.updateClass([`thy-input-search-${iconPosition}`]);
16✔
147
        });
148
    }
149

150
    ngOnInit(): void {
151
        super.ngOnInit();
16✔
152

153
        this.hostFocusControl.focusChanged = (origin: FocusOrigin) => {
16✔
154
            if (this.disabled()) {
6!
155
                return;
×
156
            }
157

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

171
    writeValue(value: any): void {
172
        this.searchText.set(value);
34✔
173
    }
174

175
    setDisabledState?(isDisabled: boolean): void {
176
        this.disabled.set(isDisabled);
17✔
177
    }
178

179
    searchModelChange() {
180
        this.onChangeFn(this.searchText());
1✔
181
    }
182

183
    clearSearchText(event: Event) {
184
        const element = this.elementRef.nativeElement.querySelector('.input-search-control');
2✔
185
        element.focus();
2✔
186
        event.stopPropagation();
2✔
187
        if (this.disabled()) {
2!
188
            return;
×
189
        }
190
        this.searchText.set('');
2✔
191
        this.onChangeFn(this.searchText());
2✔
192
        this.clear.emit(event);
2✔
193
        this.thyClear.emit(event);
2✔
194
    }
195

196
    ngOnDestroy(): void {
197
        this.hostFocusControl.destroy();
16✔
198
    }
199
}
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