@vaadin/date-time-picker 24.6.0-alpha7 → 24.6.0-alpha9
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/package.json +14 -12
- package/src/vaadin-date-time-picker-mixin.d.ts +167 -0
- package/src/vaadin-date-time-picker-mixin.js +923 -0
- package/src/vaadin-date-time-picker.d.ts +4 -141
- package/src/vaadin-date-time-picker.js +5 -921
- package/src/vaadin-lit-date-time-picker.d.ts +1 -0
- package/src/vaadin-lit-date-time-picker.js +86 -0
- package/theme/lumo/vaadin-date-time-picker-styles.d.ts +1 -2
- package/theme/lumo/vaadin-date-time-picker-styles.js +0 -2
- package/theme/lumo/vaadin-date-time-picker.d.ts +2 -0
- package/theme/lumo/vaadin-date-time-picker.js +2 -0
- package/theme/lumo/vaadin-lit-date-time-picker.d.ts +4 -0
- package/theme/lumo/vaadin-lit-date-time-picker.js +4 -0
- package/theme/material/vaadin-date-time-picker-styles.d.ts +1 -2
- package/theme/material/vaadin-date-time-picker-styles.js +0 -2
- package/theme/material/vaadin-date-time-picker.d.ts +2 -0
- package/theme/material/vaadin-date-time-picker.js +2 -0
- package/theme/material/vaadin-lit-date-time-picker.d.ts +4 -0
- package/theme/material/vaadin-lit-date-time-picker.js +4 -0
- package/vaadin-lit-date-time-picker.d.ts +1 -0
- package/vaadin-lit-date-time-picker.js +2 -0
- package/web-types.json +31 -9
- package/web-types.lit.json +15 -8
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2019 - 2024 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
|
|
7
|
+
import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
|
|
8
|
+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
9
|
+
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
|
|
10
|
+
import {
|
|
11
|
+
dateEquals,
|
|
12
|
+
formatUTCISODate,
|
|
13
|
+
normalizeUTCDate,
|
|
14
|
+
parseUTCDate,
|
|
15
|
+
} from '@vaadin/date-picker/src/vaadin-date-picker-helper.js';
|
|
16
|
+
import { datePickerI18nDefaults } from '@vaadin/date-picker/src/vaadin-date-picker-mixin.js';
|
|
17
|
+
import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
|
|
18
|
+
import { formatISOTime, parseISOTime, validateTime } from '@vaadin/time-picker/src/vaadin-time-picker-helper.js';
|
|
19
|
+
import { timePickerI18nDefaults } from '@vaadin/time-picker/src/vaadin-time-picker-mixin.js';
|
|
20
|
+
|
|
21
|
+
const datePickerI18nProps = Object.keys(datePickerI18nDefaults);
|
|
22
|
+
const timePickerI18nProps = Object.keys(timePickerI18nDefaults);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A controller to initialize slotted picker.
|
|
26
|
+
*
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
class PickerSlotController extends SlotController {
|
|
30
|
+
constructor(host, type) {
|
|
31
|
+
super(host, `${type}-picker`, `vaadin-${type}-picker`, {
|
|
32
|
+
initializer: (picker, host) => {
|
|
33
|
+
// Ensure initial value-changed is fired on the slotted pickers
|
|
34
|
+
// synchronously in Lit version to avoid overriding host value.
|
|
35
|
+
if (picker.performUpdate) {
|
|
36
|
+
picker.performUpdate();
|
|
37
|
+
}
|
|
38
|
+
const prop = `__${type}Picker`;
|
|
39
|
+
host[prop] = picker;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A mixin providing common date-time-picker functionality.
|
|
47
|
+
*
|
|
48
|
+
* @polymerMixin
|
|
49
|
+
* @mixes DisabledMixin
|
|
50
|
+
* @mixes FieldMixin
|
|
51
|
+
* @mixes FocusMixin
|
|
52
|
+
*/
|
|
53
|
+
export const DateTimePickerMixin = (superClass) =>
|
|
54
|
+
class DateTimePickerMixinClass extends FieldMixin(FocusMixin(DisabledMixin(superClass))) {
|
|
55
|
+
static get properties() {
|
|
56
|
+
return {
|
|
57
|
+
/**
|
|
58
|
+
* The name of the control, which is submitted with the form data.
|
|
59
|
+
*/
|
|
60
|
+
name: {
|
|
61
|
+
type: String,
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The value for this element.
|
|
66
|
+
*
|
|
67
|
+
* Supported date time format is based on ISO 8601 (without a time zone designator):
|
|
68
|
+
* - Minute precision `"YYYY-MM-DDThh:mm"` (default)
|
|
69
|
+
* - Second precision `"YYYY-MM-DDThh:mm:ss"`
|
|
70
|
+
* - Millisecond precision `"YYYY-MM-DDThh:mm:ss.fff"`
|
|
71
|
+
* @type {string}
|
|
72
|
+
*/
|
|
73
|
+
value: {
|
|
74
|
+
type: String,
|
|
75
|
+
notify: true,
|
|
76
|
+
value: '',
|
|
77
|
+
observer: '__valueChanged',
|
|
78
|
+
sync: true,
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The earliest allowed value (date and time) that can be selected. All earlier values will be disabled.
|
|
83
|
+
*
|
|
84
|
+
* Supported date time format is based on ISO 8601 (without a time zone designator):
|
|
85
|
+
* - Minute precision `"YYYY-MM-DDThh:mm"`
|
|
86
|
+
* - Second precision `"YYYY-MM-DDThh:mm:ss"`
|
|
87
|
+
* - Millisecond precision `"YYYY-MM-DDThh:mm:ss.fff"`
|
|
88
|
+
*
|
|
89
|
+
* @type {string | undefined}
|
|
90
|
+
*/
|
|
91
|
+
min: {
|
|
92
|
+
type: String,
|
|
93
|
+
observer: '__minChanged',
|
|
94
|
+
sync: true,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The latest value (date and time) that can be selected. All later values will be disabled.
|
|
99
|
+
*
|
|
100
|
+
* Supported date time format is based on ISO 8601 (without a time zone designator):
|
|
101
|
+
* - Minute precision `"YYYY-MM-DDThh:mm"`
|
|
102
|
+
* - Second precision `"YYYY-MM-DDThh:mm:ss"`
|
|
103
|
+
* - Millisecond precision `"YYYY-MM-DDThh:mm:ss.fff"`
|
|
104
|
+
*
|
|
105
|
+
* @type {string | undefined}
|
|
106
|
+
*/
|
|
107
|
+
max: {
|
|
108
|
+
type: String,
|
|
109
|
+
observer: '__maxChanged',
|
|
110
|
+
sync: true,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The earliest value that can be selected. All earlier values will be disabled.
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
__minDateTime: {
|
|
118
|
+
type: Date,
|
|
119
|
+
value: '',
|
|
120
|
+
sync: true,
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The latest value that can be selected. All later values will be disabled.
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
__maxDateTime: {
|
|
128
|
+
type: Date,
|
|
129
|
+
value: '',
|
|
130
|
+
sync: true,
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A placeholder string for the date field.
|
|
135
|
+
* @attr {string} date-placeholder
|
|
136
|
+
*/
|
|
137
|
+
datePlaceholder: {
|
|
138
|
+
type: String,
|
|
139
|
+
sync: true,
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A placeholder string for the time field.
|
|
144
|
+
* @attr {string} time-placeholder
|
|
145
|
+
*/
|
|
146
|
+
timePlaceholder: {
|
|
147
|
+
type: String,
|
|
148
|
+
sync: true,
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Defines the time interval (in seconds) between the items displayed
|
|
153
|
+
* in the time selection box. The default is 1 hour (i.e. `3600`).
|
|
154
|
+
*
|
|
155
|
+
* It also configures the precision of the time part of the value string. By default
|
|
156
|
+
* the component formats time values as `hh:mm` but setting a step value
|
|
157
|
+
* lower than one minute or one second, format resolution changes to
|
|
158
|
+
* `hh:mm:ss` and `hh:mm:ss.fff` respectively.
|
|
159
|
+
*
|
|
160
|
+
* Unit must be set in seconds, and for correctly configuring intervals
|
|
161
|
+
* in the dropdown, it need to evenly divide a day.
|
|
162
|
+
*
|
|
163
|
+
* Note: it is possible to define step that is dividing an hour in inexact
|
|
164
|
+
* fragments (i.e. 5760 seconds which equals 1 hour 36 minutes), but it is
|
|
165
|
+
* not recommended to use it for better UX.
|
|
166
|
+
*/
|
|
167
|
+
step: {
|
|
168
|
+
type: Number,
|
|
169
|
+
sync: true,
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Date which should be visible in the date picker overlay when there is no value selected.
|
|
174
|
+
*
|
|
175
|
+
* The same date formats as for the `value` property are supported but without the time part.
|
|
176
|
+
* @attr {string} initial-position
|
|
177
|
+
*/
|
|
178
|
+
initialPosition: {
|
|
179
|
+
type: String,
|
|
180
|
+
sync: true,
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Set true to display ISO-8601 week numbers in the calendar. Notice that
|
|
185
|
+
* displaying week numbers is only supported when `i18n.firstDayOfWeek`
|
|
186
|
+
* is 1 (Monday).
|
|
187
|
+
* @attr {boolean} show-week-numbers
|
|
188
|
+
*/
|
|
189
|
+
showWeekNumbers: {
|
|
190
|
+
type: Boolean,
|
|
191
|
+
value: false,
|
|
192
|
+
sync: true,
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Set to true to prevent the overlays from opening automatically.
|
|
197
|
+
* @attr {boolean} auto-open-disabled
|
|
198
|
+
*/
|
|
199
|
+
autoOpenDisabled: {
|
|
200
|
+
type: Boolean,
|
|
201
|
+
sync: true,
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Set to true to make this element read-only.
|
|
206
|
+
* @type {boolean}
|
|
207
|
+
*/
|
|
208
|
+
readonly: {
|
|
209
|
+
type: Boolean,
|
|
210
|
+
value: false,
|
|
211
|
+
reflectToAttribute: true,
|
|
212
|
+
sync: true,
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Specify that this control should have input focus when the page loads.
|
|
217
|
+
* @type {boolean}
|
|
218
|
+
*/
|
|
219
|
+
autofocus: {
|
|
220
|
+
type: Boolean,
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The current selected date time.
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
__selectedDateTime: {
|
|
228
|
+
type: Date,
|
|
229
|
+
sync: true,
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The object used to localize this component.
|
|
234
|
+
* To change the default localization, replace the entire
|
|
235
|
+
* `i18n` object or just the properties you want to modify.
|
|
236
|
+
*
|
|
237
|
+
* The object is a combination of the i18n properties supported by
|
|
238
|
+
* [`<vaadin-date-picker>`](#/elements/vaadin-date-picker) and
|
|
239
|
+
* [`<vaadin-time-picker>`](#/elements/vaadin-time-picker).
|
|
240
|
+
* @type {!DateTimePickerI18n}
|
|
241
|
+
*/
|
|
242
|
+
i18n: {
|
|
243
|
+
type: Object,
|
|
244
|
+
sync: true,
|
|
245
|
+
value: () => ({ ...datePickerI18nDefaults, ...timePickerI18nDefaults }),
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A space-delimited list of CSS class names to set on the overlay elements
|
|
250
|
+
* of the internal components controlled by the `<vaadin-date-time-picker>`:
|
|
251
|
+
*
|
|
252
|
+
* - [`<vaadin-date-picker>`](#/elements/vaadin-date-picker#property-overlayClass)
|
|
253
|
+
* - [`<vaadin-time-picker>`](#/elements/vaadin-time-picker#property-overlayClass)
|
|
254
|
+
*
|
|
255
|
+
* @attr {string} overlay-class
|
|
256
|
+
*/
|
|
257
|
+
overlayClass: {
|
|
258
|
+
type: String,
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The current slotted date picker.
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
__datePicker: {
|
|
266
|
+
type: Object,
|
|
267
|
+
sync: true,
|
|
268
|
+
observer: '__datePickerChanged',
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The current slotted time picker.
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
__timePicker: {
|
|
276
|
+
type: Object,
|
|
277
|
+
sync: true,
|
|
278
|
+
observer: '__timePickerChanged',
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static get observers() {
|
|
284
|
+
return [
|
|
285
|
+
'__selectedDateTimeChanged(__selectedDateTime)',
|
|
286
|
+
'__datePlaceholderChanged(datePlaceholder, __datePicker)',
|
|
287
|
+
'__timePlaceholderChanged(timePlaceholder, __timePicker)',
|
|
288
|
+
'__stepChanged(step, __timePicker)',
|
|
289
|
+
'__initialPositionChanged(initialPosition, __datePicker)',
|
|
290
|
+
'__showWeekNumbersChanged(showWeekNumbers, __datePicker)',
|
|
291
|
+
'__requiredChanged(required, __datePicker, __timePicker)',
|
|
292
|
+
'__invalidChanged(invalid, __datePicker, __timePicker)',
|
|
293
|
+
'__disabledChanged(disabled, __datePicker, __timePicker)',
|
|
294
|
+
'__readonlyChanged(readonly, __datePicker, __timePicker)',
|
|
295
|
+
'__i18nChanged(i18n, __datePicker, __timePicker)',
|
|
296
|
+
'__autoOpenDisabledChanged(autoOpenDisabled, __datePicker, __timePicker)',
|
|
297
|
+
'__themeChanged(_theme, __datePicker, __timePicker)',
|
|
298
|
+
'__overlayClassChanged(overlayClass, __datePicker, __timePicker)',
|
|
299
|
+
'__pickersChanged(__datePicker, __timePicker)',
|
|
300
|
+
'__labelOrAccessibleNameChanged(label, accessibleName, i18n, __datePicker, __timePicker)',
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
constructor() {
|
|
305
|
+
super();
|
|
306
|
+
// Default value for "min" and "max" properties of vaadin-date-picker (for removing constraint)
|
|
307
|
+
this.__defaultDateMinMaxValue = undefined;
|
|
308
|
+
// Default value for "min" property of vaadin-time-picker (for removing constraint)
|
|
309
|
+
this.__defaultTimeMinValue = '00:00:00.000';
|
|
310
|
+
// Default value for "max" property of vaadin-time-picker (for removing constraint)
|
|
311
|
+
this.__defaultTimeMaxValue = '23:59:59.999';
|
|
312
|
+
|
|
313
|
+
this.__changeEventHandler = this.__changeEventHandler.bind(this);
|
|
314
|
+
this.__valueChangedEventHandler = this.__valueChangedEventHandler.bind(this);
|
|
315
|
+
this.__openedChangedEventHandler = this.__openedChangedEventHandler.bind(this);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** @private */
|
|
319
|
+
get __pickers() {
|
|
320
|
+
return [this.__datePicker, this.__timePicker];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** @private */
|
|
324
|
+
get __formattedValue() {
|
|
325
|
+
const values = this.__pickers.map((picker) => picker.value);
|
|
326
|
+
return values.every(Boolean) ? values.join('T') : '';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** @protected */
|
|
330
|
+
ready() {
|
|
331
|
+
super.ready();
|
|
332
|
+
|
|
333
|
+
this._datePickerController = new PickerSlotController(this, 'date');
|
|
334
|
+
this.addController(this._datePickerController);
|
|
335
|
+
|
|
336
|
+
this._timePickerController = new PickerSlotController(this, 'time');
|
|
337
|
+
this.addController(this._timePickerController);
|
|
338
|
+
|
|
339
|
+
if (this.autofocus && !this.disabled) {
|
|
340
|
+
window.requestAnimationFrame(() => this.focus());
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.setAttribute('role', 'group');
|
|
344
|
+
|
|
345
|
+
this._tooltipController = new TooltipController(this);
|
|
346
|
+
this.addController(this._tooltipController);
|
|
347
|
+
this._tooltipController.setPosition('top');
|
|
348
|
+
this._tooltipController.setShouldShow((target) => {
|
|
349
|
+
return target.__datePicker && !target.__datePicker.opened && target.__timePicker && !target.__timePicker.opened;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
this.ariaTarget = this;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
focus() {
|
|
356
|
+
if (this.__datePicker) {
|
|
357
|
+
this.__datePicker.focus();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Override method inherited from `FocusMixin` to validate on blur.
|
|
363
|
+
* @param {boolean} focused
|
|
364
|
+
* @protected
|
|
365
|
+
* @override
|
|
366
|
+
*/
|
|
367
|
+
_setFocused(focused) {
|
|
368
|
+
super._setFocused(focused);
|
|
369
|
+
|
|
370
|
+
// Do not validate when focusout is caused by document
|
|
371
|
+
// losing focus, which happens on browser tab switch.
|
|
372
|
+
if (!focused && document.hasFocus()) {
|
|
373
|
+
this._requestValidation();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Override method inherited from `FocusMixin` to not remove focused
|
|
379
|
+
* state when focus moves between pickers or to the overlay.
|
|
380
|
+
* @param {FocusEvent} event
|
|
381
|
+
* @return {boolean}
|
|
382
|
+
* @protected
|
|
383
|
+
* @override
|
|
384
|
+
*/
|
|
385
|
+
_shouldRemoveFocus(event) {
|
|
386
|
+
const target = event.relatedTarget;
|
|
387
|
+
const overlayContent = this.__datePicker._overlayContent;
|
|
388
|
+
|
|
389
|
+
if (
|
|
390
|
+
this.__datePicker.contains(target) ||
|
|
391
|
+
this.__timePicker.contains(target) ||
|
|
392
|
+
(overlayContent && overlayContent.contains(target))
|
|
393
|
+
) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** @private */
|
|
401
|
+
__syncI18n(target, source, props = Object.keys(source.i18n)) {
|
|
402
|
+
const i18n = { ...target.i18n };
|
|
403
|
+
props.forEach((prop) => {
|
|
404
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
405
|
+
if (source.i18n && source.i18n.hasOwnProperty(prop)) {
|
|
406
|
+
i18n[prop] = source.i18n[prop];
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
target.i18n = i18n;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** @private */
|
|
413
|
+
__changeEventHandler(event) {
|
|
414
|
+
event.stopPropagation();
|
|
415
|
+
|
|
416
|
+
if (this.__dispatchChangeForValue === this.value) {
|
|
417
|
+
this._requestValidation();
|
|
418
|
+
this.__dispatchChange();
|
|
419
|
+
}
|
|
420
|
+
this.__dispatchChangeForValue = undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** @private */
|
|
424
|
+
__openedChangedEventHandler() {
|
|
425
|
+
const opened = this.__datePicker.opened || this.__timePicker.opened;
|
|
426
|
+
this.style.pointerEvents = opened ? 'auto' : '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** @private */
|
|
430
|
+
__addInputListeners(node) {
|
|
431
|
+
node.addEventListener('change', this.__changeEventHandler);
|
|
432
|
+
node.addEventListener('value-changed', this.__valueChangedEventHandler);
|
|
433
|
+
node.addEventListener('opened-changed', this.__openedChangedEventHandler);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** @private */
|
|
437
|
+
__removeInputListeners(node) {
|
|
438
|
+
node.removeEventListener('change', this.__changeEventHandler);
|
|
439
|
+
node.removeEventListener('value-changed', this.__valueChangedEventHandler);
|
|
440
|
+
node.removeEventListener('opened-changed', this.__openedChangedEventHandler);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** @private */
|
|
444
|
+
__isDefaultPicker(picker, type) {
|
|
445
|
+
const controller = this[`_${type}PickerController`];
|
|
446
|
+
return controller && picker === controller.defaultNode;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** @private */
|
|
450
|
+
__datePickerChanged(newDatePicker, existingDatePicker) {
|
|
451
|
+
if (!newDatePicker) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (existingDatePicker) {
|
|
455
|
+
// Remove an existing date picker
|
|
456
|
+
this.__removeInputListeners(existingDatePicker);
|
|
457
|
+
existingDatePicker.remove();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.__addInputListeners(newDatePicker);
|
|
461
|
+
|
|
462
|
+
if (!this.__isDefaultPicker(newDatePicker, 'date')) {
|
|
463
|
+
// Synchronize properties from slotted date picker
|
|
464
|
+
this.datePlaceholder = newDatePicker.placeholder;
|
|
465
|
+
this.initialPosition = newDatePicker.initialPosition;
|
|
466
|
+
this.showWeekNumbers = newDatePicker.showWeekNumbers;
|
|
467
|
+
this.__syncI18n(this, newDatePicker, datePickerI18nProps);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Min and max are always synchronized from date time picker (host) to inner fields because time picker
|
|
471
|
+
// min and max need to be dynamically set depending on currently selected date instead of simple propagation
|
|
472
|
+
newDatePicker.min = this.__formatDateISO(this.__minDateTime, this.__defaultDateMinMaxValue);
|
|
473
|
+
newDatePicker.max = this.__formatDateISO(this.__maxDateTime, this.__defaultDateMinMaxValue);
|
|
474
|
+
|
|
475
|
+
// Disable default internal validation for the component
|
|
476
|
+
newDatePicker.manualValidation = true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** @private */
|
|
480
|
+
__timePickerChanged(newTimePicker, existingTimePicker) {
|
|
481
|
+
if (!newTimePicker) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (existingTimePicker) {
|
|
485
|
+
// Remove an existing time picker
|
|
486
|
+
this.__removeInputListeners(existingTimePicker);
|
|
487
|
+
existingTimePicker.remove();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.__addInputListeners(newTimePicker);
|
|
491
|
+
|
|
492
|
+
if (!this.__isDefaultPicker(newTimePicker, 'time')) {
|
|
493
|
+
// Synchronize properties from slotted time picker
|
|
494
|
+
this.timePlaceholder = newTimePicker.placeholder;
|
|
495
|
+
this.step = newTimePicker.step;
|
|
496
|
+
this.__syncI18n(this, newTimePicker, timePickerI18nProps);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Min and max are always synchronized from parent to slotted because time picker min and max
|
|
500
|
+
// need to be dynamically set depending on currently selected date instead of simple propagation
|
|
501
|
+
this.__updateTimePickerMinMax();
|
|
502
|
+
|
|
503
|
+
// Disable default internal validation for the component
|
|
504
|
+
newTimePicker.manualValidation = true;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** @private */
|
|
508
|
+
__updateTimePickerMinMax() {
|
|
509
|
+
if (this.__timePicker && this.__datePicker) {
|
|
510
|
+
const selectedDate = this.__parseDate(this.__datePicker.value);
|
|
511
|
+
const isMinMaxSameDay = dateEquals(this.__minDateTime, this.__maxDateTime, normalizeUTCDate);
|
|
512
|
+
|
|
513
|
+
if ((this.__minDateTime && dateEquals(selectedDate, this.__minDateTime, normalizeUTCDate)) || isMinMaxSameDay) {
|
|
514
|
+
this.__timePicker.min = this.__dateToIsoTimeString(this.__minDateTime);
|
|
515
|
+
} else {
|
|
516
|
+
this.__timePicker.min = this.__defaultTimeMinValue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if ((this.__maxDateTime && dateEquals(selectedDate, this.__maxDateTime, normalizeUTCDate)) || isMinMaxSameDay) {
|
|
520
|
+
this.__timePicker.max = this.__dateToIsoTimeString(this.__maxDateTime);
|
|
521
|
+
} else {
|
|
522
|
+
this.__timePicker.max = this.__defaultTimeMaxValue;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** @private */
|
|
528
|
+
__i18nChanged(_i18n, datePicker, timePicker) {
|
|
529
|
+
if (datePicker) {
|
|
530
|
+
this.__syncI18n(datePicker, this, datePickerI18nProps);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (timePicker) {
|
|
534
|
+
this.__syncI18n(timePicker, this, timePickerI18nProps);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** @private */
|
|
539
|
+
__labelOrAccessibleNameChanged(label, accessibleName, i18n, datePicker, timePicker) {
|
|
540
|
+
const name = accessibleName || label || '';
|
|
541
|
+
|
|
542
|
+
if (datePicker) {
|
|
543
|
+
datePicker.accessibleName = `${name} ${i18n.dateLabel || ''}`.trim();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (timePicker) {
|
|
547
|
+
timePicker.accessibleName = `${name} ${i18n.timeLabel || ''}`.trim();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** @private */
|
|
552
|
+
__datePlaceholderChanged(datePlaceholder, datePicker) {
|
|
553
|
+
if (datePicker) {
|
|
554
|
+
datePicker.placeholder = datePlaceholder;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** @private */
|
|
559
|
+
__timePlaceholderChanged(timePlaceholder, timePicker) {
|
|
560
|
+
if (timePicker) {
|
|
561
|
+
timePicker.placeholder = timePlaceholder;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** @private */
|
|
566
|
+
__stepChanged(step, timePicker) {
|
|
567
|
+
if (timePicker && timePicker.step !== step) {
|
|
568
|
+
timePicker.step = step;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** @private */
|
|
573
|
+
__initialPositionChanged(initialPosition, datePicker) {
|
|
574
|
+
if (datePicker) {
|
|
575
|
+
datePicker.initialPosition = initialPosition;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** @private */
|
|
580
|
+
__showWeekNumbersChanged(showWeekNumbers, datePicker) {
|
|
581
|
+
if (datePicker) {
|
|
582
|
+
datePicker.showWeekNumbers = showWeekNumbers;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** @private */
|
|
587
|
+
__invalidChanged(invalid, datePicker, timePicker) {
|
|
588
|
+
if (datePicker) {
|
|
589
|
+
datePicker.invalid = invalid;
|
|
590
|
+
}
|
|
591
|
+
if (timePicker) {
|
|
592
|
+
timePicker.invalid = invalid;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** @private */
|
|
597
|
+
__requiredChanged(required, datePicker, timePicker) {
|
|
598
|
+
if (datePicker) {
|
|
599
|
+
datePicker.required = required;
|
|
600
|
+
}
|
|
601
|
+
if (timePicker) {
|
|
602
|
+
timePicker.required = required;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (this.__oldRequired && !required) {
|
|
606
|
+
this._requestValidation();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
this.__oldRequired = required;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** @private */
|
|
613
|
+
__disabledChanged(disabled, datePicker, timePicker) {
|
|
614
|
+
if (datePicker) {
|
|
615
|
+
datePicker.disabled = disabled;
|
|
616
|
+
}
|
|
617
|
+
if (timePicker) {
|
|
618
|
+
timePicker.disabled = disabled;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** @private */
|
|
623
|
+
__readonlyChanged(readonly, datePicker, timePicker) {
|
|
624
|
+
if (datePicker) {
|
|
625
|
+
datePicker.readonly = readonly;
|
|
626
|
+
}
|
|
627
|
+
if (timePicker) {
|
|
628
|
+
timePicker.readonly = readonly;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* String (ISO date) to Date object
|
|
634
|
+
* @param {string} str e.g. 'yyyy-mm-dd'
|
|
635
|
+
* @return {Date | undefined}
|
|
636
|
+
* @private
|
|
637
|
+
*/
|
|
638
|
+
__parseDate(str) {
|
|
639
|
+
return parseUTCDate(str);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Date object to string (ISO date)
|
|
644
|
+
* @param {Date} date
|
|
645
|
+
* @param {string} defaultValue
|
|
646
|
+
* @return {string} e.g. 'yyyy-mm-dd' (or defaultValue when date is falsy)
|
|
647
|
+
* @private
|
|
648
|
+
*/
|
|
649
|
+
__formatDateISO(date, defaultValue) {
|
|
650
|
+
if (!date) {
|
|
651
|
+
return defaultValue;
|
|
652
|
+
}
|
|
653
|
+
return formatUTCISODate(date);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* String (ISO date time) to Date object
|
|
658
|
+
* @param {string} str e.g. 'yyyy-mm-ddThh:mm', 'yyyy-mm-ddThh:mm:ss', 'yyyy-mm-ddThh:mm:ss.fff'
|
|
659
|
+
* @return {Date | undefined}
|
|
660
|
+
* @private
|
|
661
|
+
*/
|
|
662
|
+
__parseDateTime(str) {
|
|
663
|
+
const [dateValue, timeValue] = str.split('T');
|
|
664
|
+
/* c8 ignore next 3 */
|
|
665
|
+
if (!(dateValue && timeValue)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** @type {Date} */
|
|
670
|
+
const date = this.__parseDate(dateValue);
|
|
671
|
+
if (!date) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const time = parseISOTime(timeValue);
|
|
676
|
+
if (!time) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
date.setUTCHours(parseInt(time.hours));
|
|
681
|
+
date.setUTCMinutes(parseInt(time.minutes || 0));
|
|
682
|
+
date.setUTCSeconds(parseInt(time.seconds || 0));
|
|
683
|
+
date.setUTCMilliseconds(parseInt(time.milliseconds || 0));
|
|
684
|
+
|
|
685
|
+
return date;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Date object to string (ISO date time)
|
|
690
|
+
* @param {Date} date
|
|
691
|
+
* @return {string} e.g. 'yyyy-mm-ddThh:mm', 'yyyy-mm-ddThh:mm:ss', 'yyyy-mm-ddThh:mm:ss.fff'
|
|
692
|
+
* (depending on precision defined by "step" property)
|
|
693
|
+
* @private
|
|
694
|
+
*/
|
|
695
|
+
__formatDateTime(date) {
|
|
696
|
+
if (!date) {
|
|
697
|
+
return '';
|
|
698
|
+
}
|
|
699
|
+
const dateValue = this.__formatDateISO(date, '');
|
|
700
|
+
const timeValue = this.__dateToIsoTimeString(date);
|
|
701
|
+
return `${dateValue}T${timeValue}`;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Date object to string (ISO time)
|
|
706
|
+
* @param {Date} date
|
|
707
|
+
* @return {string} e.g. 'hh:mm', 'hh:mm:ss', 'hh:mm:ss.fff' (depending on precision defined by "step" property)
|
|
708
|
+
* @private
|
|
709
|
+
*/
|
|
710
|
+
__dateToIsoTimeString(date) {
|
|
711
|
+
return formatISOTime(
|
|
712
|
+
validateTime(
|
|
713
|
+
{
|
|
714
|
+
hours: date.getUTCHours(),
|
|
715
|
+
minutes: date.getUTCMinutes(),
|
|
716
|
+
seconds: date.getUTCSeconds(),
|
|
717
|
+
milliseconds: date.getUTCMilliseconds(),
|
|
718
|
+
},
|
|
719
|
+
this.step,
|
|
720
|
+
),
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Returns true if the current input value satisfies all constraints (if any)
|
|
726
|
+
*
|
|
727
|
+
* You can override the `checkValidity` method for custom validations.
|
|
728
|
+
* @return {boolean}
|
|
729
|
+
*/
|
|
730
|
+
checkValidity() {
|
|
731
|
+
const hasInvalidPickers = this.__pickers.some((picker) => !picker.checkValidity());
|
|
732
|
+
const hasEmptyRequiredPickers = this.required && this.__pickers.some((picker) => !picker.value);
|
|
733
|
+
return !hasInvalidPickers && !hasEmptyRequiredPickers;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @param {Date} date1
|
|
738
|
+
* @param {Date} date2
|
|
739
|
+
* @return {boolean}
|
|
740
|
+
* @private
|
|
741
|
+
*/
|
|
742
|
+
__dateTimeEquals(date1, date2) {
|
|
743
|
+
if (!dateEquals(date1, date2, normalizeUTCDate)) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
return (
|
|
747
|
+
date1.getUTCHours() === date2.getUTCHours() &&
|
|
748
|
+
date1.getUTCMinutes() === date2.getUTCMinutes() &&
|
|
749
|
+
date1.getUTCSeconds() === date2.getUTCSeconds() &&
|
|
750
|
+
date1.getUTCMilliseconds() === date2.getUTCMilliseconds()
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** @private */
|
|
755
|
+
__handleDateTimeChange(property, parsedProperty, value, oldValue) {
|
|
756
|
+
if (!value) {
|
|
757
|
+
this[property] = '';
|
|
758
|
+
this[parsedProperty] = '';
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const dateTime = this.__parseDateTime(value);
|
|
763
|
+
if (!dateTime) {
|
|
764
|
+
// Invalid date, revert to old value
|
|
765
|
+
this[property] = oldValue;
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (!this.__dateTimeEquals(this[parsedProperty], dateTime)) {
|
|
769
|
+
this[parsedProperty] = dateTime;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** @private */
|
|
774
|
+
__valueChanged(value, oldValue) {
|
|
775
|
+
this.__handleDateTimeChange('value', '__selectedDateTime', value, oldValue);
|
|
776
|
+
|
|
777
|
+
if (oldValue !== undefined) {
|
|
778
|
+
this.__dispatchChangeForValue = value;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
this.toggleAttribute('has-value', !!value);
|
|
782
|
+
this.__updateTimePickerMinMax();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** @private */
|
|
786
|
+
__dispatchChange() {
|
|
787
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** @private */
|
|
791
|
+
__minChanged(value, oldValue) {
|
|
792
|
+
this.__handleDateTimeChange('min', '__minDateTime', value, oldValue);
|
|
793
|
+
if (this.__datePicker) {
|
|
794
|
+
this.__datePicker.min = this.__formatDateISO(this.__minDateTime, this.__defaultDateMinMaxValue);
|
|
795
|
+
}
|
|
796
|
+
this.__updateTimePickerMinMax();
|
|
797
|
+
|
|
798
|
+
if (this.__datePicker && this.__timePicker && this.value) {
|
|
799
|
+
this._requestValidation();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** @private */
|
|
804
|
+
__maxChanged(value, oldValue) {
|
|
805
|
+
this.__handleDateTimeChange('max', '__maxDateTime', value, oldValue);
|
|
806
|
+
if (this.__datePicker) {
|
|
807
|
+
this.__datePicker.max = this.__formatDateISO(this.__maxDateTime, this.__defaultDateMinMaxValue);
|
|
808
|
+
}
|
|
809
|
+
this.__updateTimePickerMinMax();
|
|
810
|
+
|
|
811
|
+
if (this.__datePicker && this.__timePicker && this.value) {
|
|
812
|
+
this._requestValidation();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** @private */
|
|
817
|
+
__selectedDateTimeChanged(selectedDateTime) {
|
|
818
|
+
const formattedValue = this.__formatDateTime(selectedDateTime);
|
|
819
|
+
if (this.value !== formattedValue) {
|
|
820
|
+
this.value = formattedValue;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Setting the date/time picker value below triggers validation of the components.
|
|
824
|
+
// If the inputs are slotted (e.g. when using the Java API) and have an initial value this can
|
|
825
|
+
// happen before date picker ready() which would throw an error when date picker is trying to read
|
|
826
|
+
// `this.$.input` (as a result of value change triggered by setting the value).
|
|
827
|
+
// Workaround the problem by setting custom field value only if date picker is ready.
|
|
828
|
+
const isDatePickerReady = Boolean(this.__datePicker && this.__datePicker.$);
|
|
829
|
+
if (isDatePickerReady && !this.__ignoreInputValueChange) {
|
|
830
|
+
// Ignore value changes until both inputs have a value updated
|
|
831
|
+
// TODO: This logic clears both fields if one of them is cleared :(
|
|
832
|
+
this.__ignoreInputValueChange = true;
|
|
833
|
+
const [dateValue, timeValue] = this.value.split('T');
|
|
834
|
+
this.__datePicker.value = dateValue || '';
|
|
835
|
+
this.__timePicker.value = timeValue || '';
|
|
836
|
+
this.__ignoreInputValueChange = false;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** @private */
|
|
841
|
+
__valueChangedEventHandler() {
|
|
842
|
+
if (this.__ignoreInputValueChange) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
this.__ignoreInputValueChange = true;
|
|
847
|
+
this.__updateTimePickerMinMax();
|
|
848
|
+
this.value = this.__formattedValue;
|
|
849
|
+
this.__ignoreInputValueChange = false;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** @private */
|
|
853
|
+
__autoOpenDisabledChanged(autoOpenDisabled, datePicker, timePicker) {
|
|
854
|
+
if (datePicker) {
|
|
855
|
+
datePicker.autoOpenDisabled = autoOpenDisabled;
|
|
856
|
+
}
|
|
857
|
+
if (timePicker) {
|
|
858
|
+
timePicker.autoOpenDisabled = autoOpenDisabled;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** @private */
|
|
863
|
+
__themeChanged(theme, datePicker, timePicker) {
|
|
864
|
+
if (!datePicker || !timePicker) {
|
|
865
|
+
// Both pickers are not ready yet
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
[datePicker, timePicker].forEach((picker) => {
|
|
870
|
+
if (theme) {
|
|
871
|
+
picker.setAttribute('theme', theme);
|
|
872
|
+
} else {
|
|
873
|
+
picker.removeAttribute('theme');
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** @private */
|
|
879
|
+
__overlayClassChanged(overlayClass, datePicker, timePicker) {
|
|
880
|
+
if (!datePicker || !timePicker) {
|
|
881
|
+
// Both pickers are not ready yet
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
datePicker.overlayClass = overlayClass;
|
|
886
|
+
timePicker.overlayClass = overlayClass;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** @private */
|
|
890
|
+
__pickersChanged(datePicker, timePicker) {
|
|
891
|
+
if (!datePicker || !timePicker) {
|
|
892
|
+
// Both pickers are not ready yet
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (this.__isDefaultPicker(datePicker, 'date') !== this.__isDefaultPicker(timePicker, 'time')) {
|
|
897
|
+
// Both pickers are not replaced yet
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (datePicker.value) {
|
|
902
|
+
// The new pickers have a value, update the component value
|
|
903
|
+
this.__valueChangedEventHandler();
|
|
904
|
+
} else if (this.value) {
|
|
905
|
+
// The component has a value, update the new pickers values
|
|
906
|
+
this.__selectedDateTimeChanged(this.__selectedDateTime);
|
|
907
|
+
|
|
908
|
+
// When using Polymer version, mix and max observers are triggered initially
|
|
909
|
+
// before `ready()` and by that time pickers are not yet initialized, so we
|
|
910
|
+
// run initial validation here. Lit version runs observers differently and
|
|
911
|
+
// this observer is executed first - ignore it to prevent validating twice.
|
|
912
|
+
if ((this.min && this.__minDateTime) || (this.max && this.__maxDateTime)) {
|
|
913
|
+
this._requestValidation();
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Fired when the user commits a value change.
|
|
920
|
+
*
|
|
921
|
+
* @event change
|
|
922
|
+
*/
|
|
923
|
+
};
|