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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

4.92
/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
} from 'ngx-tethys/core';
11
import { ThyMaxDirective, ThyMinDirective } from 'ngx-tethys/form';
12
import { ThyIconComponent } from 'ngx-tethys/icon';
13
import { ThyInputComponent, ThyInputDirective } from 'ngx-tethys/input';
14
import { ThyAutofocusDirective } from 'ngx-tethys/shared';
15
import { DOWN_ARROW, ENTER, isNumber, isUndefinedOrNull, UP_ARROW, isFloat } from 'ngx-tethys/util';
16
import { Subject } from 'rxjs';
1✔
17
import { takeUntil } from 'rxjs/operators';
1✔
18

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

×
36
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
37

38
enum Type {
×
39
    up,
×
40
    down
×
41
}
×
42

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

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

×
72
    private autoStepTimer: any;
×
73

74
    validValue: number | string;
75

76
    displayValue: number | string;
×
77

×
78
    disabledUp = false;
79

80
    disabledDown = false;
81

82
    activeValue: string = '';
83

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

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

95
    /**
96
     * 是否禁用
×
97
     * @default false
×
98
     */
99
    @Input() @InputBoolean() thyDisabled: boolean;
×
100

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

113
    get thyMax() {
114
        return this.innerMax;
×
115
    }
×
116

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

×
129
    get thyMin() {
130
        return this.innerMin;
131
    }
132

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

138
    /**
139
     * 输入框大小
140
     * @type xs | sm | md | lg
×
141
     */
×
142
    @Input() thySize: InputSize;
143

144
    /**
145
     * 数值精度
×
146
     */
×
147
    @Input() thyPrecision: number;
×
148

×
149
    /**
×
150
     * 数值后缀
151
     */
152
    @Input() thySuffix: string;
153

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

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

164
    private innerMax: number = Infinity;
165

166
    private innerMin: number = -Infinity;
×
167

×
168
    private isFocused: boolean;
169

170
    private ngUnsubscribe$ = new Subject<void>();
171

×
172
    constructor(private cdr: ChangeDetectorRef, private elementRef: ElementRef, private focusMonitor: FocusMonitor) {
×
173
        super();
×
174
    }
×
175

176
    setDisabledState?(isDisabled: boolean): void {
×
177
        this.thyDisabled = isDisabled;
178
    }
×
179

×
180
    ngOnInit() {
181
        this.focusMonitor
×
182
            .monitor(this.elementRef, true)
×
183
            .pipe(takeUntil(this.ngUnsubscribe$))
184
            .subscribe(focusOrigin => {
×
185
                if (!focusOrigin) {
×
186
                    this.onBlur();
×
187
                } else {
×
188
                    // call when inputElement.focus and input-number focus
×
189
                    if (!this.isFocused) {
×
190
                        this.onFocus();
×
191
                    }
192
                }
×
193
            });
×
194
    }
195

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

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

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

229
    onModelChange(value: string): void {
×
230
        const parseValue = this.parser(value);
231
        const validValue = this.getCurrentValidValue(parseValue);
×
232
        if (this.validValue !== validValue) {
×
233
            this.updateValidValue(validValue);
234
            this.onChangeFn(this.validValue);
×
235
        }
236
    }
237

×
238
    onInput(input?: ThyInputComponent) {
×
239
        const value = input.value;
240
        if (this.isInputNumber(value)) {
241
            this.activeValue = value;
×
242
        } else {
×
243
            this.displayValue = this.activeValue;
244
            input.value = this.displayValue;
245
        }
×
246
    }
×
247

×
248
    onBlur(event?: FocusEvent) {
249
        if (this.isFocused) {
250
            this.displayValue = this.formatterValue(this.validValue);
×
251
            this.onTouchedFn();
252
            this.thyBlur.emit();
253
            this.isFocused = false;
254
        }
×
255
    }
256

257
    onFocus(event?: Event) {
258
        if (!this.isFocused) {
259
            this.inputElement.nativeElement.focus();
260
        }
261
    }
×
262

×
263
    onInputFocus(event?: Event) {
×
264
        this.activeValue = this.parser(this.displayValue.toString());
265
        if (!this.isFocused) {
×
266
            this.isFocused = true;
×
267
            this.focusMonitor.focusVia(this.inputElement, 'keyboard');
×
268
            this.thyFocus.emit(event);
269
        }
×
270
    }
×
271

272
    onKeyDown(e: KeyboardEvent): void {
×
273
        if (e.keyCode === UP_ARROW) {
×
274
            this.up(e);
275
            this.stop();
×
276
        } else if (e.keyCode === DOWN_ARROW) {
277
            this.down(e);
278
            this.stop();
×
279
        } else if (e.keyCode === ENTER) {
280
            this.displayValue = this.formatterValue(this.validValue);
281
        }
×
282
    }
×
283

284
    stop() {
×
285
        if (this.autoStepTimer) {
×
286
            clearTimeout(this.autoStepTimer);
×
287
        }
288
    }
×
289

