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