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

atinc / ngx-tethys / e62d3b10-1466-49c3-aabd-707148681fc8

14 Jun 2024 08:24AM UTC coverage: 90.422%. Remained the same
e62d3b10-1466-49c3-aabd-707148681fc8

push

circleci

minlovehua
feat: use the ngx-tethys/util's coerceBooleanProperty instead of booleanAttribute #INFR-12648

5467 of 6692 branches covered (81.69%)

Branch coverage included in aggregate %.

117 of 120 new or added lines in 66 files covered. (97.5%)

183 existing lines in 46 files now uncovered.

13216 of 13970 relevant lines covered (94.6%)

985.91 hits per line

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

82.0
/src/date-picker/abstract-picker.component.ts
1
import { TabIndexDisabledControlValueAccessorMixin } from 'ngx-tethys/core';
2
import { coerceBooleanProperty, TinyDate } from 'ngx-tethys/util';
3
import { Subject } from 'rxjs';
4

5
import {
6
    ChangeDetectorRef,
7
    Directive,
8
    EventEmitter,
9
    inject,
10
    Input,
11
    OnChanges,
12
    OnDestroy,
1✔
13
    OnInit,
14
    Output,
69✔
15
    SimpleChanges,
16
    ViewChild
17
} from '@angular/core';
269✔
18
import { ControlValueAccessor } from '@angular/forms';
19

20
import { CompatibleValue, RangeAdvancedValue } from './inner-types';
66!
21
import { ThyPicker } from './picker.component';
66✔
22
import { makeValue, setValueByTimestampPrecision, transformDateValue } from './picker.util';
23
import {
24
    CompatibleDate,
25
    DateEntry,
66✔
26
    DisabledDateFn,
27
    ThyDateRangeEntry,
28
    ThyPanelMode,
342✔
29
    ThyShortcutPosition,
30
    CompatiblePresets,
31
    ThyDateGranularity,
3,026✔
32
    ThyDateChangeEvent
33
} from './standard-types';
34
import { ThyDatePickerConfigService } from './date-picker.service';
13✔
35
import { SafeAny } from 'ngx-tethys/types';
36

37
/**
691✔
38
 * @private
39
 */
40
@Directive()
153✔
41
export abstract class AbstractPickerComponent
42
    extends TabIndexDisabledControlValueAccessorMixin
43
    implements OnInit, OnChanges, OnDestroy, ControlValueAccessor
