@vaadin/date-picker 23.2.0-dev.48e5e3967 → 23.2.0-rc1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/date-picker",
3
- "version": "23.2.0-dev.48e5e3967",
3
+ "version": "23.2.0-rc1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,7 +23,9 @@
23
23
  "src",
24
24
  "theme",
25
25
  "vaadin-*.d.ts",
26
- "vaadin-*.js"
26
+ "vaadin-*.js",
27
+ "web-types.json",
28
+ "web-types.lit.json"
27
29
  ],
28
30
  "keywords": [
29
31
  "Vaadin",
@@ -34,19 +36,23 @@
34
36
  "dependencies": {
35
37
  "@open-wc/dedupe-mixin": "^1.3.0",
36
38
  "@polymer/polymer": "^3.2.0",
37
- "@vaadin/button": "23.2.0-dev.48e5e3967",
38
- "@vaadin/component-base": "23.2.0-dev.48e5e3967",
39
- "@vaadin/field-base": "23.2.0-dev.48e5e3967",
40
- "@vaadin/input-container": "23.2.0-dev.48e5e3967",
41
- "@vaadin/vaadin-lumo-styles": "23.2.0-dev.48e5e3967",
42
- "@vaadin/vaadin-material-styles": "23.2.0-dev.48e5e3967",
43
- "@vaadin/vaadin-overlay": "23.2.0-dev.48e5e3967",
44
- "@vaadin/vaadin-themable-mixin": "23.2.0-dev.48e5e3967"
39
+ "@vaadin/button": "23.2.0-rc1",
40
+ "@vaadin/component-base": "23.2.0-rc1",
41
+ "@vaadin/field-base": "23.2.0-rc1",
42
+ "@vaadin/input-container": "23.2.0-rc1",
43
+ "@vaadin/vaadin-lumo-styles": "23.2.0-rc1",
44
+ "@vaadin/vaadin-material-styles": "23.2.0-rc1",
45
+ "@vaadin/vaadin-overlay": "23.2.0-rc1",
46
+ "@vaadin/vaadin-themable-mixin": "23.2.0-rc1"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@esm-bundle/chai": "^4.3.4",
48
50
  "@vaadin/testing-helpers": "^0.3.2",
49
51
  "sinon": "^13.0.2"
50
52
  },
51
- "gitHead": "961bc4ae5b707c3c02f12b99819b3c12c9b478aa"
53
+ "web-types": [
54
+ "web-types.json",
55
+ "web-types.lit.json"
56
+ ],
57
+ "gitHead": "e78a1f2fe6f42d78cefa3f48085b09a3033c9588"
52
58
  }
@@ -30,12 +30,19 @@ export type DatePickerLightInvalidChangedEvent = CustomEvent<{ value: boolean }>
30
30
  */
31
31
  export type DatePickerLightValueChangedEvent = CustomEvent<{ value: string }>;
32
32
 
33
+ /**
34
+ * Fired whenever the field is validated.
35
+ */
36
+ export type DatePickerLightValidatedEvent = CustomEvent<{ valid: boolean }>;
37
+
33
38
  export interface DatePickerLightCustomEventMap {
34
39
  'opened-changed': DatePickerLightOpenedChangedEvent;
35
40
 
36
41
  'invalid-changed': DatePickerLightInvalidChangedEvent;
37
42
 
38
43
  'value-changed': DatePickerLightValueChangedEvent;
44
+
45
+ validated: DatePickerLightValidatedEvent;
39
46
  }
40
47
 