290
    step(type: Type, e: MouseEvent | KeyboardEvent): void {
291
        this.stop();
×
292
        e.preventDefault();
293
        if (this.thyDisabled) {
294
            return;
×
295
        }
×
296
        const value = this.validValue as number;
×
297
        let val;
298
        if (type === Type.up) {
1✔
299
            val = this.upStep(value);
300
        } else if (type === Type.down) {
301
            val = this.downStep(value);
302
        }
303
        const outOfRange = val > this.thyMax || val < this.thyMin;
1✔
304
        val = this.getCurrentValidValue(val);
305
        this.updateValidValue(val);
306
        this.onChangeFn(this.validValue);
307
        this.displayValue = this.formatterValue(val);
308
        if (outOfRange) {
309
            return;
310
        }
311
        this.autoStepTimer = setTimeout(() => {
312
            (this[Type[type]] as (e: MouseEvent | KeyboardEvent) => void)(e);
313
        }, 300);
314
    }
315

316
    upStep(value: number): number {
317
        const precisionFactor = this.getPrecisionFactor(value);
318
        const precision = this.getMaxPrecision(value);
1✔
319
        const result = ((precisionFactor * value + precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
320
        return this.toNumber(result);
321
    }
322

1✔
323
    downStep(value: number): number {
324
        const precisionFactor = this.getPrecisionFactor(value);
325
        const precision = Math.abs(this.getMaxPrecision(value));
326
        const result = ((precisionFactor * value - precisionFactor * this.thyStep) / precisionFactor).toFixed(precision);
1✔
327
        return this.toNumber(result);
328
    }
329

330
    getMaxPrecision(value: string | number): number {
1✔
331
        if (!isUndefinedOrNull(this.thyPrecision)) {
332
            return this.thyPrecision;
333
        }
334
        const stepPrecision = this.getPrecision(this.thyStep);
335
        const currentValuePrecision = this.getPrecision(value as number);
336
        if (!value) {
337
            return stepPrecision;
×
338
        }
339
        return Math.max(currentValuePrecision, stepPrecision);
340
    }
341

342
    getPrecisionFactor(activeValue: string | number): number {
343
        const precision = this.getMaxPrecision(activeValue);
344
        return Math.pow(10, precision);
345
    }
346

347
    getPrecision(value: number): number {
348
        const valueString = value.toString();
349
        // 0.0000000004.toString() = 4e10  => 10
350
        if (valueString.indexOf('e-') >= 0) {
351
            return parseInt(valueString.slice(valueString.indexOf('e-') + 2), 10);
352
        }
353
        let precision = 0;
354
        // 1.2222 =>  4
355
        if (valueString.indexOf('.') >= 0) {
356
            precision = valueString.length - valueString.indexOf('.') - 1;
357
        }
358
        return precision;
359
    }
360

361
    up(e: MouseEvent | KeyboardEvent) {
362
        this.inputElement.nativeElement.focus();
363
        this.step(Type.up, e);
364
    }
365

366
    down(e: MouseEvent | KeyboardEvent) {
367
        this.inputElement.nativeElement.focus();
368
        this.step(Type.down, e);
369
    }
370

371
    formatterValue(value: number | string) {
372
        const parseValue = this.parser(`${value}`);
373
        if (parseValue) {
374
            return this.thySuffix ? `${parseValue} ${this.thySuffix}` : parseValue;
375
        } else {
376
            return '';
377
        }
378
    }
379

380
    parser(value: string) {
381
        return value
382
            .trim()
383
            .replace(/。/g, '.')
384
            .replace(/[^\w\.-]+/g, '')
385
            .replace(this.thySuffix, '');
386
    }
387

388
    getCurrentValidValue(value: string | number): number | string {
389
        let val = value;
390
        if (value === '' || value === undefined) {
391
            return '';
392
        }
393
        val = parseFloat(value as string);
394
        if (this.isNotValid(val)) {
395
            val = this.validValue;
396
        }
397
        if ((val as number) < this.thyMin) {
398
            val = this.thyMin;
399
        }
400
        if ((val as number) > this.thyMax) {
401
            val = this.thyMax;
402
        }
403

404
        return this.toNumber(val);
405
    }
406

407
    isNotValid(num: string | number): boolean {
408
        return isNaN(num as number) || num === '' || num === null || !!(num && num.toString().indexOf('.') === num.toString().length - 1);
409
    }
410

411
    toNumber(num: string | number): number {
412
        if (this.isNotValid(num)) {
413
            return num as number;
414
        }
415
        const numStr = String(num);
416
        if (numStr.indexOf('.') >= 0 && !isUndefinedOrNull(this.thyPrecision)) {
417
            return Number(Number(num).toFixed(this.thyPrecision));
418
        }
419
        return Number(num);
420
    }
421

422
    isInputNumber(value: string) {
423
        return isFloat(value) || /^[-+Ee]$|^([-+.]?[0-9])*(([.]|[.eE])?[eE]?[+-]?)?$|^$/.test(value);
424
    }
425

426
    ngOnDestroy() {
427
        this.ngUnsubscribe$.next();
428
        this.ngUnsubscribe$.complete();
429
        this.focusMonitor.stopMonitoring(this.elementRef);
430
    }
431
}
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