187✔
44
{
187✔
45
    thyValue: CompatibleValue | null;
187✔
46

187✔
47
    _panelMode: ThyPanelMode = 'date';
187✔
48

187✔
49
    private datePickerConfigService = inject(ThyDatePickerConfigService);
187✔
50

187!
51
    /**
187✔
52
     * 模式
187✔
53
     * @type decade | year | month | date | week | flexible
187✔
54
     */
187✔
55
    @Input() set thyMode(value: ThyPanelMode) {
187✔
56
        this._panelMode = value ?? 'date';
187✔
57
    }
187✔
58

187✔
59
    get thyMode() {
187✔
60
        return this._panelMode;
187✔
61
    }
187✔
62

63
    /**
64
     * 是否显示清除按钮
153✔
65
     */
153✔
66
    @Input({ transform: coerceBooleanProperty }) thyAllowClear = true;
153✔
67

68
    /**
69
     * 是否自动获取焦点
153✔
70
     * @default false
71
     */
72
    @Input({ transform: coerceBooleanProperty }) thyAutoFocus = false;
41✔
73

74
    @Input({ transform: coerceBooleanProperty }) thyOpen: boolean;
75

230✔
76
    @Input() thyDisabledDate: DisabledDateFn;
37✔
77

78
    /**
79
     * 最小值
80
     * @type Date | number
153✔
81
     */
153✔
82
    @Input() thyMinDate: Date | number;
83

84
    /**
45✔
85
     * 最大值
86
     * @type Date | number
87
     */
UNCOV
88
    @Input() thyMaxDate: Date | number;
×
89

UNCOV
90
    /**
×
91
     * 输入框提示文字
92
     * @type string | string[]
93
     */
UNCOV
94
    @Input() thyPlaceHolder: string | string[];
×
95

UNCOV
96
    /**
×
97
     * 是否只读
98
     * @default false
99
     */
NEW
100
    @Input({ transform: coerceBooleanProperty }) thyReadonly: boolean;
×
101

UNCOV
102
    /**
×
103
     * 选择器 className
104
     */
105
    @Input() thyOriginClassName: string;
UNCOV
106

×
107
    /**
UNCOV
108
     * 弹出层 className
×
109
     */
110
    @Input() thyPanelClassName: string;
111

UNCOV
112
    /**
×
113
     * 输入框的大小
UNCOV
114
     * @type xs | sm | md | lg | default
×
115
     */
116
    @Input() thySize: 'lg' | 'md' | 'sm' | 'xs' | 'default' = 'default';
117

UNCOV
118
    /**
×
119
     * 设置时间戳精度
UNCOV
120
     * @default seconds 10位
×
121
     */
122
    @Input() thyTimestampPrecision: 'seconds' | 'milliseconds' = this.datePickerConfigService.config?.timestampPrecision || 'seconds';
123

70✔
124
    /**
70✔
125
     * 展示的日期格式
70✔
126
     * @default yyyy-MM-dd
70✔
127
     */
70✔
128
    @Input() thyFormat: string;
28✔
129

28✔
130
    /**
28✔
131
     * @description.en-us only for range picker, Whether to automatically take the beginning and ending unixTime of the day
25✔
132
     * @description.zh-cn 是否取值开始日期的00:00以及截止日期的24:00
25!
UNCOV
133
     * @default false
×
134
     */
135
    @Input({ transform: coerceBooleanProperty }) thyAutoStartAndEnd = false;
136

25✔
137
    /**
138
     * 面板默认日期
139
     * @type CompatibleDate | number | null
140
     */
141
    @Input() thyDefaultPickerValue: CompatibleDate | number | null = null;
142

28✔
143
    /**
28✔
144
     * 自定义的后缀图标
145
     */
146
    @Input() thySuffixIcon = 'calendar';
42✔
147

42✔
148
    /**
32✔
149
     * 是否展示快捷选项面板
150
     * @default false
42✔
151
     */
39✔
152
    @Input({ transform: coerceBooleanProperty }) thyShowShortcut: boolean;
153

154
    /**
3✔
155
     * 快捷选项面板的显示位置
156
     * @type left | bottom
157
     */
158
    @Input() set thyShortcutPosition(position: ThyShortcutPosition) {
159
        if (!!position) {
450✔
160
            this.shortcutPosition = position;
74!
UNCOV
161
        }
×
162
    }
163

164
    /**
74!
UNCOV
165
     * 自定义快捷选项
×
166
     * @type ThyShortcutPreset[]
167
     */
168
    @Input() set thyShortcutPresets(presets: CompatiblePresets) {
169
        this.shortcutPresets = presets;
170
    }
UNCOV
171

×
UNCOV
172
    /**
×
173
     * 日期变化的回调
174
     */
175
    @Output() readonly thyDateChange = new EventEmitter<ThyDateChangeEvent>();
380✔
176

380✔
177
    @Output() readonly thyOpenChange = new EventEmitter<boolean>();
380✔
178

3✔
179
    @ViewChild(ThyPicker, { static: true }) public picker: ThyPicker;
2✔
180

181
    /**
182
     * 是否禁用
380✔
183
     * @default false
380✔
184
     */
380✔
185
    @Input({ transform: coerceBooleanProperty })
380✔
186
    set thyDisabled(value: boolean) {
380✔
187
        this.disabled = value;
380✔
188
    }
189
    get thyDisabled(): boolean {
190
        return this.disabled;
381✔
191
    }
192

193
    disabled = false;
182✔
194

182✔
195
    shortcutPosition: ThyShortcutPosition = 'left';
196

197
    shortcutPresets: CompatiblePresets;
153✔
198

116✔
199
    isRange: boolean;
200

153✔
201
    withTime: boolean;
202

203
    flexible: boolean;
450✔
204

205
    flexibleDateGranularity: ThyDateGranularity;
206

70✔
207
    protected destroyed$: Subject<void> = new Subject();
208
    protected isCustomPlaceHolder = false;
1✔
209
    private onlyEmitDate = false;
210
    protected originWithTime: boolean;
211

1✔
212
    get realOpenState(): boolean {
213
        return this.picker.realOpenState;
214
    }
215

216
    get isShowDatePopup(): boolean {
217
        return this.picker.isShowDatePopup;
218
    }
219

220
    initValue(): void {
221
        this.thyValue = this.isRange ? [] : null;
222
    }
223

224
    constructor(public cdr: ChangeDetectorRef) {
225
        super();
226
    }
227

228
    ngOnInit(): void {
229
        this.setDefaultPlaceHolder();
230
        this.initValue();
231
        this.isFlexible();
232
    }
233

234
    isFlexible() {
235
        this.flexible = this.thyMode === 'flexible';
236
    }
237

238
    onDateValueChange(event: ThyDateChangeEvent) {
1✔
239
        this.thyDateChange.emit(event);
240
    }
241

242
    ngOnChanges(changes: SimpleChanges): void {
243
        if (changes.thyPlaceHolder && changes.thyPlaceHolder.firstChange && typeof this.thyPlaceHolder !== 'undefined') {
244
            this.isCustomPlaceHolder = true;
245
        }
246
    }
247

248
    ngOnDestroy(): void {
249
        this.destroyed$.next();
250
        this.destroyed$.complete();
251
    }
252

253
    closeOverlay(): void {
254
        this.picker.hideOverlay();
255
    }
256

257
    getAutoStartAndEndValue(begin: TinyDate, end: TinyDate) {
258
        let value: { begin: number; end: number };
259
        switch (this.thyMode) {
260
            case 'date':
261
                value = {
262
                    begin: begin.startOfDay().getUnixTime(),
263
                    end: end.endOfDay().getUnixTime()
264
                };
265
                break;
266
            case 'week':
267
                value = {
268
                    begin: begin.startOfWeek().getUnixTime(),
269
                    end: end.endOfWeek().getUnixTime()
270
                };
271
                break;
272
            case 'month':
273
                value = {
274
                    begin: begin.startOfMonth().getUnixTime(),
275
                    end: end.endOfMonth().getUnixTime()
276
                };
277
                break;
278
            case 'year':
279
                value = {
280
                    begin: begin.startOfYear().getUnixTime(),
281
                    end: end.endOfYear().getUnixTime()
282
                };
283
                break;
284
            default:
285
                value = {
286
                    begin: begin.startOfDay().getUnixTime(),
287
                    end: end.endOfDay().getUnixTime()
288
                };
289
                break;
290
        }
291
        return value;
292
    }
293

294
    onValueChange(originalValue: CompatibleValue | RangeAdvancedValue): void {
295
        this.setFormatRule();
296
        const { value, withTime, flexibleDateGranularity } = transformDateValue(originalValue);
297
        this.flexibleDateGranularity = flexibleDateGranularity;
298
        this.setValue(value);
299
        if (this.isRange) {
300
            const vAsRange: any = this.thyValue;
301
            let value = { begin: null, end: null } as ThyDateRangeEntry;
302
            if (vAsRange.length) {
303
                const [begin, end] = vAsRange as TinyDate[];
304
                if (this.thyAutoStartAndEnd) {
305
                    value = this.getAutoStartAndEndValue(begin, end);
306
                } else {
307
                    value = {
308
                        begin: begin.getUnixTime(),
309
                        end: end.getUnixTime()
310
                    };
311
                }
312
            }
313
            const [beginUnixTime, endUnixTime] = this.setValueByPrecision(value) as number[];
314
            this.onChangeFn(
315
                Object.assign({ begin: beginUnixTime, end: endUnixTime }, this.flexible ? { granularity: flexibleDateGranularity } : {})
316
            );
317
        } else {
318
            const value = { date: null, with_time: this.withTime ? 1 : 0 } as DateEntry;
319
            if (this.thyValue) {
320
                value.date = (this.thyValue as TinyDate).getUnixTime();
321
            }
322
            if (this.onlyEmitDate) {
323
                this.onChangeFn(this.setValueByPrecision(value.date) as number);
324
            } else {
325
                this.onChangeFn(Object.assign(value, { date: this.setValueByPrecision(value.date) as number }));
326
            }
327
        }
328
    }
329

330
    setFormatRule() {
331
        if (!this.thyFormat) {
332
            if (this.withTime) {
333
                this.thyFormat = 'yyyy-MM-dd HH:mm';
334
            } else {
335
                if (!this.onlyEmitDate) {
336
                    this.thyFormat = 'yyyy-MM-dd';
337
                }
338
            }
339
        }
340
    }
341

342
    onOpenChange(open: boolean): void {
343
        this.thyOpen = open;
344
        this.thyOpenChange.emit(open);
345
    }
346

347
    onChangeFn: (val: CompatibleDate | DateEntry | ThyDateRangeEntry | number | null) => void = () => void 0;
348

349
    writeValue(originalValue: CompatibleDate | ThyDateRangeEntry): void {
350
        const { value, withTime, flexibleDateGranularity } = transformDateValue(originalValue);
351
        this.flexibleDateGranularity = flexibleDateGranularity;
352
        if (this.flexible && value && (value as Date[]).length) {
353
            if (!this.flexibleDateGranularity) {
354
                this.flexibleDateGranularity = 'day';
355
            }
356
        }
357

358
        this.setValue(value);
359
        this.setTimePickerState(withTime);
360
        this.onlyEmitDate = typeof withTime === 'undefined';
361
        this.originWithTime = withTime;
362
        this.setFormatRule();
363
        this.cdr.markForCheck();
364
    }
365

366
    setTimePickerState(withTime: boolean): void {
367
        this.withTime = withTime;
368
    }
369

370
    setDisabledState(disabled: boolean): void {
371
        this.thyDisabled = disabled;
372
        this.cdr.markForCheck();
373
    }
374

375
    private setDefaultPlaceHolder(): void {
376
        if (!this.isCustomPlaceHolder) {
377
            this.thyPlaceHolder = this.isRange ? ['开始日期', '结束日期'] : '请选择日期';
378
        }
379
        this.cdr.markForCheck();
380
    }
381

382
    public setValue(value: CompatibleDate): void {
383
        this.thyValue = makeValue(value, this.isRange);
384
    }
385

386
    private setValueByPrecision(value: CompatibleDate | number | Date | DateEntry | ThyDateRangeEntry | SafeAny): number | number[] {
387
        return setValueByTimestampPrecision(value, this.isRange, this.thyTimestampPrecision);
388
    }
389
}
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