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

atinc / ngx-tethys / 3cf91e0b-1240-4b8d-a45a-4404129e8cb9

11 Mar 2024 02:10AM UTC coverage: 90.575% (-0.03%) from 90.604%
3cf91e0b-1240-4b8d-a45a-4404129e8cb9

Pull #3022

circleci

web-flow
feat(date-picker): remove date-picker deprecated parameter #INFR-11790 @wumeimin @xuhaifeng (#INFR-11790) (#3046)
Pull Request #3022: feat: upgrade ng to 17 #INFR-11427 (#3021)

5414 of 6634 branches covered (81.61%)

Branch coverage included in aggregate %.

329 of 338 new or added lines in 192 files covered. (97.34%)

170 existing lines in 32 files now uncovered.

13489 of 14236 relevant lines covered (94.75%)

977.3 hits per line

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

97.3
/src/input-number/input-number.component.ts
1
import { InputBoolean, InputNumber, TabIndexDisabledControlValueAccessorMixin, useHostFocusControl } from 'ngx-tethys/core';
2
import { ThyMaxDirective, ThyMinDirective } from 'ngx-tethys/form';
3
import { ThyIcon } from 'ngx-tethys/icon';
4
import { ThyInputDirective } from 'ngx-tethys/input';
5
import { ThyAutofocusDirective } from 'ngx-tethys/shared';
6
import { DOWN_ARROW, ENTER, isFloat, isNumber, isUndefinedOrNull, UP_ARROW } from 'ngx-tethys/util';
7

8
import { FocusOrigin } from '@angular/cdk/a11y';
9
import {
10
    ChangeDetectorRef,
11
    Component,
12
    ElementRef,
1✔
13
    EventEmitter,
1✔
14
    forwardRef,
1✔
15
    Input,
2✔
16
    OnChanges,
17
    OnDestroy,
18
    OnInit,
19
    Output,
20
    SimpleChanges,
21
    ViewChild
1✔
22
} from '@angular/core';
23
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
106✔
24

106✔
25
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
4✔
26

4✔
27
enum Type {
28
    up,
29
    down
30
}
928✔
31

32
/**
33
 * 数字输入框
106✔
34
 * @name thy-input-number
106✔
35
 * @order 10
4✔
36
 */
4✔
37
@Component({
38
    selector: 'thy-input-number',
39
    templateUrl: './input-number.component.html',
40
    providers: [
930✔
41
        {
42
            provide: NG_VALUE_ACCESSOR,
43
            useExisting: forwardRef(() => ThyInputNumber),
102✔
44
            multi: true
102✔
45
        }
102✔
46
    ],
102✔
47
    standalone: true,
102✔
48
    imports: [ThyIcon, ThyInputDirective, ThyAutofocusDirective, FormsModule, ThyMinDirective, ThyMaxDirective],
102✔
49
    host: {
102✔
50
        class: 'thy-input-number',
102✔
51
        '[attr.tabindex]': 'tabIndex'
102✔
52
    }
102✔
53
})
102✔
54
export class ThyInputNumber
102✔
55
    extends TabIndexDisabledControlValueAccessorMixin
102✔
56
    implements ControlValueAccessor, OnChanges, OnInit, OnDestroy
