@imihai/angular-datetime-picker 21.0.2 → 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +9 -0
- package/.editorconfig +13 -0
- package/.eslintrc.js +208 -0
- package/.github/workflows/ci_test.yml +35 -0
- package/.prettierrc +2 -0
- package/.travis.yml +16 -0
- package/angular.json +221 -0
- package/e2e/protractor.conf.js +28 -0
- package/e2e/src/app.e2e-spec.ts +14 -0
- package/e2e/src/app.po.ts +11 -0
- package/e2e/tsconfig.e2e.json +13 -0
- package/package.json +77 -33
- package/projects/picker/karma.conf.js +62 -0
- package/projects/picker/ng-package.json +7 -0
- package/projects/picker/package.json +23 -0
- package/projects/picker/src/lib/date-time/adapter/date-time-adapter.class.ts +313 -0
- package/projects/picker/src/lib/date-time/adapter/date-time-format.class.ts +18 -0
- package/projects/picker/src/lib/date-time/adapter/luxon-date-time-adapter.class.ts +307 -0
- package/projects/picker/src/lib/date-time/adapter/luxon-date-time-format.class.ts +11 -0
- package/projects/picker/src/lib/date-time/adapter/luxon-date-time.module.ts +31 -0
- package/projects/picker/src/lib/date-time/adapter/unix-timestamp-adapter/unix-timestamp-date-time-adapter.class.ts +356 -0
- package/projects/picker/src/lib/date-time/adapter/unix-timestamp-adapter/unix-timestamp-date-time-format.class.ts +14 -0
- package/projects/picker/src/lib/date-time/adapter/unix-timestamp-adapter/unix-timestamp-date-time.module.ts +26 -0
- package/projects/picker/src/lib/date-time/calendar-body.component.html +38 -0
- package/projects/picker/src/lib/date-time/calendar-body.component.scss +0 -0
- package/projects/picker/src/lib/date-time/calendar-body.component.spec.ts +120 -0
- package/projects/picker/src/lib/date-time/calendar-body.component.ts +190 -0
- package/projects/picker/src/lib/date-time/calendar-month-view.component.html +47 -0
- package/projects/picker/src/lib/date-time/calendar-month-view.component.scss +16 -0
- package/projects/picker/src/lib/date-time/calendar-month-view.component.spec.ts +394 -0
- package/projects/picker/src/lib/date-time/calendar-month-view.component.ts +705 -0
- package/projects/picker/src/lib/date-time/calendar-multi-year-view.component.html +44 -0
- package/projects/picker/src/lib/date-time/calendar-multi-year-view.component.scss +0 -0
- package/projects/picker/src/lib/date-time/calendar-multi-year-view.component.spec.ts +343 -0
- package/projects/picker/src/lib/date-time/calendar-multi-year-view.component.ts +465 -0
- package/projects/picker/src/lib/date-time/calendar-year-view.component.html +16 -0
- package/projects/picker/src/lib/date-time/calendar-year-view.component.scss +0 -0
- package/projects/picker/src/lib/date-time/calendar-year-view.component.spec.ts +381 -0
- package/projects/picker/src/lib/date-time/calendar-year-view.component.ts +549 -0
- package/projects/picker/src/lib/date-time/calendar.component.html +169 -0
- package/projects/picker/src/lib/date-time/calendar.component.scss +0 -0
- package/projects/picker/src/lib/date-time/calendar.component.spec.ts +592 -0
- package/projects/picker/src/lib/date-time/calendar.component.ts +496 -0
- package/projects/picker/src/lib/date-time/date-time-inline.component.html +1 -0
- package/projects/picker/src/lib/date-time/date-time-inline.component.scss +4 -0
- package/projects/picker/src/lib/date-time/date-time-inline.component.ts +365 -0
- package/projects/picker/src/lib/date-time/date-time-picker-animation-event.ts +4 -0
- package/projects/picker/src/lib/date-time/date-time-picker-container.component.html +126 -0
- package/projects/picker/src/lib/date-time/date-time-picker-container.component.scss +0 -0
- package/projects/picker/src/lib/date-time/date-time-picker-container.component.ts +631 -0
- package/projects/picker/src/lib/date-time/date-time-picker-input.directive.ts +851 -0
- package/projects/picker/src/lib/date-time/date-time-picker-intl.service.ts +76 -0
- package/projects/picker/src/lib/date-time/date-time-picker-trigger.directive.ts +88 -0
- package/projects/picker/src/lib/date-time/date-time-picker.component.html +0 -0
- package/projects/picker/src/lib/date-time/date-time-picker.component.scss +0 -0
- package/projects/picker/src/lib/date-time/date-time-picker.component.spec.ts +2648 -0
- package/projects/picker/src/lib/date-time/date-time-picker.component.ts +731 -0
- package/projects/picker/src/lib/date-time/date-time.class.ts +256 -0
- package/projects/picker/src/lib/date-time/date-time.module.ts +61 -0
- package/projects/picker/src/lib/date-time/numberedFixLen.pipe.ts +28 -0
- package/projects/picker/src/lib/date-time/options-provider.ts +44 -0
- package/projects/picker/src/lib/date-time/timer-box.component.html +79 -0
- package/projects/picker/src/lib/date-time/timer-box.component.scss +0 -0
- package/projects/picker/src/lib/date-time/timer-box.component.ts +175 -0
- package/projects/picker/src/lib/date-time/timer.component.html +58 -0
- package/projects/picker/src/lib/date-time/timer.component.scss +0 -0
- package/projects/picker/src/lib/date-time/timer.component.spec.ts +399 -0
- package/projects/picker/src/lib/date-time/timer.component.ts +371 -0
- package/projects/picker/src/lib/dialog/dialog-config.class.ts +220 -0
- package/projects/picker/src/lib/dialog/dialog-container.component.html +1 -0
- package/projects/picker/src/lib/dialog/dialog-container.component.scss +0 -0
- package/projects/picker/src/lib/dialog/dialog-container.component.ts +231 -0
- package/projects/picker/src/lib/dialog/dialog-ref.class.ts +178 -0
- package/projects/picker/src/lib/dialog/dialog.module.ts +25 -0
- package/projects/picker/src/lib/dialog/dialog.service.ts +353 -0
- package/projects/picker/src/lib/utils/array.utils.ts +12 -0
- package/projects/picker/src/lib/utils/constants.ts +59 -0
- package/projects/picker/src/lib/utils/date.utils.ts +97 -0
- package/projects/picker/src/lib/utils/index.ts +5 -0
- package/projects/picker/src/lib/utils/object.utils.ts +29 -0
- package/projects/picker/src/public_api.ts +56 -0
- package/projects/picker/src/sass/picker.scss +743 -0
- package/projects/picker/src/test-helpers.ts +125 -0
- package/projects/picker/src/test.ts +52 -0
- package/projects/picker/tsconfig.lib.json +19 -0
- package/projects/picker/tsconfig.lib.prod.json +7 -0
- package/projects/picker/tsconfig.spec.json +17 -0
- package/projects/picker/tslint.json +8 -0
- package/src/app/app.component.html +77 -0
- package/src/app/app.component.scss +32 -0
- package/src/app/app.component.spec.ts +16 -0
- package/src/app/app.component.ts +35 -0
- package/src/assets/.gitkeep +0 -0
- package/src/environments/environment.prod.ts +3 -0
- package/src/environments/environment.ts +16 -0
- package/src/favicon.ico +0 -0
- package/src/index.html +14 -0
- package/src/karma.conf.js +31 -0
- package/src/main.ts +16 -0
- package/src/polyfills.ts +53 -0
- package/src/styles.scss +7 -0
- package/src/test.ts +50 -0
- package/src/tsconfig.app.json +16 -0
- package/src/tsconfig.spec.json +18 -0
- package/src/tslint.json +17 -0
- package/tsconfig.base.json +31 -0
- package/tsconfig.json +30 -0
- package/tslint.json +130 -0
- package/assets/style/picker.min.css +0 -1
- package/fesm2022/imihai-angular-datetime-picker.mjs +0 -5845
- package/fesm2022/imihai-angular-datetime-picker.mjs.map +0 -1
- package/imihai-angular-datetime-picker-21.0.2.tgz +0 -0
- package/types/imihai-angular-datetime-picker.d.ts +0 -2193
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* date-time-picker-input.directive
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AfterContentInit,
|
|
7
|
+
Directive,
|
|
8
|
+
ElementRef,
|
|
9
|
+
EventEmitter,
|
|
10
|
+
forwardRef,
|
|
11
|
+
Inject,
|
|
12
|
+
Input,
|
|
13
|
+
OnDestroy,
|
|
14
|
+
OnInit,
|
|
15
|
+
Optional,
|
|
16
|
+
Output,
|
|
17
|
+
Renderer2
|
|
18
|
+
} from '@angular/core';
|
|
19
|
+
import {
|
|
20
|
+
AbstractControl,
|
|
21
|
+
ControlValueAccessor,
|
|
22
|
+
NG_VALIDATORS,
|
|
23
|
+
NG_VALUE_ACCESSOR,
|
|
24
|
+
ValidationErrors,
|
|
25
|
+
Validator,
|
|
26
|
+
ValidatorFn,
|
|
27
|
+
Validators
|
|
28
|
+
} from '@angular/forms';
|
|
29
|
+
import { DOWN_ARROW } from '@angular/cdk/keycodes';
|
|
30
|
+
import { OwlDateTimeComponent } from './date-time-picker.component';
|
|
31
|
+
import { DateTimeAdapter } from './adapter/date-time-adapter.class';
|
|
32
|
+
import {
|
|
33
|
+
OWL_DATE_TIME_FORMATS,
|
|
34
|
+
OwlDateTimeFormats
|
|
35
|
+
} from './adapter/date-time-format.class';
|
|
36
|
+
import { Subscription } from 'rxjs';
|
|
37
|
+
import { SelectMode } from './date-time.class';
|
|
38
|
+
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
39
|
+
|
|
40
|
+
export const OWL_DATETIME_VALUE_ACCESSOR: any = {
|
|
41
|
+
provide: NG_VALUE_ACCESSOR,
|
|
42
|
+
useExisting: forwardRef(() => OwlDateTimeInputDirective),
|
|
43
|
+
multi: true
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const OWL_DATETIME_VALIDATORS: any = {
|
|
47
|
+
provide: NG_VALIDATORS,
|
|
48
|
+
useExisting: forwardRef(() => OwlDateTimeInputDirective),
|
|
49
|
+
multi: true
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
@Directive({
|
|
53
|
+
selector: 'input[owlDateTime]',
|
|
54
|
+
exportAs: 'owlDateTimeInput',
|
|
55
|
+
standalone: false,
|
|
56
|
+
host: {
|
|
57
|
+
'(keydown)': 'handleKeydownOnHost($event)',
|
|
58
|
+
'(blur)': 'handleBlurOnHost($event)',
|
|
59
|
+
'(input)': 'handleInputOnHost($event)',
|
|
60
|
+
'(change)': 'handleChangeOnHost($event)',
|
|
61
|
+
'[attr.aria-haspopup]': 'owlDateTimeInputAriaHaspopup',
|
|
62
|
+
'[attr.aria-owns]': 'owlDateTimeInputAriaOwns',
|
|
63
|
+
'[attr.min]': 'minIso8601',
|
|
64
|
+
'[attr.max]': 'maxIso8601',
|
|
65
|
+
'[disabled]': 'owlDateTimeInputDisabled'
|
|
66
|
+
},
|
|
67
|
+
providers: [
|
|
68
|
+
OWL_DATETIME_VALUE_ACCESSOR,
|
|
69
|
+
OWL_DATETIME_VALIDATORS,
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
export class OwlDateTimeInputDirective<T>
|
|
73
|
+
implements
|
|
74
|
+
OnInit,
|
|
75
|
+
AfterContentInit,
|
|
76
|
+
OnDestroy,
|
|
77
|
+
ControlValueAccessor,
|
|
78
|
+
Validator {
|
|
79
|
+
static ngAcceptInputType_disabled: boolean|'';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Required flag to be used for range of [null, null]
|
|
83
|
+
* */
|
|
84
|
+
private _required: boolean;
|
|
85
|
+
@Input()
|
|
86
|
+
get required() {
|
|
87
|
+
return this._required;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set required(value: any) {
|
|
91
|
+
this._required = value === '' || value;
|
|
92
|
+
this.validatorOnChange();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The date time picker that this input is associated with.
|
|
97
|
+
* */
|
|
98
|
+
@Input()
|
|
99
|
+
set owlDateTime(value: OwlDateTimeComponent<T>) {
|
|
100
|
+
this.registerDateTimePicker(value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A function to filter date time
|
|
105
|
+
*/
|
|
106
|
+
@Input()
|
|
107
|
+
set owlDateTimeFilter(filter: (date: T | null) => boolean) {
|
|
108
|
+
this._dateTimeFilter = filter;
|
|
109
|
+
this.validatorOnChange();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private _dateTimeFilter: (date: T | null) => boolean;
|
|
113
|
+
get dateTimeFilter() {
|
|
114
|
+
return this._dateTimeFilter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Whether the date time picker's input is disabled. */
|
|
118
|
+
@Input()
|
|
119
|
+
private _disabled: boolean;
|
|
120
|
+
get disabled() {
|
|
121
|
+
return !!this._disabled;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
set disabled(value: boolean) {
|
|
125
|
+
const newValue = coerceBooleanProperty(value);
|
|
126
|
+
const element = this.elmRef.nativeElement;
|
|
127
|
+
|
|
128
|
+
if (this._disabled !== newValue) {
|
|
129
|
+
this._disabled = newValue;
|
|
130
|
+
this.disabledChange.emit(newValue);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// We need to null check the `blur` method, because it's undefined during SSR.
|
|
134
|
+
if (newValue && element.blur) {
|
|
135
|
+
// Normally, native input elements automatically blur if they turn disabled. This behavior
|
|
136
|
+
// is problematic, because it would mean that it triggers another change detection cycle,
|
|
137
|
+
// which then causes a changed after checked error if the input element was focused before.
|
|
138
|
+
element.blur();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The minimum valid date. */
|
|
143
|
+
private _min: T | null;
|
|
144
|
+
@Input()
|
|
145
|
+
get min(): T | null {
|
|
146
|
+
return this._min;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
set min(value: T | null) {
|
|
150
|
+
this._min = this.getValidDate(this.dateTimeAdapter.deserialize(value));
|
|
151
|
+
this.validatorOnChange();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The maximum valid date. */
|
|
155
|
+
private _max: T | null;
|
|
156
|
+
@Input()
|
|
157
|
+
get max(): T | null {
|
|
158
|
+
return this._max;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
set max(value: T | null) {
|
|
162
|
+
this._max = this.getValidDate(this.dateTimeAdapter.deserialize(value));
|
|
163
|
+
this.validatorOnChange();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The picker's select mode
|
|
168
|
+
*/
|
|
169
|
+
private _selectMode: SelectMode = 'single';
|
|
170
|
+
@Input()
|
|
171
|
+
get selectMode() {
|
|
172
|
+
return this._selectMode;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set selectMode(mode: SelectMode) {
|
|
176
|
+
if (
|
|
177
|
+
mode !== 'single' &&
|
|
178
|
+
mode !== 'range' &&
|
|
179
|
+
mode !== 'rangeFrom' &&
|
|
180
|
+
mode !== 'rangeTo'
|
|
181
|
+
) {
|
|
182
|
+
throw Error('OwlDateTime Error: invalid selectMode value!');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this._selectMode = mode;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The character to separate the 'from' and 'to' in input value
|
|
190
|
+
*/
|
|
191
|
+
@Input()
|
|
192
|
+
rangeSeparator = '-';
|
|
193
|
+
|
|
194
|
+
private _value: T | null;
|
|
195
|
+
@Input()
|
|
196
|
+
get value() {
|
|
197
|
+
return this._value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
set value(value: T | null) {
|
|
201
|
+
value = this.dateTimeAdapter.deserialize(value);
|
|
202
|
+
this.lastValueValid = !value || this.dateTimeAdapter.isValid(value);
|
|
203
|
+
value = this.getValidDate(value);
|
|
204
|
+
const oldDate = this._value;
|
|
205
|
+
this._value = value;
|
|
206
|
+
|
|
207
|
+
// set the input property 'value'
|
|
208
|
+
this.formatNativeInputValue();
|
|
209
|
+
|
|
210
|
+
// check if the input value changed
|
|
211
|
+
if (!this.dateTimeAdapter.isEqual(oldDate, value)) {
|
|
212
|
+
this.valueChange.emit(value);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private _values: T[] = [];
|
|
217
|
+
@Input()
|
|
218
|
+
get values() {
|
|
219
|
+
return this._values;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
set values(values: T[]) {
|
|
223
|
+
if (values && values.length > 0) {
|
|
224
|
+
this._values = values.map(v => {
|
|
225
|
+
v = this.dateTimeAdapter.deserialize(v);
|
|
226
|
+
return this.getValidDate(v);
|
|
227
|
+
});
|
|
228
|
+
this.lastValueValid =
|
|
229
|
+
(!this._values[0] ||
|
|
230
|
+
this.dateTimeAdapter.isValid(this._values[0])) &&
|
|
231
|
+
(!this._values[1] ||
|
|
232
|
+
this.dateTimeAdapter.isValid(this._values[1]));
|
|
233
|
+
} else {
|
|
234
|
+
this._values = [];
|
|
235
|
+
this.lastValueValid = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// set the input property 'value'
|
|
239
|
+
this.formatNativeInputValue();
|
|
240
|
+
|
|
241
|
+
this.valueChange.emit(this._values);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Callback to invoke when `change` event is fired on this `<input>`
|
|
246
|
+
* */
|
|
247
|
+
@Output()
|
|
248
|
+
dateTimeChange = new EventEmitter<any>();
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Callback to invoke when an `input` event is fired on this `<input>`.
|
|
252
|
+
* */
|
|
253
|
+
@Output()
|
|
254
|
+
dateTimeInput = new EventEmitter<any>();
|
|
255
|
+
|
|
256
|
+
get elementRef(): ElementRef {
|
|
257
|
+
return this.elmRef;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
get isInSingleMode(): boolean {
|
|
261
|
+
return this._selectMode === 'single';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
get isInRangeMode(): boolean {
|
|
265
|
+
return (
|
|
266
|
+
this._selectMode === 'range' ||
|
|
267
|
+
this._selectMode === 'rangeFrom' ||
|
|
268
|
+
this._selectMode === 'rangeTo'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** The date-time-picker that this input is associated with. */
|
|
273
|
+
public dtPicker: OwlDateTimeComponent<T>;
|
|
274
|
+
|
|
275
|
+
private dtPickerSub: Subscription = Subscription.EMPTY;
|
|
276
|
+
private localeSub: Subscription = Subscription.EMPTY;
|
|
277
|
+
|
|
278
|
+
private lastValueValid = true;
|
|
279
|
+
|
|
280
|
+
private onModelChange: Function = () => {};
|
|
281
|
+
private onModelTouched: Function = () => {};
|
|
282
|
+
private validatorOnChange: Function = () => {};
|
|
283
|
+
|
|
284
|
+
/** The form control validator for whether the input parses. */
|
|
285
|
+
private parseValidator: ValidatorFn = (): ValidationErrors | null => {
|
|
286
|
+
return this.lastValueValid
|
|
287
|
+
? null
|
|
288
|
+
: { owlDateTimeParse: { text: this.elmRef.nativeElement.value } };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The form control validator for the min date. */
|
|
292
|
+
private minValidator: ValidatorFn = (
|
|
293
|
+
control: AbstractControl
|
|
294
|
+
): ValidationErrors | null => {
|
|
295
|
+
if (this.isInSingleMode) {
|
|
296
|
+
const controlValue = this.getValidDate(
|
|
297
|
+
this.dateTimeAdapter.deserialize(control.value)
|
|
298
|
+
);
|
|
299
|
+
return !this.min ||
|
|
300
|
+
!controlValue ||
|
|
301
|
+
this.dateTimeAdapter.compare(this.min, controlValue) <= 0
|
|
302
|
+
? null
|
|
303
|
+
: { owlDateTimeMin: { min: this.min, actual: controlValue } };
|
|
304
|
+
} else if (this.isInRangeMode && control.value) {
|
|
305
|
+
const controlValueFrom = this.getValidDate(
|
|
306
|
+
this.dateTimeAdapter.deserialize(control.value[0])
|
|
307
|
+
);
|
|
308
|
+
const controlValueTo = this.getValidDate(
|
|
309
|
+
this.dateTimeAdapter.deserialize(control.value[1])
|
|
310
|
+
);
|
|
311
|
+
return !this.min ||
|
|
312
|
+
!controlValueFrom ||
|
|
313
|
+
!controlValueTo ||
|
|
314
|
+
this.dateTimeAdapter.compare(this.min, controlValueFrom) <= 0
|
|
315
|
+
? null
|
|
316
|
+
: {
|
|
317
|
+
owlDateTimeMin: {
|
|
318
|
+
min: this.min,
|
|
319
|
+
actual: [controlValueFrom, controlValueTo]
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The form control validator for the max date. */
|
|
326
|
+
private maxValidator: ValidatorFn = (
|
|
327
|
+
control: AbstractControl
|
|
328
|
+
): ValidationErrors | null => {
|
|
329
|
+
if (this.isInSingleMode) {
|
|
330
|
+
const controlValue = this.getValidDate(
|
|
331
|
+
this.dateTimeAdapter.deserialize(control.value)
|
|
332
|
+
);
|
|
333
|
+
return !this.max ||
|
|
334
|
+
!controlValue ||
|
|
335
|
+
this.dateTimeAdapter.compare(this.max, controlValue) >= 0
|
|
336
|
+
? null
|
|
337
|
+
: { owlDateTimeMax: { max: this.max, actual: controlValue } };
|
|
338
|
+
} else if (this.isInRangeMode && control.value) {
|
|
339
|
+
const controlValueFrom = this.getValidDate(
|
|
340
|
+
this.dateTimeAdapter.deserialize(control.value[0])
|
|
341
|
+
);
|
|
342
|
+
const controlValueTo = this.getValidDate(
|
|
343
|
+
this.dateTimeAdapter.deserialize(control.value[1])
|
|
344
|
+
);
|
|
345
|
+
return !this.max ||
|
|
346
|
+
!controlValueFrom ||
|
|
347
|
+
!controlValueTo ||
|
|
348
|
+
this.dateTimeAdapter.compare(this.max, controlValueTo) >= 0
|
|
349
|
+
? null
|
|
350
|
+
: {
|
|
351
|
+
owlDateTimeMax: {
|
|
352
|
+
max: this.max,
|
|
353
|
+
actual: [controlValueFrom, controlValueTo]
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The form control validator for the date filter. */
|
|
360
|
+
private filterValidator: ValidatorFn = (
|
|
361
|
+
control: AbstractControl
|
|
362
|
+
): ValidationErrors | null => {
|
|
363
|
+
const controlValue = this.getValidDate(
|
|
364
|
+
this.dateTimeAdapter.deserialize(control.value)
|
|
365
|
+
);
|
|
366
|
+
return !this._dateTimeFilter ||
|
|
367
|
+
!controlValue ||
|
|
368
|
+
this._dateTimeFilter(controlValue)
|
|
369
|
+
? null
|
|
370
|
+
: { owlDateTimeFilter: true };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The form control validator for the range.
|
|
375
|
+
* Check whether the 'before' value is before the 'to' value
|
|
376
|
+
* */
|
|
377
|
+
private rangeValidator: ValidatorFn = (
|
|
378
|
+
control: AbstractControl
|
|
379
|
+
): ValidationErrors | null => {
|
|
380
|
+
if (this.isInSingleMode || !control.value) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const controlValueFrom = this.getValidDate(
|
|
385
|
+
this.dateTimeAdapter.deserialize(control.value[0])
|
|
386
|
+
);
|
|
387
|
+
const controlValueTo = this.getValidDate(
|
|
388
|
+
this.dateTimeAdapter.deserialize(control.value[1])
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
return !controlValueFrom ||
|
|
392
|
+
!controlValueTo ||
|
|
393
|
+
this.dateTimeAdapter.compare(controlValueFrom, controlValueTo) <= 0
|
|
394
|
+
? null
|
|
395
|
+
: { owlDateTimeRange: true };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The form control validator for the range when required.
|
|
400
|
+
* Check whether the 'before' and 'to' values are present
|
|
401
|
+
* */
|
|
402
|
+
private requiredRangeValidator: ValidatorFn = (
|
|
403
|
+
control: AbstractControl
|
|
404
|
+
): ValidationErrors | null => {
|
|
405
|
+
if (this.isInSingleMode || !control.value || !this.required) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const controlValueFrom = this.getValidDate(
|
|
410
|
+
this.dateTimeAdapter.deserialize(control.value[0])
|
|
411
|
+
);
|
|
412
|
+
const controlValueTo = this.getValidDate(
|
|
413
|
+
this.dateTimeAdapter.deserialize(control.value[1])
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return !controlValueFrom ||
|
|
417
|
+
!controlValueTo
|
|
418
|
+
? { owlRequiredDateTimeRange: [controlValueFrom, controlValueTo] }
|
|
419
|
+
: null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** The combined form control validator for this input. */
|
|
423
|
+
private validator: ValidatorFn | null = Validators.compose([
|
|
424
|
+
this.parseValidator,
|
|
425
|
+
this.minValidator,
|
|
426
|
+
this.maxValidator,
|
|
427
|
+
this.filterValidator,
|
|
428
|
+
this.rangeValidator,
|
|
429
|
+
this.requiredRangeValidator
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
/** Emits when the value changes (either due to user input or programmatic change). */
|
|
433
|
+
public valueChange = new EventEmitter<T[] | T | null>();
|
|
434
|
+
|
|
435
|
+
/** Emits when the disabled state has changed */
|
|
436
|
+
public disabledChange = new EventEmitter<boolean>();
|
|
437
|
+
|
|
438
|
+
get owlDateTimeInputAriaHaspopup(): boolean {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
get owlDateTimeInputAriaOwns(): string {
|
|
443
|
+
return (this.dtPicker.opened && this.dtPicker.id) || null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
get minIso8601(): string {
|
|
447
|
+
return this.min ? this.dateTimeAdapter.toIso8601(this.min) : null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
get maxIso8601(): string {
|
|
451
|
+
return this.max ? this.dateTimeAdapter.toIso8601(this.max) : null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
get owlDateTimeInputDisabled(): boolean {
|
|
455
|
+
return this.disabled;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
constructor(
|
|
459
|
+
private elmRef: ElementRef,
|
|
460
|
+
private renderer: Renderer2,
|
|
461
|
+
@Optional()
|
|
462
|
+
private dateTimeAdapter: DateTimeAdapter<T>,
|
|
463
|
+
@Optional() @Inject(OWL_DATE_TIME_FORMATS)
|
|
464
|
+
private dateTimeFormats: OwlDateTimeFormats ) {
|
|
465
|
+
if (!this.dateTimeAdapter) {
|
|
466
|
+
throw Error(
|
|
467
|
+
`OwlDateTimePicker: No provider found for DateTimePicker. You must import one of the following ` +
|
|
468
|
+
`modules at your application root: OwlNativeDateTimeModule, OwlMomentDateTimeModule, or provide a ` +
|
|
469
|
+
`custom implementation.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!this.dateTimeFormats) {
|
|
474
|
+
throw Error(
|
|
475
|
+
`OwlDateTimePicker: No provider found for OWL_DATE_TIME_FORMATS. You must import one of the following ` +
|
|
476
|
+
`modules at your application root: OwlNativeDateTimeModule, OwlMomentDateTimeModule, or provide a ` +
|
|
477
|
+
`custom implementation.`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.localeSub = this.dateTimeAdapter.localeChanges.subscribe(() => {
|
|
482
|
+
this.value = this.value;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
public ngOnInit(): void {
|
|
487
|
+
if (!this.dtPicker) {
|
|
488
|
+
throw Error(
|
|
489
|
+
`OwlDateTimePicker: the picker input doesn't have any associated owl-date-time component`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
public ngAfterContentInit(): void {
|
|
495
|
+
this.dtPickerSub = this.dtPicker.confirmSelectedChange.subscribe(
|
|
496
|
+
(selecteds: T[] | T) => {
|
|
497
|
+
if (Array.isArray(selecteds)) {
|
|
498
|
+
this.values = selecteds;
|
|
499
|
+
} else {
|
|
500
|
+
this.value = selecteds;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
this.onModelChange(selecteds);
|
|
504
|
+
this.onModelTouched();
|
|
505
|
+
this.dateTimeChange.emit({
|
|
506
|
+
source: this,
|
|
507
|
+
value: selecteds,
|
|
508
|
+
input: this.elmRef.nativeElement
|
|
509
|
+
});
|
|
510
|
+
this.dateTimeInput.emit({
|
|
511
|
+
source: this,
|
|
512
|
+
value: selecteds,
|
|
513
|
+
input: this.elmRef.nativeElement
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
public ngOnDestroy(): void {
|
|
520
|
+
this.dtPickerSub.unsubscribe();
|
|
521
|
+
this.localeSub.unsubscribe();
|
|
522
|
+
this.valueChange.complete();
|
|
523
|
+
this.disabledChange.complete();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
public writeValue(value: any): void {
|
|
527
|
+
if (this.isInSingleMode) {
|
|
528
|
+
this.value = value;
|
|
529
|
+
} else {
|
|
530
|
+
this.values = value;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
public registerOnChange(fn: any): void {
|
|
535
|
+
this.onModelChange = fn;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
public registerOnTouched(fn: any): void {
|
|
539
|
+
this.onModelTouched = fn;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
public setDisabledState(isDisabled: boolean): void {
|
|
543
|
+
this.disabled = isDisabled;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
public validate(c: AbstractControl): { [key: string]: any } {
|
|
547
|
+
return this.validator ? this.validator(c) : null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
public registerOnValidatorChange(fn: () => void): void {
|
|
551
|
+
this.validatorOnChange = fn;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Open the picker when user hold alt + DOWN_ARROW
|
|
556
|
+
* */
|
|
557
|
+
public handleKeydownOnHost(event: KeyboardEvent ): void {
|
|
558
|
+
if (event.altKey && event.keyCode === DOWN_ARROW) {
|
|
559
|
+
this.dtPicker.open();
|
|
560
|
+
event.preventDefault();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
public handleBlurOnHost(event: Event ): void {
|
|
565
|
+
this.onModelTouched();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
public handleInputOnHost(event: any ): void {
|
|
569
|
+
const value = event.target.value;
|
|
570
|
+
if (this._selectMode === 'single') {
|
|
571
|
+
this.changeInputInSingleMode(value);
|
|
572
|
+
} else if (this._selectMode === 'range') {
|
|
573
|
+
this.changeInputInRangeMode(value);
|
|
574
|
+
} else {
|
|
575
|
+
this.changeInputInRangeFromToMode(value);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
public handleChangeOnHost(event: any ): void {
|
|
580
|
+
|
|
581
|
+
let v;
|
|
582
|
+
if (this.isInSingleMode) {
|
|
583
|
+
v = this.value;
|
|
584
|
+
} else if (this.isInRangeMode) {
|
|
585
|
+
v = this.values;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
this.dateTimeChange.emit({
|
|
589
|
+
source: this,
|
|
590
|
+
value: v,
|
|
591
|
+
input: this.elmRef.nativeElement
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Set the native input property 'value'
|
|
597
|
+
*/
|
|
598
|
+
public formatNativeInputValue(): void {
|
|
599
|
+
if (this.isInSingleMode) {
|
|
600
|
+
this.renderer.setProperty(
|
|
601
|
+
this.elmRef.nativeElement,
|
|
602
|
+
'value',
|
|
603
|
+
this._value
|
|
604
|
+
? this.dateTimeAdapter.format(
|
|
605
|
+
this._value,
|
|
606
|
+
this.dtPicker.formatString
|
|
607
|
+
)
|
|
608
|
+
: ''
|
|
609
|
+
);
|
|
610
|
+
} else if (this.isInRangeMode) {
|
|
611
|
+
if (this._values && this.values.length > 0) {
|
|
612
|
+
const from = this._values[0];
|
|
613
|
+
const to = this._values[1];
|
|
614
|
+
|
|
615
|
+
const fromFormatted = from
|
|
616
|
+
? this.dateTimeAdapter.format(
|
|
617
|
+
from,
|
|
618
|
+
this.dtPicker.formatString
|
|
619
|
+
)
|
|
620
|
+
: '';
|
|
621
|
+
const toFormatted = to
|
|
622
|
+
? this.dateTimeAdapter.format(
|
|
623
|
+
to,
|
|
624
|
+
this.dtPicker.formatString
|
|
625
|
+
)
|
|
626
|
+
: '';
|
|
627
|
+
|
|
628
|
+
if (!fromFormatted && !toFormatted) {
|
|
629
|
+
this.renderer.setProperty(
|
|
630
|
+
this.elmRef.nativeElement,
|
|
631
|
+
'value',
|
|
632
|
+
null
|
|
633
|
+
);
|
|
634
|
+
} else {
|
|
635
|
+
if (this._selectMode === 'range') {
|
|
636
|
+
this.renderer.setProperty(
|
|
637
|
+
this.elmRef.nativeElement,
|
|
638
|
+
'value',
|
|
639
|
+
fromFormatted +
|
|
640
|
+
' ' +
|
|
641
|
+
this.rangeSeparator +
|
|
642
|
+
' ' +
|
|
643
|
+
toFormatted
|
|
644
|
+
);
|
|
645
|
+
} else if (this._selectMode === 'rangeFrom') {
|
|
646
|
+
this.renderer.setProperty(
|
|
647
|
+
this.elmRef.nativeElement,
|
|
648
|
+
'value',
|
|
649
|
+
fromFormatted
|
|
650
|
+
);
|
|
651
|
+
} else if (this._selectMode === 'rangeTo') {
|
|
652
|
+
this.renderer.setProperty(
|
|
653
|
+
this.elmRef.nativeElement,
|
|
654
|
+
'value',
|
|
655
|
+
toFormatted
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
this.renderer.setProperty(
|
|
661
|
+
this.elmRef.nativeElement,
|
|
662
|
+
'value',
|
|
663
|
+
''
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Register the relationship between this input and its picker component
|
|
673
|
+
*/
|
|
674
|
+
private registerDateTimePicker(picker: OwlDateTimeComponent<T>) {
|
|
675
|
+
if (picker) {
|
|
676
|
+
this.dtPicker = picker;
|
|
677
|
+
this.dtPicker.registerInput(this);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Convert a given obj to a valid date object
|
|
683
|
+
*/
|
|
684
|
+
private getValidDate(obj: any): T | null {
|
|
685
|
+
return this.dateTimeAdapter.isDateInstance(obj) &&
|
|
686
|
+
this.dateTimeAdapter.isValid(obj)
|
|
687
|
+
? obj
|
|
688
|
+
: null;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Convert a time string to a date-time string
|
|
693
|
+
* When pickerType is 'timer', the value in the picker's input is a time string.
|
|
694
|
+
* The dateTimeAdapter parse fn could not parse a time string to a Date Object.
|
|
695
|
+
* Therefore we need this fn to convert a time string to a date-time string.
|
|
696
|
+
*/
|
|
697
|
+
private convertTimeStringToDateTimeString(
|
|
698
|
+
timeString: string,
|
|
699
|
+
dateTime: T
|
|
700
|
+
): string | null {
|
|
701
|
+
if (timeString) {
|
|
702
|
+
const v = dateTime || this.dateTimeAdapter.now();
|
|
703
|
+
const dateString = this.dateTimeAdapter.format(
|
|
704
|
+
v,
|
|
705
|
+
this.dateTimeFormats.datePickerInput
|
|
706
|
+
);
|
|
707
|
+
return dateString + ' ' + timeString;
|
|
708
|
+
} else {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Handle input change in single mode
|
|
715
|
+
*/
|
|
716
|
+
private changeInputInSingleMode(inputValue: string): void {
|
|
717
|
+
let value = inputValue;
|
|
718
|
+
if (this.dtPicker.pickerType === 'timer') {
|
|
719
|
+
value = this.convertTimeStringToDateTimeString(value, this.value);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
let result = this.dateTimeAdapter.parse(
|
|
723
|
+
value,
|
|
724
|
+
this.dateTimeFormats.parseInput
|
|
725
|
+
);
|
|
726
|
+
this.lastValueValid = !result || this.dateTimeAdapter.isValid(result);
|
|
727
|
+
result = this.getValidDate(result);
|
|
728
|
+
|
|
729
|
+
// if the newValue is the same as the oldValue, we intend to not fire the valueChange event
|
|
730
|
+
// result equals to null means there is input event, but the input value is invalid
|
|
731
|
+
if (!this.isSameValue(result, this._value) || result === null) {
|
|
732
|
+
this._value = result;
|
|
733
|
+
this.valueChange.emit(result);
|
|
734
|
+
this.onModelChange(result);
|
|
735
|
+
this.dateTimeInput.emit({
|
|
736
|
+
source: this,
|
|
737
|
+
value: result,
|
|
738
|
+
input: this.elmRef.nativeElement
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Handle input change in rangeFrom or rangeTo mode
|
|
745
|
+
*/
|
|
746
|
+
private changeInputInRangeFromToMode(inputValue: string): void {
|
|
747
|
+
const originalValue =
|
|
748
|
+
this._selectMode === 'rangeFrom'
|
|
749
|
+
? this._values[0]
|
|
750
|
+
: this._values[1];
|
|
751
|
+
|
|
752
|
+
if (this.dtPicker.pickerType === 'timer') {
|
|
753
|
+
inputValue = this.convertTimeStringToDateTimeString(
|
|
754
|
+
inputValue,
|
|
755
|
+
originalValue
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
let result = this.dateTimeAdapter.parse(
|
|
760
|
+
inputValue,
|
|
761
|
+
this.dateTimeFormats.parseInput
|
|
762
|
+
);
|
|
763
|
+
this.lastValueValid = !result || this.dateTimeAdapter.isValid(result);
|
|
764
|
+
result = this.getValidDate(result);
|
|
765
|
+
|
|
766
|
+
// if the newValue is the same as the oldValue, we intend to not fire the valueChange event
|
|
767
|
+
if (
|
|
768
|
+
(this._selectMode === 'rangeFrom' &&
|
|
769
|
+
this.isSameValue(result, this._values[0]) &&
|
|
770
|
+
result) ||
|
|
771
|
+
(this._selectMode === 'rangeTo' &&
|
|
772
|
+
this.isSameValue(result, this._values[1]) &&
|
|
773
|
+
result)
|
|
774
|
+
) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
this._values =
|
|
779
|
+
this._selectMode === 'rangeFrom'
|
|
780
|
+
? [result, this._values[1]]
|
|
781
|
+
: [this._values[0], result];
|
|
782
|
+
this.valueChange.emit(this._values);
|
|
783
|
+
this.onModelChange(this._values);
|
|
784
|
+
this.dateTimeInput.emit({
|
|
785
|
+
source: this,
|
|
786
|
+
value: this._values,
|
|
787
|
+
input: this.elmRef.nativeElement
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Handle input change in range mode
|
|
793
|
+
*/
|
|
794
|
+
private changeInputInRangeMode(inputValue: string): void {
|
|
795
|
+
const selecteds = inputValue.split(this.rangeSeparator);
|
|
796
|
+
let fromString = selecteds[0];
|
|
797
|
+
let toString = selecteds[1];
|
|
798
|
+
|
|
799
|
+
if (this.dtPicker.pickerType === 'timer') {
|
|
800
|
+
fromString = this.convertTimeStringToDateTimeString(
|
|
801
|
+
fromString,
|
|
802
|
+
this.values[0]
|
|
803
|
+
);
|
|
804
|
+
toString = this.convertTimeStringToDateTimeString(
|
|
805
|
+
toString,
|
|
806
|
+
this.values[1]
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let from = this.dateTimeAdapter.parse(
|
|
811
|
+
fromString,
|
|
812
|
+
this.dateTimeFormats.parseInput
|
|
813
|
+
);
|
|
814
|
+
let to = this.dateTimeAdapter.parse(
|
|
815
|
+
toString,
|
|
816
|
+
this.dateTimeFormats.parseInput
|
|
817
|
+
);
|
|
818
|
+
this.lastValueValid =
|
|
819
|
+
(!from || this.dateTimeAdapter.isValid(from)) &&
|
|
820
|
+
(!to || this.dateTimeAdapter.isValid(to));
|
|
821
|
+
from = this.getValidDate(from);
|
|
822
|
+
to = this.getValidDate(to);
|
|
823
|
+
|
|
824
|
+
// if the newValue is the same as the oldValue, we intend to not fire the valueChange event
|
|
825
|
+
if (
|
|
826
|
+
!this.isSameValue(from, this._values[0]) ||
|
|
827
|
+
!this.isSameValue(to, this._values[1]) ||
|
|
828
|
+
(from === null && to === null)
|
|
829
|
+
) {
|
|
830
|
+
this._values = [from, to];
|
|
831
|
+
this.valueChange.emit(this._values);
|
|
832
|
+
this.onModelChange(this._values);
|
|
833
|
+
this.dateTimeInput.emit({
|
|
834
|
+
source: this,
|
|
835
|
+
value: this._values,
|
|
836
|
+
input: this.elmRef.nativeElement
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Check if the two value is the same
|
|
843
|
+
*/
|
|
844
|
+
private isSameValue(first: T | null, second: T | null): boolean {
|
|
845
|
+
if (first && second) {
|
|
846
|
+
return this.dateTimeAdapter.compare(first, second) === 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return first === second;
|
|
850
|
+
}
|
|
851
|
+
}
|