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

atinc / ngx-tethys / 49bf6d4a-7ffc-4708-ae72-443407594841

22 Aug 2023 10:32AM UTC coverage: 90.071% (-0.09%) from 90.157%
49bf6d4a-7ffc-4708-ae72-443407594841

Pull #2771

circleci

Guoxiin
fix(schematics): update action-menu crossing type replace
Pull Request #2771: feat: bump angular to 16.x

5091 of 6307 branches covered (80.72%)

Branch coverage included in aggregate %.

47 of 47 new or added lines in 8 files covered. (100.0%)

12852 of 13614 relevant lines covered (94.4%)

977.77 hits per line

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

97.29
/src/input-number/input-number.component.ts
1
import {
2
    AbstractControlValueAccessor,
3
    Constructor,
4
    InputBoolean,
5
    InputNumber,
6
    mixinDisabled,
7
    mixinTabIndex,
8
    ThyCanDisable,
9
    ThyHasTabIndex,
10
    useHostFocusControl
11
} from 'ngx-tethys/core';
12
import { ThyMaxDirective, ThyMinDirective } from 'ngx-tethys/form';
13
import { ThyIconComponent } from 'ngx-tethys/icon';
1✔
14
import { ThyInputComponent, ThyInputDirective } from 'ngx-tethys/input';
1✔
15
import { ThyAutofocusDirective } from 'ngx-tethys/shared';
1✔
16
import { DOWN_ARROW, ENTER, isNumber, isUndefinedOrNull, UP_ARROW, isFloat } from 'ngx-tethys/util';
2✔
17

1✔
18
import { FocusOrigin } from '@angular/cdk/a11y';
19
import {
20
    ChangeDetectorRef,
21
    Component,
22
    ElementRef,
23
    EventEmitter,
1✔
24
    forwardRef,
25
    Input,
94✔
26
    OnChanges,
94✔
27
    OnDestroy,
4✔
28
    OnInit,
4✔
29
    Output,
30
    SimpleChanges,
31
    ViewChild
32
} from '@angular/core';
857✔
33
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
34

35
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
94✔
36

94✔
37
enum Type {
4✔
38
    up,
4✔
39
    down
40
}
41

42
const _MixinBase: Constructor<ThyHasTabIndex> & Constructor<ThyCanDisable> & typeof AbstractControlValueAccessor = mixinTabIndex(
857✔
43
    mixinDisabled(AbstractControlValueAccessor)
44
);
45

90✔
46
/**
90✔
47
 * 数字输入框
90✔
48
 * @name thy-input-number
90✔
49
 * @order 10
90✔
50
 */