57
{
58
    @ViewChild('input', { static: true }) inputElement: ElementRef<any>;
102✔
59

60
    private autoStepTimer: any;
61

102✔
62
    private hostFocusControl = useHostFocusControl();
36✔
63

4✔
64
    validValue: number | string;
65

32✔
66
    displayValue: number | string;
25✔
67

22✔
68
    disabledUp = false;
69

70
    disabledDown = false;
71

7✔
72
    activeValue: string = '';
6✔
73

6✔
74
    /**
6✔
75
     * 是否自动聚焦
6✔
76
     * @default false
77
     */
78
    @Input() @InputBoolean() thyAutoFocus: boolean;
79

80
    /**
81
     * 输入框的placeholder
129✔
82
     */
2✔
83
    @Input() thyPlaceholder: string = '';
2✔
84

2✔
85
    /**
86
     * 是否禁用
87
     * @default false
88
     */
241✔
89
    @Input() @InputBoolean() thyDisabled: boolean;
241✔
90

241✔
91
    /**
241✔
92
     * 最大值
93
     * @default Infinity
94
     */
268✔
95
    @Input() set thyMax(value: number) {
162✔
96
        this.innerMax = isNumber(value) ? value : this.innerMax;
97
        if (this.displayValue || this.displayValue === 0) {
106✔
98
            const val = Number(this.displayValue);
105✔
99
            this.disabledUp = val >= this.innerMax;
100
        }
268✔
101
    }
268✔
102

106✔
103
    get thyMax() {
106✔
104
        return this.innerMax;
19✔
105
    }
106

106✔
107
    /**
18✔
108
     * 最小值
109
     * @default -Infinity
110
     */
111
    @Input() set thyMin(value: number) {
112
        this.innerMin = isNumber(value) ? value : this.innerMin;
20✔
113
        if (this.displayValue || this.displayValue === 0) {
20✔
114
            const val = Number(this.displayValue);
18✔
115
            this.disabledDown = val <= this.innerMin;
116
        }
117
    }
2✔
118

2✔
119
    get thyMin() {
120
        return this.innerMin;
20✔
121
    }
20✔
122

14✔
123
    /**
14✔
124
     * 每次改变步数,可以为小数
125
     */
126
    @Input() @InputNumber() thyStep = 1;
127

23✔
128
    /**
23✔
129
     * 改变步数时的延迟毫秒数,值越小变化的速度越快
15✔
130
     * @default 300
15✔
131
     */
132
    @Input() @InputNumber() thyStepDelay = 300;
133

134
    /**
7✔
135
     * 输入框大小
1✔
136
     * @type xs | sm | md | lg
1✔
137
     */
138
    @Input() thySize: InputSize;
6✔
139

1✔
140
    /**
1✔
141
     * 数值精度
142
     */
5!
143
    @Input() thyPrecision: number;
5✔
144

145
    /**
146
     * 数值后缀
147
     */
25✔
148
    @Input() thySuffix: string;
19✔
149

150
    /**
25✔
151
     * 焦点失去事件
152
     */
153
    @Output() thyBlur = new EventEmitter<Event>();
12✔
154

12✔
155
    /**
12✔
156
     * 焦点激活事件
1✔
157
     */
158
    @Output() thyFocus = new EventEmitter<Event>();
11✔
159

160
    private innerMax: number = Infinity;
11✔
161

8✔
162
    private innerMin: number = -Infinity;
163

3!
164
    private isFocused: boolean;
3✔
165

166
    constructor(private cdr: ChangeDetectorRef) {
11✔
167
        super();
11✔
168
    }
11✔
169

11✔
170
    setDisabledState?(isDisabled: boolean): void {
11✔
171
        this.thyDisabled = isDisabled;
11!
UNCOV
172
    }
×
173

174
    ngOnInit() {
11✔
UNCOV
175
        this.hostFocusControl.focusChanged = (origin: FocusOrigin) => {
×
176
            if (this.thyDisabled) {
177
                return;
178
            }
179

8✔
180
            if (origin) {
8✔
181
                if (!this.isFocused) {
8✔
182
                    this.inputElement.nativeElement.focus();
8✔
183
                }
184
            } else {
185
                if (this.isFocused) {
3✔
186
                    this.displayValue = this.formatterValue(this.validValue);
3✔
187
                    this.onTouchedFn();
3✔
188
                    this.thyBlur.emit();
3✔
189
                    this.isFocused = false;
190
                }
191
            }
22✔
192
        };
18✔
193
    }
194

4✔
195
    ngOnChanges(changes: SimpleChanges) {
4✔
196
        if (changes.thySuffix && !changes.thySuffix.isFirstChange()) {
4!
UNCOV
197
            const validValue = this.getCurrentValidValue(this.validValue);
×
198
            this.updateValidValue(validValue);
199
            this.displayValue = this.formatterValue(validValue);
4✔
200
        }
201
    }
202

11✔
203
    writeValue(value: number | string): void {
11✔
204
        const _value = this.getCurrentValidValue(value);
205
        this.updateValidValue(_value);
206
        this.displayValue = this.formatterValue(_value);
8✔
207
        this.cdr.markForCheck();
208
    }
8✔
209

2✔
210
    updateValidValue(value: number | string): void {
211
        if (this.isNotValid(value)) {
6✔
212
            this.validValue = '';
213
        } else if (this.validValue !== value) {
6✔
214
            this.validValue = value;
2✔
215
        }
216
        this.disabledUp = this.disabledDown = false;
6✔
217
        if (value || value === 0) {
218
            const val = Number(value);
219
            if (val >= this.thyMax) {
9✔
220
                this.disabledUp = true;
9✔
221
            }
222
            if (val <= this.thyMin) {
223
                this.disabledDown = true;
3✔
224
            }
3✔
225
        }
226
    }
227

265✔
228
    onModelChange(value: string): void {
265✔
229
        const parseValue = this.parser(value);
164✔
230
        if (this.isInputNumber(value)) {
231
            this.activeValue = value;
232
        } else {
101✔
233
            this.displayValue = parseValue;
234
            this.inputElement.nativeElement.value = parseValue;
235
        }
236
        const validValue = this.getCurrentValidValue(parseValue);
307✔
237
        if (`${this.validValue}` !== `${validValue}`) {
238
            this.updateValidValue(validValue);
239
            this.onChangeFn(validValue);
240
        }
241
    }
242

243
    onInputFocus(event?: Event) {
272✔
244
        this.activeValue = this.parser(this.displayValue.toString());
272✔
245
        if (!this.isFocused) {
60✔
246
            this.isFocused = true;
247
            this.thyFocus.emit(event);
212✔
248
        }
212✔
249
    }
104✔
250

251
    onKeyDown(e: KeyboardEvent): void {
212✔
252
        if (e.keyCode === UP_ARROW) {
5✔
253
            this.up(e);
254
            this.stop();
212✔
255
        } else if (e.keyCode === DOWN_ARROW) {
3✔
256
            this.down(e);
257
            this.stop();
212✔
258
        } else if (e.keyCode === ENTER) {
259
            this.displayValue = this.formatterValue(this.validValue);
260
        }
728✔
261
    }
262

263
    stop() {
248✔
264
        if (this.autoStepTimer) {
107✔
265
            clearTimeout(this.autoStepTimer);
266
        }
141✔
267
        this.displayValue = this.toNumber(this.displayValue);
141✔
268
    }
15✔
269

270
    step(type: Type, e: MouseEvent | KeyboardEvent): void {
126✔
271
        this.stop();
272
        e.preventDefault();
273
        if (this.thyDisabled) {
41✔
274
            return;
275
        }
276
        const value = this.validValue as number;
102✔
277
        let val;
278
        if (type === Type.up) {
1✔
279
            val = this.upStep(value);
280
        } else if (type === Type.down) {
281
            val = this.downStep(value);
1✔
282
        }
283
        const outOfRange = val > this.thyMax || val < this.thyMin;
284
        val = this.getCurrentValidValue(val);
285
        this.updateValidValue(val);
286
        this.onChangeFn(this.validValue);
287
        this.displayValue = this.formatterValue(val);
288
        if (outOfRange) {
289
            return;
290
        }
291
        this.autoStepTimer = setTimeout(() => {
292
            (this[Type[type]] as (e: MouseEvent | KeyboardEvent) => void)(e);
293
        }, this.thyStepDelay);
294
    }
295

296
    upStep(value: number): number {
297
        const precisionFactor = this.getPrecisionFactor(value);
1✔
298
        const precision = this.getMaxPrecision(value);
299
        const result = ((precisionFactor * value + precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
300
        return this.toNumber(result);
301
    }
1✔
302

303
    downStep(value: number): number {
304
        const precisionFactor = this.getPrecisionFactor(value);
305
        const precision = Math.abs(this.getMaxPrecision(value));
1✔
306
        const result = ((precisionFactor * value - precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
307
        return this.toNumber(result);
308
    }
309

1✔
310
    getMaxPrecision(value: string | number): number {
311
        if (!isUndefinedOrNull(this.thyPrecision)) {
312
            return this.thyPrecision;
313
        }
1✔
314
        const stepPrecision = this.getPrecision(this.thyStep);
315
        const currentValuePrecision = this.getPrecision(value as number);
316
        if (!value) {
317
            return stepPrecision;
318
        }
319
        return Math.max(currentValuePrecision, stepPrecision);
320
    }
102✔
321

322
    getPrecisionFactor(activeValue: string | number): number {
323
        const precision = this.getMaxPrecision(activeValue);
324
        return Math.pow(10, precision);
325
    }
326

327
    getPrecision(value: number): number {
328
        const valueString = value.toString();
329
        // 0.0000000004.toString() = 4e10  => 10
330
        if (valueString.indexOf('e-') >= 0) {
331
            return parseInt(valueString.slice(valueString.indexOf('e-') + 2), 10);
332
        }
333
        let precision = 0;
334
        // 1.2222 =>  4
335
        if (valueString.indexOf('.') >= 0) {
336
            precision = valueString.length - valueString.indexOf('.') - 1;
337
        }
338
        return precision;
339
    }
340

341
    up(e: MouseEvent | KeyboardEvent) {
342
        this.inputElement.nativeElement.focus();
343
        this.step(Type.up, e);
344
    }
345

346
    down(e: MouseEvent | KeyboardEvent) {
347
        this.inputElement.nativeElement.focus();
348
        this.step(Type.down, e);
349
    }
350

351
    formatterValue(value: number | string) {
352
        const parseValue = this.parser(`${value}`);
353
        if (parseValue) {
354
            return this.thySuffix ? `${parseValue} ${this.thySuffix}` : parseValue;
355
        } else {
356
            return '';
357
        }
358
    }
359

360
    parser(value: string) {
361
        return value
362
            .trim()
363
            .replace(/。/g, '.')
364
            .replace(/[^\w\.-]+/g, '')
365
            .replace(this.thySuffix, '');
366
    }
367

368
    getCurrentValidValue(value: string | number): number | string {
369
        let val = value;
370
        if (value === '' || value === undefined) {
371
            return '';
372
        }
373
        val = parseFloat(value as string);
374
        if (this.isNotValid(val)) {
375
            val = this.validValue;
376
        }
377
        if ((val as number) < this.thyMin) {
378
            val = this.thyMin;
379
        }
380
        if ((val as number) > this.thyMax) {
381
            val = this.thyMax;
382
        }
383

384
        return this.toNumber(val);
385
    }
386

387
    isNotValid(num: string | number): boolean {
388
        return isNaN(num as number) || num === '' || num === null || !!(num && num.toString().indexOf('.') === num.toString().length - 1);
389
    }
390

391
    toNumber(num: string | number): number {
392
        if (this.isNotValid(num)) {
393
            return num as number;
394
        }
395
        const numStr = String(num);
396
        if (numStr.indexOf('.') >= 0 && !isUndefinedOrNull(this.thyPrecision)) {
397
            return Number(Number(num).toFixed(this.thyPrecision));
398
        }
399
        return Number(num);
400
    }
401

402
    isInputNumber(value: string) {
403
        return isFloat(value) || /^[-+Ee]$|^([-+.]?[0-9])*(([.]|[.eE])?[eE]?[+-]?)?$|^$/.test(value);
404
    }
405

406
    ngOnDestroy() {
407
        this.hostFocusControl.destroy();
408
    }
409
}
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