@vaadin/date-picker 23.3.3 → 24.0.0-alpha10

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.
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2016 - 2022 Vaadin Ltd.
3
+ * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import '@vaadin/button/src/vaadin-button.js';
7
7
  import './vaadin-month-calendar.js';
8
- import './vaadin-infinite-scroller.js';
8
+ import './vaadin-date-picker-month-scroller.js';
9
+ import './vaadin-date-picker-year-scroller.js';
10
+ import './vaadin-date-picker-year.js';
11
+ import { flush } from '@polymer/polymer/lib/utils/flush.js';
12
+ import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
9
13
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
10
14
  import { timeOut } from '@vaadin/component-base/src/async.js';
11
15
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
@@ -13,8 +17,9 @@ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
13
17
  import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
14
18
  import { addListener, setTouchAction } from '@vaadin/component-base/src/gestures.js';
15
19
  import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
20
+ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
16
21
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
17
- import { dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js';
22
+ import { dateAfterXMonths, dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js';
18
23
 
19
24
  /**
20
25
  * @extends HTMLElement
@@ -63,62 +68,17 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
63
68
  overflow: hidden;
64
69
  }
65
70
 
66
- [part='months'],
67
- [part='years'] {
68
- height: 100%;
69
- }
70
-
71
- [part='months'] {
72
- --vaadin-infinite-scroller-item-height: 270px;
73
- position: absolute;
74
- top: 0;
75
- left: 0;
76
- right: 0;
77
- bottom: 0;
78
- }
79
-
80
- #scrollers[desktop] [part='months'] {
71
+ :host([desktop]) ::slotted([slot='months']) {
81
72
  right: 50px;
82
73
  transform: none !important;
83
74
  }
84
75
 
85
- [part='years'] {
86
- --vaadin-infinite-scroller-item-height: 80px;
87
- width: 50px;
88
- position: absolute;
89
- right: 0;
90
- transform: translateX(100%);
91
- -webkit-tap-highlight-color: transparent;
92
- -webkit-user-select: none;
93
- -moz-user-select: none;
94
- user-select: none;
95
- /* Center the year scroller position. */
96
- --vaadin-infinite-scroller-buffer-offset: 50%;
97
- }
98
-
99
- #scrollers[desktop] [part='years'] {
100
- position: absolute;
76
+ :host([desktop]) ::slotted([slot='years']) {
101
77
  transform: none !important;
102
78
  }
103
79
 
104
- [part='years']::before {
105
- content: '';
106
- display: block;
107
- background: transparent;
108
- width: 0;
109
- height: 0;
110
- position: absolute;
111
- left: 0;
112
- top: 50%;
113
- transform: translateY(-50%);
114
- border-width: 6px;
115
- border-style: solid;
116
- border-color: transparent;
117
- border-left-color: #000;
118
- }
119
-
120
- :host(.animate) [part='months'],
121
- :host(.animate) [part='years'] {
80
+ :host(.animate) ::slotted([slot='months']),
81
+ :host(.animate) ::slotted([slot='years']) {
122
82
  transition: all 200ms;
123
83
  }
124
84
 
@@ -130,7 +90,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
130
90
  }
131
91
  </style>
132
92
 
133
- <div part="overlay-header" on-touchend="_preventDefault" desktop$="[[_desktopMode]]" aria-hidden="true">
93
+ <div part="overlay-header" on-touchend="_preventDefault" aria-hidden="true">
134
94
  <div part="label">[[_formatDisplayed(selectedDate, i18n.formatDate, label)]]</div>
135
95
  <div part="clear-button" hidden$="[[!selectedDate]]"></div>
136
96
  <div part="toggle-button"></div>
@@ -140,67 +100,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
140
100
  </div>
141
101
  </div>
142
102
 
143
- <div id="scrollers" desktop$="[[_desktopMode]]">
144
- <vaadin-infinite-scroller
145
- id="monthScroller"
146
- on-custom-scroll="_onMonthScroll"
147
- on-touchstart="_onMonthScrollTouchStart"
148
- buffer-size="3"
149
- active="[[initialPosition]]"
150
- part="months"
151
- >
152
- <template>
153
- <vaadin-month-calendar
154
- i18n="[[i18n]]"
155
- month="[[_dateAfterXMonths(index)]]"
156
- selected-date="{{selectedDate}}"
157
- focused-date="[[focusedDate]]"
158
- ignore-taps="[[_ignoreTaps]]"
159
- show-week-numbers="[[showWeekNumbers]]"
160
- min-date="[[minDate]]"
161
- max-date="[[maxDate]]"
162
- part="month"
163
- theme$="[[_theme]]"
164
- on-keydown="__onMonthCalendarKeyDown"
165
- >
166
- </vaadin-month-calendar>
167
- </template>
168
- </vaadin-infinite-scroller>
169
- <vaadin-infinite-scroller
170
- id="yearScroller"
171
- on-custom-scroll="_onYearScroll"
172
- on-touchstart="_onYearScrollTouchStart"
173
- buffer-size="12"
174
- active="[[initialPosition]]"
175
- part="years"
176
- aria-hidden="true"
177
- >
178
- <template>
179
- <div
180
- part="year-number"
181
- current$="[[_isCurrentYear(index)]]"
182
- selected$="[[_isSelectedYear(index, selectedDate)]]"
183
- >
184
- [[_yearAfterXYears(index)]]
185
- </div>
186
- <div part="year-separator" aria-hidden="true"></div>
187
- </template>
188
- </vaadin-infinite-scroller>
103
+ <div id="scrollers">
104
+ <slot name="months"></slot>
105
+ <slot name="years"></slot>
189
106
  </div>
190
107
 
191
108
  <div on-touchend="_preventDefault" role="toolbar" part="toolbar">
192
- <vaadin-button
193
- id="todayButton"
194
- part="today-button"
195
- theme="tertiary"
196
- disabled="[[!_isTodayAllowed(minDate, maxDate)]]"
197
- on-keydown="__onTodayButtonKeyDown"
198
- >
199
- [[i18n.today]]
200
- </vaadin-button>
201
- <vaadin-button id="cancelButton" part="cancel-button" theme="tertiary" on-keydown="__onCancelButtonKeyDown">
202
- [[i18n.cancel]]
203
- </vaadin-button>
109
+ <slot name="today-button"></slot>
110
+ <slot name="cancel-button"></slot>
204
111
  </div>
205
112
  `;
206
113
  }
@@ -249,7 +156,10 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
249
156
 
250
157
  _visibleMonthIndex: Number,
251
158
 
252
- _desktopMode: Boolean,
159
+ _desktopMode: {
160
+ type: Boolean,
161
+ observer: '_desktopModeChanged',
162
+ },
253
163
 
254
164
  _desktopMediaQuery: {
255
165
  type: String,
@@ -270,6 +180,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
270
180
 
271
181
  showWeekNumbers: {
272
182
  type: Boolean,
183
+ value: false,
273
184
  },
274
185
 
275
186
  _ignoreTaps: Boolean,
@@ -290,11 +201,34 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
290
201
  * Input label
291
202
  */
292
203
  label: String,
204
+
205
+ _cancelButton: {
206
+ type: Object,
207
+ },
208
+
209
+ _todayButton: {
210
+ type: Object,
211
+ },
212
+
213
+ calendars: {
214
+ type: Array,
215
+ value: () => [],
216
+ },
217
+
218
+ years: {
219
+ type: Array,
220
+ value: () => [],
221
+ },
293
222
  };
294
223
  }
295
224
 
296
- get __isRTL() {
297
- return this.getAttribute('dir') === 'rtl';
225
+ static get observers() {
226
+ return [
227
+ '__updateCalendars(calendars, i18n, minDate, maxDate, selectedDate, focusedDate, showWeekNumbers, _ignoreTaps, _theme)',
228
+ '__updateCancelButton(_cancelButton, i18n)',
229
+ '__updateTodayButton(_todayButton, i18n, minDate, maxDate)',
230
+ '__updateYears(years, selectedDate, _theme)',
231
+ ];
298
232
  }
299
233
 
300
234
  /**
@@ -306,11 +240,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
306
240
  * @private
307
241
  */
308
242
  get __useSubMonthScrolling() {
309
- return this.$.monthScroller.clientHeight < this.$.monthScroller.itemHeight + this.$.monthScroller.bufferOffset;
310
- }
311
-
312
- get calendars() {
313
- return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')];
243
+ return this._monthScroller.clientHeight < this._monthScroller.itemHeight + this._monthScroller.bufferOffset;
314
244
  }
315
245
 
316
246
  get focusableDateElement() {
@@ -324,10 +254,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
324
254
 
325
255
  addListener(this.$.scrollers, 'track', this._track.bind(this));
326
256
  addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this));
327
- addListener(this.shadowRoot.querySelector('[part="today-button"]'), 'tap', this._onTodayTap.bind(this));
328
- addListener(this.shadowRoot.querySelector('[part="cancel-button"]'), 'tap', this._cancel.bind(this));
329
257
  addListener(this.shadowRoot.querySelector('[part="toggle-button"]'), 'tap', this._cancel.bind(this));
330
- addListener(this.shadowRoot.querySelector('[part="years"]'), 'tap', this._onYearTap.bind(this));
331
258
  addListener(
332
259
  this.shadowRoot.querySelector('[part="years-toggle-button"]'),
333
260
  'tap',
@@ -339,6 +266,33 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
339
266
  this._desktopMode = matches;
340
267
  }),
341
268
  );
269
+
270
+ this.addController(
271
+ new SlotController(this, 'today-button', 'vaadin-button', {
272
+ observe: false,
273
+ initializer: (btn) => {
274
+ btn.setAttribute('theme', 'tertiary');
275
+ btn.addEventListener('keydown', (e) => this.__onTodayButtonKeyDown(e));
276
+ addListener(btn, 'tap', this._onTodayTap.bind(this));
277
+ this._todayButton = btn;
278
+ },
279
+ }),
280
+ );
281
+
282
+ this.addController(
283
+ new SlotController(this, 'cancel-button', 'vaadin-button', {
284
+ observe: false,
285
+ initializer: (btn) => {
286
+ btn.setAttribute('theme', 'tertiary');
287
+ btn.addEventListener('keydown', (e) => this.__onCancelButtonKeyDown(e));
288
+ addListener(btn, 'tap', this._cancel.bind(this));
289
+ this._cancelButton = btn;
290
+ },
291
+ }),
292
+ );
293
+
294
+ this.__initMonthScroller();
295
+ this.__initYearScroller();
342
296
  }
343
297
 
344
298
  /**
@@ -359,7 +313,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
359
313
  * Focuses the cancel button
360
314
  */
361
315
  focusCancel() {
362
- this.$.cancelButton.focus();
316
+ this._cancelButton.focus();
363
317
  }
364
318
 
365
319
  /**
@@ -368,7 +322,122 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
368
322
  scrollToDate(date, animate) {
369
323
  const offset = this.__useSubMonthScrolling ? this._calculateWeekScrollOffset(date) : 0;
370
324
  this._scrollToPosition(this._differenceInMonths(date, this._originDate) + offset, animate);
371
- this.$.monthScroller.forceUpdate();
325
+ this._monthScroller.forceUpdate();
326
+ }
327
+
328
+ __initMonthScroller() {
329
+ this.addController(
330
+ new SlotController(this, 'months', 'vaadin-date-picker-month-scroller', {
331
+ observe: false,
332
+ initializer: (scroller) => {
333
+ scroller.addEventListener('custom-scroll', () => {
334
+ this._onMonthScroll();
335
+ });
336
+
337
+ scroller.addEventListener('touchstart', () => {
338
+ this._onMonthScrollTouchStart();
339
+ });
340
+
341
+ scroller.addEventListener('keydown', (e) => {
342
+ this.__onMonthCalendarKeyDown(e);
343
+ });
344
+
345
+ scroller.addEventListener('init-done', () => {
346
+ const calendars = [...this.querySelectorAll('vaadin-month-calendar')];
347
+
348
+ // Two-way binding for selectedDate property
349
+ calendars.forEach((calendar) => {
350
+ calendar.addEventListener('selected-date-changed', (e) => {
351
+ this.selectedDate = e.detail.value;
352
+ });
353
+ });
354
+
355
+ this.calendars = calendars;
356
+ });
357
+
358
+ this._monthScroller = scroller;
359
+ },
360
+ }),
361
+ );
362
+ }
363
+
364
+ __initYearScroller() {
365
+ this.addController(
366
+ new SlotController(this, 'years', 'vaadin-date-picker-year-scroller', {
367
+ observe: false,
368
+ initializer: (scroller) => {
369
+ scroller.setAttribute('aria-hidden', 'true');
370
+
371
+ addListener(scroller, 'tap', (e) => {
372
+ this._onYearTap(e);
373
+ });
374
+
375
+ scroller.addEventListener('custom-scroll', () => {
376
+ this._onYearScroll();
377
+ });
378
+
379
+ scroller.addEventListener('touchstart', () => {
380
+ this._onYearScrollTouchStart();
381
+ });
382
+
383
+ scroller.addEventListener('init-done', () => {
384
+ this.years = [...this.querySelectorAll('vaadin-date-picker-year')];
385
+ });
386
+
387
+ this._yearScroller = scroller;
388
+ },
389
+ }),
390
+ );
391
+ }
392
+
393
+ __updateCancelButton(cancelButton, i18n) {
394
+ if (cancelButton) {
395
+ cancelButton.textContent = i18n && i18n.cancel;
396
+ }
397
+ }
398
+
399
+ __updateTodayButton(todayButton, i18n, minDate, maxDate) {
400
+ if (todayButton) {
401
+ todayButton.textContent = i18n && i18n.today;
402
+ todayButton.disabled = !this._isTodayAllowed(minDate, maxDate);
403
+ }
404
+ }
405
+
406
+ // eslint-disable-next-line max-params
407
+ __updateCalendars(calendars, i18n, minDate, maxDate, selectedDate, focusedDate, showWeekNumbers, ignoreTaps, theme) {
408
+ if (calendars && calendars.length) {
409
+ calendars.forEach((calendar) => {
410
+ calendar.setProperties({
411
+ i18n,
412
+ minDate,
413
+ maxDate,
414
+ focusedDate,
415
+ selectedDate,
416
+ showWeekNumbers,
417
+ ignoreTaps,
418
+ });
419
+
420
+ if (theme) {
421
+ calendar.setAttribute('theme', theme);
422
+ } else {
423
+ calendar.removeAttribute('theme');
424
+ }
425
+ });
426
+ }
427
+ }
428
+
429
+ __updateYears(years, selectedDate, theme) {
430
+ if (years && years.length) {
431
+ years.forEach((year) => {
432
+ year.selectedDate = selectedDate;
433
+
434
+ if (theme) {
435
+ year.setAttribute('theme', theme);
436
+ } else {
437
+ year.removeAttribute('theme');
438
+ }
439
+ });
440
+ }
372
441
  }
373
442
 
374
443
  /**
@@ -382,18 +451,12 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
382
451
  );
383
452
  }
384
453
 
385
- _focusedDateChanged(focusedDate) {
386
- this.revealDate(focusedDate);
387
- }
388
-
389
- _isCurrentYear(yearsFromNow) {
390
- return yearsFromNow === 0;
454
+ _desktopModeChanged(desktopMode) {
455
+ this.toggleAttribute('desktop', desktopMode);
391
456
  }
392
457
 
393
- _isSelectedYear(yearsFromNow, selectedDate) {
394
- if (selectedDate) {
395
- return selectedDate.getFullYear() === this._originDate.getFullYear() + yearsFromNow;
396
- }
458
+ _focusedDateChanged(focusedDate) {
459
+ this.revealDate(focusedDate);
397
460
  }
398
461
 
399
462
  /**
@@ -413,14 +476,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
413
476
  }
414
477
 
415
478
  // Otherwise determine if we need to scroll to make the month of the date visible
416
- const scrolledAboveViewport = this.$.monthScroller.position > diff;
479
+ const scrolledAboveViewport = this._monthScroller.position > diff;
417
480
 
418
481
  const visibleArea = Math.max(
419
- this.$.monthScroller.itemHeight,
420
- this.$.monthScroller.clientHeight - this.$.monthScroller.bufferOffset * 2,
482
+ this._monthScroller.itemHeight,
483
+ this._monthScroller.clientHeight - this._monthScroller.bufferOffset * 2,
421
484
  );
422
- const visibleItems = visibleArea / this.$.monthScroller.itemHeight;
423
- const scrolledBelowViewport = this.$.monthScroller.position + visibleItems - 1 < diff;
485
+ const visibleItems = visibleArea / this._monthScroller.itemHeight;
486
+ const scrolledBelowViewport = this._monthScroller.position + visibleItems - 1 < diff;
424
487
 
425
488
  if (scrolledAboveViewport) {
426
489
  this._scrollToPosition(diff, animate);
@@ -460,17 +523,23 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
460
523
  }
461
524
 
462
525
  _initialPositionChanged(initialPosition) {
526
+ if (this._monthScroller && this._yearScroller) {
527
+ this._monthScroller.active = true;
528
+ this._yearScroller.active = true;
529
+ }
530
+
463
531
  this.scrollToDate(initialPosition);
464
532
  }
465
533
 
466
534
  _repositionYearScroller() {
467
- this._visibleMonthIndex = Math.floor(this.$.monthScroller.position);
468
- this.$.yearScroller.position = (this.$.monthScroller.position + this._originDate.getMonth()) / 12;
535
+ const monthPosition = this._monthScroller.position;
536
+ this._visibleMonthIndex = Math.floor(monthPosition);
537
+ this._yearScroller.position = (monthPosition + this._originDate.getMonth()) / 12;
469
538
  }
470
539
 
471
540
  _repositionMonthScroller() {
472
- this.$.monthScroller.position = this.$.yearScroller.position * 12 - this._originDate.getMonth();
473
- this._visibleMonthIndex = Math.floor(this.$.monthScroller.position);
541
+ this._monthScroller.position = this._yearScroller.position * 12 - this._originDate.getMonth();
542
+ this._visibleMonthIndex = Math.floor(this._monthScroller.position);
474
543
  }
475
544
 
476
545
  _onMonthScroll() {
@@ -513,7 +582,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
513
582
  _onTodayTap() {
514
583
  const today = new Date();
515
584
 
516
- if (Math.abs(this.$.monthScroller.position - this._differenceInMonths(today, this._originDate)) < 0.001) {
585
+ if (Math.abs(this._monthScroller.position - this._differenceInMonths(today, this._originDate)) < 0.001) {
517
586
  // Select today only if the month scroller is positioned approximately
518
587
  // at the beginning of the current month
519
588
  this._selectDate(today);
@@ -534,9 +603,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
534
603
  _onYearTap(e) {
535
604
  if (!this._ignoreTaps && !this._notTapping) {
536
605
  const scrollDelta =
537
- e.detail.y - (this.$.yearScroller.getBoundingClientRect().top + this.$.yearScroller.clientHeight / 2);
538
- const yearDelta = scrollDelta / this.$.yearScroller.itemHeight;
539
- this._scrollToPosition(this.$.monthScroller.position + yearDelta * 12, true);
606
+ e.detail.y - (this._yearScroller.getBoundingClientRect().top + this._yearScroller.clientHeight / 2);
607
+ const yearDelta = scrollDelta / this._yearScroller.itemHeight;
608
+ this._scrollToPosition(this._monthScroller.position + yearDelta * 12, true);
540
609
  }
541
610
  }
542
611
 
@@ -547,7 +616,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
547
616
  }
548
617
 
549
618
  if (!animate) {
550
- this.$.monthScroller.position = targetPosition;
619
+ this._monthScroller.position = targetPosition;
551
620
  this._targetPosition = undefined;
552
621
  this._repositionYearScroller();
553
622
  this.__tryFocusDate();
@@ -572,10 +641,13 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
572
641
  };
573
642
 
574
643
  let start = 0;
575
- const initialPosition = this.$.monthScroller.position;
644
+ const initialPosition = this._monthScroller.position;
576
645
 
577
646
  const smoothScroll = (timestamp) => {
578
- start = start || timestamp;
647
+ if (!start) {
648
+ start = timestamp;
649
+ }
650
+
579
651
  const currentTime = timestamp - start;
580
652
 
581
653
  if (currentTime < this.scrollDuration) {
@@ -585,7 +657,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
585
657
  this._targetPosition - initialPosition,
586
658
  this.scrollDuration,
587
659
  );
588
- this.$.monthScroller.position = currentPos;
660
+ this._monthScroller.position = currentPos;
589
661
  window.requestAnimationFrame(smoothScroll);
590
662
  } else {
591
663
  this.dispatchEvent(
@@ -599,7 +671,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
599
671
  }),
600
672
  );
601
673
 
602
- this.$.monthScroller.position = this._targetPosition;
674
+ this._monthScroller.position = this._targetPosition;
603
675
  this._targetPosition = undefined;
604
676
 
605
677
  revealResolve();
@@ -694,26 +766,13 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
694
766
 
695
767
  _translateXChanged(x) {
696
768
  if (!this._desktopMode) {
697
- this.$.monthScroller.style.transform = `translateX(${x - this._yearScrollerWidth}px)`;
698
- this.$.yearScroller.style.transform = `translateX(${x}px)`;
769
+ this._monthScroller.style.transform = `translateX(${x - this._yearScrollerWidth}px)`;
770
+ this._yearScroller.style.transform = `translateX(${x}px)`;
699
771
  }
700
772
  }
701
773
 
702
- _yearAfterXYears(index) {
703
- const result = new Date(this._originDate);
704
- result.setFullYear(parseInt(index) + this._originDate.getFullYear());
705
- return result.getFullYear();
706
- }
707
-
708
774
  _yearAfterXMonths(months) {
709
- return this._dateAfterXMonths(months).getFullYear();
710
- }
711
-
712
- _dateAfterXMonths(months) {
713
- const result = new Date(this._originDate);
714
- result.setDate(1);
715
- result.setMonth(parseInt(months) + this._originDate.getMonth());
716
- return result;
775
+ return dateAfterXMonths(months).getFullYear();
717
776
  }
718
777
 
719
778
  _differenceInMonths(date1, date2) {
@@ -817,7 +876,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
817
876
 
818
877
  if (this.hasAttribute('fullscreen')) {
819
878
  // Trap focus in the overlay
820
- this.$.cancelButton.focus();
879
+ this.focusCancel();
821
880
  } else {
822
881
  this.__focusInput();
823
882
  }
@@ -890,7 +949,11 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
890
949
  // Wait for `vaadin-month-calendar` elements to be rendered
891
950
  if (!this.calendars.length) {
892
951
  await new Promise((resolve) => {
893
- setTimeout(resolve);
952
+ afterNextRender(this, () => {
953
+ // Force dom-repeat elements to render
954
+ flush();
955
+ resolve();
956
+ });
894
957
  });
895
958
  }
896
959
 
@@ -912,58 +975,55 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
912
975
  this.focusDate(getClosestDate(focus, [this.minDate, this.maxDate]));
913
976
  }
914
977
 
915
- _moveFocusByDays(days) {
916
- const focus = this.focusedDate;
917
- const dateToFocus = new Date(0, 0);
918
- dateToFocus.setFullYear(focus.getFullYear());
919
- dateToFocus.setMonth(focus.getMonth());
920
- dateToFocus.setDate(focus.getDate() + days);
921
-
922
- if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
923
- this.focusDate(dateToFocus);
924
- } else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
978
+ _focusAllowedDate(dateToFocus, diff, keepMonth) {
979
+ if (this._dateAllowed(dateToFocus)) {
980
+ this.focusDate(dateToFocus, keepMonth);
981
+ } else if (this._dateAllowed(this.focusedDate)) {
925
982
  // Move to min or max date
926
- if (days > 0) {
927
- // Down or right
983
+ if (diff > 0) {
984
+ // Down, Right or Page Down key
928
985
  this.focusDate(this.maxDate);
929
986
  } else {
930
- // Up or left
987
+ // Up, Left or Page Up key
931
988
  this.focusDate(this.minDate);
932
989
  }
933
990
  } else {
934
991
  // Move to closest allowed date
935
- this._focusClosestDate(focus);
992
+ this._focusClosestDate(this.focusedDate);
936
993
  }
937
994
  }
938
995
 
939
- _moveFocusByMonths(months) {
940
- const focus = this.focusedDate;
941
- const dateToFocus = new Date(0, 0);
942
- dateToFocus.setFullYear(focus.getFullYear());
943
- dateToFocus.setMonth(focus.getMonth() + months);
996
+ _getDateDiff(months, days) {
997
+ const date = new Date(0, 0);
998
+ date.setFullYear(this.focusedDate.getFullYear());
999
+ date.setMonth(this.focusedDate.getMonth() + months);
1000
+ if (days) {
1001
+ date.setDate(this.focusedDate.getDate() + days);
1002
+ }
1003
+ return date;
1004
+ }
1005
+
1006
+ _moveFocusByDays(days) {
1007
+ const dateToFocus = this._getDateDiff(0, days);
1008
+
1009
+ this._focusAllowedDate(dateToFocus, days, false);
1010
+ }
944
1011
 
1012
+ _moveFocusByMonths(months) {
1013
+ const dateToFocus = this._getDateDiff(months);
945
1014
  const targetMonth = dateToFocus.getMonth();
946
1015
 
947
- dateToFocus.setDate(this._focusedMonthDate || (this._focusedMonthDate = focus.getDate()));
1016
+ if (!this._focusedMonthDate) {
1017
+ this._focusedMonthDate = this.focusedDate.getDate();
1018
+ }
1019
+
1020
+ dateToFocus.setDate(this._focusedMonthDate);
1021
+
948
1022
  if (dateToFocus.getMonth() !== targetMonth) {
949
1023
  dateToFocus.setDate(0);
950
1024
  }
951
1025
 
952
- if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
953
- this.focusDate(dateToFocus, true);
954
- } else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
955
- // Move to min or max date
956
- if (months > 0) {
957
- // Pagedown
958
- this.focusDate(this.maxDate);
959
- } else {
960
- // Pageup
961
- this.focusDate(this.minDate);
962
- }
963
- } else {
964
- // Move to closest allowed date
965
- this._focusClosestDate(focus);
966
- }
1026
+ this._focusAllowedDate(dateToFocus, months, true);
967
1027
  }
968
1028
 
969
1029
  _moveFocusInsideMonth(focusedDate, property) {
@@ -978,9 +1038,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
978
1038
  dateToFocus.setDate(0);
979
1039
  }
980
1040
 
981
- if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
1041
+ if (this._dateAllowed(dateToFocus)) {
982
1042
  this.focusDate(dateToFocus);
983
- } else if (this._dateAllowed(focusedDate, this.minDate, this.maxDate)) {
1043
+ } else if (this._dateAllowed(focusedDate)) {
984
1044
  // Move to minDate or maxDate
985
1045
  this.focusDate(this[property]);
986
1046
  } else {
@@ -989,7 +1049,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
989
1049
  }
990
1050
  }
991
1051
 
992
- _dateAllowed(date, min, max) {
1052
+ _dateAllowed(date, min = this.minDate, max = this.maxDate) {
993
1053
  return (!min || date >= min) && (!max || date <= max);
994
1054
  }
995
1055