90✔
51
@Component({
90✔
52
    selector: 'thy-input-number',
90✔
53
    templateUrl: './input-number.component.html',
90✔
54
    providers: [
90✔
55
        {
90✔
56
            provide: NG_VALUE_ACCESSOR,
90✔
57
            useExisting: forwardRef(() => ThyInputNumberComponent),
58
            multi: true
59
        }
90✔
60
    ],
61
    standalone: true,
62
    imports: [ThyIconComponent, ThyInputDirective, ThyAutofocusDirective, FormsModule, ThyMinDirective, ThyMaxDirective],
90✔
63
    host: {
33✔
64
        class: 'thy-input-number',
1✔
65
        '[attr.tabindex]': 'tabIndex'
66
    }
32✔
67
})
25✔
68
export class ThyInputNumberComponent extends _MixinBase implements ControlValueAccessor, OnChanges, OnInit, OnDestroy {
22✔
69
    @ViewChild('input', { static: true }) inputElement: ElementRef<any>;
70

71
    private autoStepTimer: any;
72

7✔
73
    private hostFocusControl = useHostFocusControl();
6✔
74

6✔
75
    validValue: number | string;
6✔
76

6✔
77
    displayValue: number | string;
78

79
    disabledUp = false;
80

81
    disabledDown = false;
82

117✔
83
    activeValue: string = '';
2✔
84

2✔
85
    /**
2✔
86
     * 是否自动聚焦
87
     * @default false
88
     */
89
    @Input() @InputBoolean() thyAutoFocus: boolean;
214✔
90

214✔
91
    /**
214✔
92
     * 输入框的placeholder
214✔
93
     */
94
    @Input() thyPlaceholder: string = '';
95

237✔
96
    /**
137✔
97
     * 是否禁用
98
     * @default false
100✔
99
     */
99✔
100
    @Input() @InputBoolean() thyDisabled: boolean;
101

237✔
102
    /**
237✔
103
     * 最大值
100✔
104
     * @default Infinity
100✔
105
     */
17✔
106
    @Input() set thyMax(value: number) {
107
        this.innerMax = isNumber(value) ? value : this.innerMax;
100✔
108
        if (this.displayValue || this.displayValue === 0) {
16✔
109
            const val = Number(this.displayValue);
110
            this.disabledUp = val >= this.innerMax;
111
        }
112
    }
113

12✔
114
    get thyMax() {
12✔
115
        return this.innerMax;
12✔
116
    }
10✔
117

10✔
118
    /**
119
     * 最小值
120
     * @default -Infinity
121
     */
4✔
122
    @Input() set thyMin(value: number) {
4✔
123
        this.innerMin = isNumber(value) ? value : this.innerMin;
3✔
124
        if (this.displayValue || this.displayValue === 0) {
125
            const val = Number(this.displayValue);
126
            this.disabledDown = val <= this.innerMin;
1✔
127
        }
1✔
128
    }
129

130
    get thyMin() {
131
        return this.innerMin;
22✔
132
    }
22✔
133

14✔
134
    /**
14✔
135
     * 每次改变步数,可以为小数
136
     */
137
    @Input() @InputNumber() thyStep = 1;
138

3✔
139
    /**
1✔
140
     * 输入框大小
1✔
141
     * @type xs | sm | md | lg
142
     */
2✔
143
    @Input() thySize: InputSize;
1✔
144

1✔
145
    /**
146
     * 数值精度
1!
147
     */
1✔
148
    @Input() thyPrecision: number;
149

150
    /**
151
     * 数值后缀
24✔
152
     */
19✔
153
    @Input() thySuffix: string;
154

155
    /**
156
     * 焦点失去事件
12✔
157
     */
12✔
158
    @Output() thyBlur = new EventEmitter<Event>();
12✔
159

1✔
160
    /**
161
     * 焦点激活事件
11✔
162
     */
163
    @Output() thyFocus = new EventEmitter<Event>();
11✔
164

8✔
165
    private innerMax: number = Infinity;
166

3!
167
    private innerMin: number = -Infinity;
3✔
168

169
    private isFocused: boolean;
11✔
170

11✔
171
    constructor(private cdr: ChangeDetectorRef) {
11✔
172
        super();
11✔
173
    }
11✔
174

11!
175
    setDisabledState?(isDisabled: boolean): void {
×
176
        this.thyDisabled = isDisabled;
177
    }
11✔
178

×
179
    ngOnInit() {
180
        this.hostFocusControl.focusChanged = (origin: FocusOrigin) => {
181
            if (this.thyDisabled) {
182
                return;
8✔
183
            }
8✔
184

8✔
185
            if (origin) {
8✔
186
                if (!this.isFocused) {
187
                    this.inputElement.nativeElement.focus();
188
                }
3✔
189
            } else {
3✔
190
                if (this.isFocused) {
3✔
191
                    this.displayValue = this.formatterValue(this.validValue);
3✔
192
                    this.onTouchedFn();
193
                    this.thyBlur.emit();
194
                    this.isFocused = false;
22✔
195
                }
18✔
196
            }
197
        };
4✔
198
    }
4✔
199

4!
200
    ngOnChanges(changes: SimpleChanges) {
×
201
        if (changes.thySuffix && !changes.thySuffix.isFirstChange()) {
202
            const validValue = this.getCurrentValidValue(this.validValue);
4✔
203
            this.updateValidValue(validValue);
204
            this.displayValue = this.formatterValue(validValue);
205
        }
11✔
206
    }
11✔
207

208
    writeValue(value: number | string): void {
209
        const _value = this.getCurrentValidValue(value);
8✔
210
        this.updateValidValue(_value);
211
        this.displayValue = this.formatterValue(_value);
8✔
212
        this.cdr.markForCheck();
2✔
213
    }
214

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

146✔
233
    onModelChange(value: string): void {
234
        const parseValue = this.parser(value);
235
        const validValue = this.getCurrentValidValue(parseValue);
88✔
236
        if (this.validValue !== validValue) {
237
            this.updateValidValue(validValue);
238
            this.onChangeFn(this.validValue);
239
        }
268✔
240
    }
241

242
    onInput(input?: ThyInputComponent) {
243
        const value = input.value;
244
        if (this.isInputNumber(value)) {
245
            this.activeValue = value;
246
        } else {
239✔
247
            this.displayValue = this.activeValue;
239✔
248
            input.value = this.displayValue;
47✔
249
        }
250
    }
192✔
251

192✔
252
    onInputFocus(event?: Event) {
91✔
253
        this.activeValue = this.parser(this.displayValue.toString());
254
        if (!this.isFocused) {
192✔
255
            this.isFocused = true;
2✔
256
            this.thyFocus.emit(event);
257
        }
192✔
258
    }
2✔
259

260
    onKeyDown(e: KeyboardEvent): void {
192✔
261
        if (e.keyCode === UP_ARROW) {
262
            this.up(e);
263
            this.stop();
632✔
264
        } else if (e.keyCode === DOWN_ARROW) {
265
            this.down(e);
266
            this.stop();
203✔
267
        } else if (e.keyCode === ENTER) {
91✔
268
            this.displayValue = this.formatterValue(this.validValue);
269
        }
112✔
270
    }
112✔
271

12✔
272
    stop() {
273
        if (this.autoStepTimer) {
100✔
274
            clearTimeout(this.autoStepTimer);
275
        }
276
    }
25✔
277

278
    step(type: Type, e: MouseEvent | KeyboardEvent): void {
279
        this.stop();
90✔
280
        e.preventDefault();
281
        if (this.thyDisabled) {
1✔
282
            return;
283
        }
284
        const value = this.validValue as number;
1✔
285
        let val;
286
        if (type === Type.up) {
287
            val = this.upStep(value);
288
        } else if (type === Type.down) {
289
            val = this.downStep(value);
290
        }
291
        const outOfRange = val > this.thyMax || val < this.thyMin;
292
        val = this.getCurrentValidValue(val);
293
        this.updateValidValue(val);
294
        this.onChangeFn(this.validValue);
295
        this.displayValue = this.formatterValue(val);
296
        if (outOfRange) {
297
            return;
298
        }
299
        this.autoStepTimer = setTimeout(() => {
1✔
300
            (this[Type[type]] as (e: MouseEvent | KeyboardEvent) => void)(e);
301
        }, 300);
302
    }
303

1✔
304
    upStep(value: number): number {
305
        const precisionFactor = this.getPrecisionFactor(value);
306
        const precision = this.getMaxPrecision(value);
307
        const result = ((precisionFactor * value + precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
1✔
308
        return this.toNumber(result);
309
    }
310

311
    downStep(value: number): number {
1✔
312
        const precisionFactor = this.getPrecisionFactor(value);
313
        const precision = Math.abs(this.getMaxPrecision(value));
314
        const result = ((precisionFactor * value - precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
315
        return this.toNumber(result);
316
    }
317

318
    getMaxPrecision(value: string | number): number {
90✔
319
        if (!isUndefinedOrNull(this.thyPrecision)) {
320
            return this.thyPrecision;
321
        }
322
        const stepPrecision = this.getPrecision(this.thyStep);
323
        const currentValuePrecision = this.getPrecision(value as number);
324
        if (!value) {
325
            return stepPrecision;
326
        }
327
        return Math.max(currentValuePrecision, stepPrecision);
328
    }
329

330
    getPrecisionFactor(activeValue: string | number): number {
331
        const precision = this.getMaxPrecision(activeValue);
332
        return Math.pow(10, precision);
333
    }
334

335
    getPrecision(value: number): number {
336
        const valueString = value.toString();
337
        // 0.0000000004.toString() = 4e10  => 10
338
        if (valueString.indexOf('e-') >= 0) {
339
            return parseInt(valueString.slice(valueString.indexOf('e-') + 2), 10);
340
        }
341
        let precision = 0;
342
        // 1.2222 =>  4
343
        if (valueString.indexOf('.') >= 0) {
344
            precision = valueString.length - valueString.indexOf('.') - 1;
345
        }
346
        return precision;
347
    }
348

349
    up(e: MouseEvent | KeyboardEvent) {
350
        this.inputElement.nativeElement.focus();
351
        this.step(Type.up, e);
352
    }
353

354
    down(e: MouseEvent | KeyboardEvent) {
355
        this.inputElement.nativeElement.focus();
356
        this.step(Type.down, e);
357
    }
358

359
    formatterValue(value: number | string) {
360
        const parseValue = this.parser(`${value}`);
361
        if (parseValue) {
362
            return this.thySuffix ? `${parseValue} ${this.thySuffix}` : parseValue;
363
        } else {
364
            return '';
365
        }
366
    }
367

368
    parser(value: string) {
369
        return value
370
            .trim()
371
            .replace(/。/g, '.')
372
            .replace(/[^\w\.-]+/g, '')
373
            .replace(this.thySuffix, '');
374
    }
375

376
    getCurrentValidValue(value: string | number): number | string {
377
        let val = value;
378
        if (value === '' || value === undefined) {
379
            return '';
380
        }
381
        val = parseFloat(value as string);
382
        if (this.isNotValid(val)) {
383
            val = this.validValue;
384
        }
385
        if ((val as number) < this.thyMin) {
386
            val = this.thyMin;
387
        }
388
        if ((val as number) > this.thyMax) {
389
            val = this.thyMax;
390
        }
391

392
        return this.toNumber(val);
393
    }
394

395
    isNotValid(num: string | number): boolean {
396
        return isNaN(num as number) || num === '' || num === null || !!(num && num.toString().indexOf('.') === num.toString().length - 1);
397
    }
398

399
    toNumber(num: string | number): number {
400
        if (this.isNotValid(num)) {
401
            return num as number;
402
        }
403
        const numStr = String(num);
404
        if (numStr.indexOf('.') >= 0 && !isUndefinedOrNull(this.thyPrecision)) {
405
            return Number(Number(num).toFixed(this.thyPrecision));
406
        }
407
        return Number(num);
408
    }
409

410
    isInputNumber(value: string) {
411
        return isFloat(value) || /^[-+Ee]$|^([-+.]?[0-9])*(([.]|[.eE])?[eE]?[+-]?)?$|^$/.test(value);
412
    }
413

414
    ngOnDestroy() {
415
        this.hostFocusControl.destroy();
416
    }
417
}
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