41
48
  export interface DatePickerLightEventMap extends HTMLElementEventMap, DatePickerLightCustomEventMap {
@@ -80,6 +87,7 @@ export interface DatePickerLightEventMap extends HTMLElementEventMap, DatePicker
80
87
  * @fires {Event} change - Fired when the user commits a value change.
81
88
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
82
89
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
90
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
83
91
  */
84
92
  declare class DatePickerLight extends ThemableMixin(DatePickerMixin(ValidateMixin(HTMLElement))) {
85
93
  /**
@@ -91,13 +99,13 @@ declare class DatePickerLight extends ThemableMixin(DatePickerMixin(ValidateMixi
91
99
  addEventListener<K extends keyof DatePickerLightEventMap>(
92
100
  type: K,
93
101
  listener: (this: DatePickerLight, ev: DatePickerLightEventMap[K]) => void,
94
- options?: boolean | AddEventListenerOptions,
102
+ options?: AddEventListenerOptions | boolean,
95
103
  ): void;
96
104
 
97
105
  removeEventListener<K extends keyof DatePickerLightEventMap>(
98
106
  type: K,
99
107
  listener: (this: DatePickerLight, ev: DatePickerLightEventMap[K]) => void,
100
- options?: boolean | EventListenerOptions,
108
+ options?: EventListenerOptions | boolean,
101
109
  ): void;
102
110
  }
103
111
 
@@ -49,6 +49,7 @@ import { DatePickerMixin } from './vaadin-date-picker-mixin.js';
49
49
  * @fires {Event} change - Fired when the user commits a value change.
50
50
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
51
51
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
52
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
52
53
  *
53
54
  * @extends HTMLElement
54
55
  * @mixes ThemableMixin
@@ -73,7 +74,7 @@ class DatePickerLight extends ThemableMixin(DatePickerMixin(ValidateMixin(Polyme
73
74
  fullscreen$="[[_fullscreen]]"
74
75
  opened="{{opened}}"
75
76
  on-vaadin-overlay-open="_onOverlayOpened"
76
- on-vaadin-overlay-close="_onOverlayClosed"
77
+ on-vaadin-overlay-closing="_onOverlayClosed"
77
78
  restore-focus-on-close
78
79
  restore-focus-node="[[inputElement]]"
79
80
  theme$="[[__getOverlayTheme(_theme, _overlayInitialized)]]"
@@ -3,12 +3,13 @@
3
3
  * Copyright (c) 2016 - 2022 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { Constructor } from '@open-wc/dedupe-mixin';
7
- import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
- import { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
9
- import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
10
- import { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js';
11
- import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
6
+ import type { Constructor } from '@open-wc/dedupe-mixin';
7
+ import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
+ import type { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
9
+ import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
10
+ import type { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js';
11
+ import type { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js';
12
+ import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
12
13
 
13
14
  export interface DatePickerDate {
14
15
  day: number;
@@ -33,13 +34,14 @@ export interface DatePickerI18n {
33
34
 
34
35
  export declare function DatePickerMixin<T extends Constructor<HTMLElement>>(
35
36
  base: T,
36
- ): T &
37
- Constructor<DatePickerMixinClass> &
37
+ ): Constructor<DatePickerMixinClass> &
38
38
  Constructor<DelegateFocusMixinClass> &
39
39
  Constructor<DisabledMixinClass> &
40
40
  Constructor<FocusMixinClass> &
41
+ Constructor<InputConstraintsMixinClass> &
41
42
  Constructor<InputMixinClass> &
42
- Constructor<KeyboardMixinClass>;
43
+ Constructor<KeyboardMixinClass> &
44
+ T;
43
45
 
44
46
  export declare class DatePickerMixinClass {
45
47
  /**
@@ -8,7 +8,7 @@ import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js'
8
8
  import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
9
9
  import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
10
10
  import { DelegateFocusMixin } from '@vaadin/field-base/src/delegate-focus-mixin.js';
11
- import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
11
+ import { InputConstraintsMixin } from '@vaadin/field-base/src/input-constraints-mixin.js';
12
12
  import { VirtualKeyboardController } from '@vaadin/field-base/src/virtual-keyboard-controller.js';
13
13
  import { dateAllowed, dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js';
14
14
 
@@ -17,7 +17,9 @@ import { dateAllowed, dateEquals, extractDateParts, getClosestDate } from './vaa
17
17
  * @param {function(new:HTMLElement)} subclass
18
18
  */
19
19
  export const DatePickerMixin = (subclass) =>
20
- class VaadinDatePickerMixin extends ControllerMixin(DelegateFocusMixin(InputMixin(KeyboardMixin(subclass)))) {
20
+ class VaadinDatePickerMixin extends ControllerMixin(
21
+ DelegateFocusMixin(InputConstraintsMixin(KeyboardMixin(subclass))),
22
+ ) {
21
23
  static get properties() {
22
24
  return {
23
25
  /**
@@ -46,7 +48,6 @@ export const DatePickerMixin = (subclass) =>
46
48
  */
47
49
  value: {
48
50
  type: String,
49
- observer: '_valueChanged',
50
51
  notify: true,
51
52
  value: '',
52
53
  },
@@ -102,13 +103,6 @@ export const DatePickerMixin = (subclass) =>
102
103
  value: '(max-width: 420px), (max-height: 420px)',
103
104
  },
104
105
 
105
- /**
106
- * An array of ancestor elements whose -webkit-overflow-scrolling is forced from value
107
- * 'touch' to value 'auto' in order to prevent them from clipping the dropdown. iOS only.
108
- * @private
109
- */
110
- _touchPrevented: Array,
111
-
112
106
  /**
113
107
  * The object used to localize this component.
114
108
  * To change the default localization, replace the entire
@@ -264,7 +258,6 @@ export const DatePickerMixin = (subclass) =>
264
258
  */
265
259
  min: {
266
260
  type: String,
267
- observer: '_minChanged',
268
261
  },
269
262
 
270
263
  /**
@@ -278,28 +271,26 @@ export const DatePickerMixin = (subclass) =>
278
271
  */
279
272
  max: {
280
273
  type: String,
281
- observer: '_maxChanged',
282
274
  },
283
275
 
284
276
  /**
285
277
  * The earliest date that can be selected. All earlier dates will be disabled.
286
- * @type {Date | string}
278
+ * @type {Date | undefined}
287
279
  * @protected
288
280
  */
289
281
  _minDate: {
290
282
  type: Date,
291
- // Null does not work here because minimizer passes undefined to overlay (#351)
292
- value: '',
283
+ computed: '__computeMinOrMaxDate(min)',
293
284
  },
294
285
 
295
286
  /**
296
287
  * The latest date that can be selected. All later dates will be disabled.
297
- * @type {Date | string}
288
+ * @type {Date | undefined}
298
289
  * @protected
299
290
  */
300
291
  _maxDate: {
301
292
  type: Date,
302
- value: '',
293
+ computed: '__computeMinOrMaxDate(max)',
303
294
  },
304
295
 
305
296
  /** @private */
@@ -314,12 +305,6 @@ export const DatePickerMixin = (subclass) =>
314
305
  value: isIOS,
315
306
  },
316
307
 
317
- /** @private */
318
- _webkitOverflowScroll: {
319
- type: Boolean,
320
- value: document.createElement('div').style.webkitOverflowScrolling === '',
321
- },
322
-
323
308
  /** @private */
324
309
  _focusOverlayOnOpen: Boolean,
325
310
 
@@ -335,6 +320,10 @@ export const DatePickerMixin = (subclass) =>
335
320
  ];
336
321
  }
337
322
 
323
+ static get constraints() {
324
+ return [...super.constraints, 'min', 'max'];
325
+ }
326
+
338
327
  /**
339
328
  * Override a getter from `InputControlMixin` to make it optional
340
329
  * and to prevent warning when a clear button is missing,
@@ -401,12 +390,10 @@ export const DatePickerMixin = (subclass) =>
401
390
  }
402
391
  }
403
392
 
404
- if (this.inputElement.value === '' && this.__dispatchChange) {
405
- this.validate();
393
+ this.validate();
394
+
395
+ if (this._inputValue === '' && this.value !== '') {
406
396
  this.value = '';
407
- this.__dispatchChange = false;
408
- } else {
409
- this.validate();
410
397
  }
411
398
  }
412
399
  }
@@ -475,14 +462,19 @@ export const DatePickerMixin = (subclass) =>
475
462
  this.$.overlay.removeAttribute('disable-upgrade');
476
463
  this._overlayInitialized = true;
477
464
 
478
- this.$.overlay.addEventListener('opened-changed', (e) => (this.opened = e.detail.value));
465
+ this.$.overlay.addEventListener('opened-changed', (e) => {
466
+ this.opened = e.detail.value;
467
+ });
479
468
 
480
469
  this.$.overlay.addEventListener('vaadin-overlay-escape-press', () => {
481
470
  this._focusedDate = this._selectedDate;
482
471
  this._close();
483
472
  });
484
473
 
485
- this._overlayContent.addEventListener('close', this._close.bind(this));
474
+ this._overlayContent.addEventListener('close', () => {
475
+ this._close();
476
+ });
477
+
486
478
  this._overlayContent.addEventListener('focus-input', this._focusAndSelect.bind(this));
487
479
 
488
480
  // User confirmed selected date by clicking the calendar.
@@ -491,7 +483,7 @@ export const DatePickerMixin = (subclass) =>
491
483
 
492
484
  this._selectDate(e.detail.date);
493
485
 
494
- this._close(e);
486
+ this._close();
495
487
  });
496
488
 
497
489
  // User confirmed selected date by pressing Enter or Today.
@@ -501,9 +493,12 @@ export const DatePickerMixin = (subclass) =>
501
493
  this._selectDate(e.detail.date);
502
494
  });
503
495
 
504
- // Keep focus attribute in focusElement for styling
496
+ // Set focus-ring attribute when moving focus to the overlay
497
+ // by pressing Tab or arrow key, after opening it on click.
505
498
  this._overlayContent.addEventListener('focusin', () => {
506
- this._setFocused(true);
499
+ if (this._keyboardActive) {
500
+ this._setFocused(true);
501
+ }
507
502
  });
508
503
 
509
504
  this.addEventListener('mousedown', () => this.__bringToFront());
@@ -536,6 +531,51 @@ export const DatePickerMixin = (subclass) =>
536
531
  return inputValid && minMaxValid && inputValidity;
537
532
  }
538
533
 
534
+ /**
535
+ * Override method inherited from `FocusMixin`
536
+ * to not call `_setFocused(true)` when focus
537
+ * is restored after closing overlay on click,
538
+ * and to avoid removing `focus-ring` attribute.
539
+ *
540
+ * @param {!FocusEvent} _event
541
+ * @return {boolean}
542
+ * @protected
543
+ * @override
544
+ */
545
+ _shouldSetFocus(_event) {
546
+ return !this._shouldKeepFocusRing;
547
+ }
548
+
549
+ /**
550
+ * Override method inherited from `FocusMixin`
551
+ * to prevent removing the `focused` attribute:
552
+ * - when moving focus to the overlay content,
553
+ * - when closing on date click / outside click.
554
+ *
555
+ * @param {!FocusEvent} _event
556
+ * @return {boolean}
557
+ * @protected
558
+ * @override
559
+ */
560
+ _shouldRemoveFocus(_event) {
561
+ return !this.opened;
562
+ }
563
+
564
+ /**
565
+ * Override method inherited from `FocusMixin`
566
+ * to store the `focus-ring` state to restore
567
+ * it later when closing on outside click.
568
+ *
569
+ * @param {boolean} focused
570
+ * @protected
571
+ * @override
572
+ */
573
+ _setFocused(focused) {
574
+ super._setFocused(focused);
575
+
576
+ this._shouldKeepFocusRing = focused && this._keyboardActive;
577
+ }
578
+
539
579
  /**
540
580
  * Select date on user interaction and set the flag
541
581
  * to fire change event if necessary.
@@ -555,10 +595,7 @@ export const DatePickerMixin = (subclass) =>
555
595
  }
556
596
 
557
597
  /** @private */
558
- _close(e) {
559
- if (e) {
560
- e.stopPropagation();
561
- }
598
+ _close() {
562
599
  this._focus();
563
600
  this.close();
564
601
  }
@@ -687,47 +724,46 @@ export const DatePickerMixin = (subclass) =>
687
724
  }
688
725
  }
689
726
 
690
- /** @private */
691
- _handleDateChange(property, value, oldValue) {
692
- if (!value) {
693
- this[property] = '';
694
- return;
695
- }
727
+ /**
728
+ * Override the value observer from `InputMixin` to implement custom
729
+ * handling of the `value` property. The date-picker doesn't forward
730
+ * the value directly to the input like the default implementation of `InputMixin`.
731
+ * Instead, it parses the value into a date, puts it in `_selectedDate` which
732
+ * is then displayed in the input with respect to the specified date format.
733
+ *
734
+ * @param {string | undefined} value
735
+ * @param {string | undefined} oldValue
736
+ * @protected
737
+ * @override
738
+ */
739
+ _valueChanged(value, oldValue) {
740
+ const newDate = this._parseDate(value);
696
741
 
697
- const date = this._parseDate(value);
698
- if (!date) {
742
+ if (value && !newDate) {
743
+ // The new value cannot be parsed, revert the old value.
699
744
  this.value = oldValue;
700
745
  return;
701
746
  }
702
- if (!dateEquals(this[property], date)) {
703
- this[property] = date;
704
- if (this.value) {
705
- this.validate();
706
- }
707
- }
708
- }
709
747
 
710
- /** @private */
711
- _valueChanged(value, oldValue) {
712
- this._handleDateChange('_selectedDate', value, oldValue);
748
+ if (value) {
749
+ if (!dateEquals(this._selectedDate, newDate)) {
750
+ // Update the date instance only if the date has actually changed.
751
+ this._selectedDate = newDate;
713
752
 
714
- this._toggleHasValue(!!value);
715
- }
716
-
717
- /** @private */
718
- _minChanged(value, oldValue) {
719
- this._handleDateChange('_minDate', value, oldValue);
720
- }
753
+ if (oldValue !== undefined) {
754
+ // Validate only if `value` changes after initialization.
755
+ this.validate();
756
+ }
757
+ }
758
+ } else {
759
+ this._selectedDate = null;
760
+ }
721
761
 
722
- /** @private */
723
- _maxChanged(value, oldValue) {
724
- this._handleDateChange('_maxDate', value, oldValue);
762
+ this._toggleHasValue(this._hasValue);
725
763
  }
726
764
 
727
765
  /** @protected */
728
766
  _onOverlayOpened() {
729
- this._openedWithFocusRing = this.hasAttribute('focus-ring');
730
-
731
767
  const parsedInitialPosition = this._parseDate(this.initialPosition);
732
768
 
733
769
  const initialPosition =
@@ -747,10 +783,6 @@ export const DatePickerMixin = (subclass) =>
747
783
 
748
784
  window.addEventListener('scroll', this._boundOnScroll, true);
749
785
 
750
- if (this._webkitOverflowScroll) {
751
- this._touchPrevented = this._preventWebkitOverflowScrollingTouch(this.parentElement);
752
- }
753
-
754
786
  if (this._focusOverlayOnOpen) {
755
787
  this._overlayContent.focusDateElement();
756
788
  this._focusOverlayOnOpen = false;
@@ -764,25 +796,6 @@ export const DatePickerMixin = (subclass) =>
764
796
  }
765
797
  }
766
798
 
767
- // A hack needed for iOS to prevent dropdown from being clipped in an
768
- // ancestor container with -webkit-overflow-scrolling: touch;
769
- /** @private */
770
- _preventWebkitOverflowScrollingTouch(element) {
771
- const result = [];
772
- while (element) {
773
- if (window.getComputedStyle(element).webkitOverflowScrolling === 'touch') {
774
- const oldInlineValue = element.style.webkitOverflowScrolling;
775
- element.style.webkitOverflowScrolling = 'auto';
776
- result.push({
777
- element,
778
- oldInlineValue,
779
- });
780
- }
781
- element = element.parentElement;
782
- }
783
- return result;
784
- }
785
-
786
799
  /** @private */
787
800
  _selectParsedOrFocusedDate() {
788
801
  // Select the parsed input or focused date
@@ -809,13 +822,6 @@ export const DatePickerMixin = (subclass) =>
809
822
  _onOverlayClosed() {
810
823
  window.removeEventListener('scroll', this._boundOnScroll, true);
811
824
 
812
- if (this._touchPrevented) {
813
- this._touchPrevented.forEach(
814
- (prevented) => (prevented.element.style.webkitOverflowScrolling = prevented.oldInlineValue),
815
- );
816
- this._touchPrevented = [];
817
- }
818
-
819
825
  // No need to select date on close if it was confirmed by the user.
820
826
  if (this.__userConfirmedDate) {
821
827
  this.__userConfirmedDate = false;
@@ -831,11 +837,6 @@ export const DatePickerMixin = (subclass) =>
831
837
  if (!this.value) {
832
838
  this.validate();
833
839
  }
834
-
835
- // If the input isn't focused when overlay closes (fullscreen mode), clear focused state
836
- if (this.getRootNode().activeElement !== this.inputElement) {
837
- this._setFocused(false);
838
- }
839
840
  }
840
841
 
841
842
  /** @private */
@@ -888,10 +889,7 @@ export const DatePickerMixin = (subclass) =>
888
889
  _onChange(event) {
889
890
  // For change event on the native <input> blur, after the input is cleared,
890
891
  // we schedule change event to be dispatched on date-picker blur.
891
- if (
892
- this.inputElement.value === '' &&
893
- !(event.detail && event.detail.sourceEvent && event.detail.sourceEvent.__fromClearButton)
894
- ) {
892
+ if (this._inputValue === '') {
895
893
  this.__dispatchChange = true;
896
894
  }
897
895
 
@@ -978,7 +976,7 @@ export const DatePickerMixin = (subclass) =>
978
976
  if (e.shiftKey) {
979
977
  this._overlayContent.focusCancel();
980
978
  } else {
981
- this._overlayContent.focusDate(this._focusedDate);
979
+ this._overlayContent.focusDateElement();
982
980
  }
983
981
  }
984
982
  break;
@@ -1091,6 +1089,11 @@ export const DatePickerMixin = (subclass) =>
1091
1089
  return this.$.overlay.content.querySelector('#overlay-content');
1092
1090
  }
1093
1091
 
1092
+ /** @private */
1093
+ __computeMinOrMaxDate(dateString) {
1094
+ return this._parseDate(dateString);
1095
+ }
1096
+
1094
1097
  /**
1095
1098
  * Fired when the user commits a value change.
1096
1099
  *