@vaadin/date-picker 23.0.0-beta1 → 23.0.0-beta5
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 +12 -13
- package/src/vaadin-date-picker-light.js +0 -3
- package/src/vaadin-date-picker-mixin.js +14 -25
- package/src/vaadin-date-picker-overlay-content.js +172 -176
- package/src/vaadin-date-picker-overlay.js +11 -0
- package/src/vaadin-date-picker.js +5 -7
- package/src/vaadin-month-calendar.js +148 -72
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/date-picker",
|
|
3
|
-
"version": "23.0.0-
|
|
3
|
+
"version": "23.0.0-beta5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -33,23 +33,22 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
36
|
-
"@polymer/iron-media-query": "^3.0.0",
|
|
37
36
|
"@polymer/polymer": "^3.2.0",
|
|
38
|
-
"@vaadin/button": "23.0.0-
|
|
39
|
-
"@vaadin/component-base": "23.0.0-
|
|
40
|
-
"@vaadin/field-base": "23.0.0-
|
|
41
|
-
"@vaadin/input-container": "23.0.0-
|
|
42
|
-
"@vaadin/vaadin-lumo-styles": "23.0.0-
|
|
43
|
-
"@vaadin/vaadin-material-styles": "23.0.0-
|
|
44
|
-
"@vaadin/vaadin-overlay": "23.0.0-
|
|
45
|
-
"@vaadin/vaadin-themable-mixin": "23.0.0-
|
|
37
|
+
"@vaadin/button": "23.0.0-beta5",
|
|
38
|
+
"@vaadin/component-base": "23.0.0-beta5",
|
|
39
|
+
"@vaadin/field-base": "23.0.0-beta5",
|
|
40
|
+
"@vaadin/input-container": "23.0.0-beta5",
|
|
41
|
+
"@vaadin/vaadin-lumo-styles": "23.0.0-beta5",
|
|
42
|
+
"@vaadin/vaadin-material-styles": "23.0.0-beta5",
|
|
43
|
+
"@vaadin/vaadin-overlay": "23.0.0-beta5",
|
|
44
|
+
"@vaadin/vaadin-themable-mixin": "23.0.0-beta5"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
47
|
"@esm-bundle/chai": "^4.3.4",
|
|
49
|
-
"@vaadin/dialog": "23.0.0-
|
|
50
|
-
"@vaadin/polymer-legacy-adapter": "23.0.0-
|
|
48
|
+
"@vaadin/dialog": "23.0.0-beta5",
|
|
49
|
+
"@vaadin/polymer-legacy-adapter": "23.0.0-beta5",
|
|
51
50
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
52
51
|
"sinon": "^9.2.0"
|
|
53
52
|
},
|
|
54
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "4c388aeec6623869c70896c909799804fc95d0f9"
|
|
55
54
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
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 '@polymer/iron-media-query/iron-media-query.js';
|
|
7
6
|
import './vaadin-date-picker-overlay.js';
|
|
8
7
|
import './vaadin-date-picker-overlay-content.js';
|
|
9
8
|
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js';
|
|
@@ -98,8 +97,6 @@ class DatePickerLight extends ThemableMixin(DatePickerMixin(PolymerElement)) {
|
|
|
98
97
|
</vaadin-date-picker-overlay-content>
|
|
99
98
|
</template>
|
|
100
99
|
</vaadin-date-picker-overlay>
|
|
101
|
-
|
|
102
|
-
<iron-media-query query="[[_fullscreenMediaQuery]]" query-matches="{{_fullscreen}}"> </iron-media-query>
|
|
103
100
|
`;
|
|
104
101
|
}
|
|
105
102
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
7
7
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
8
|
import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
|
|
9
|
+
import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
|
|
9
10
|
import { DelegateFocusMixin } from '@vaadin/field-base/src/delegate-focus-mixin.js';
|
|
10
11
|
import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
|
|
11
12
|
import { VirtualKeyboardController } from '@vaadin/field-base/src/virtual-keyboard-controller.js';
|
|
@@ -319,11 +320,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
319
320
|
value: document.createElement('div').style.webkitOverflowScrolling === ''
|
|
320
321
|
},
|
|
321
322
|
|
|
322
|
-
/** @private */
|
|
323
|
-
_ignoreAnnounce: {
|
|
324
|
-
value: true
|
|
325
|
-
},
|
|
326
|
-
|
|
327
323
|
/** @private */
|
|
328
324
|
_focusOverlayOnOpen: Boolean,
|
|
329
325
|
|
|
@@ -335,8 +331,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
335
331
|
static get observers() {
|
|
336
332
|
return [
|
|
337
333
|
'_selectedDateChanged(_selectedDate, i18n.formatDate)',
|
|
338
|
-
'_focusedDateChanged(_focusedDate, i18n.formatDate)'
|
|
339
|
-
'_announceFocusedDate(_focusedDate, opened, _ignoreAnnounce)'
|
|
334
|
+
'_focusedDateChanged(_focusedDate, i18n.formatDate)'
|
|
340
335
|
];
|
|
341
336
|
}
|
|
342
337
|
|
|
@@ -423,6 +418,12 @@ export const DatePickerMixin = (subclass) =>
|
|
|
423
418
|
}
|
|
424
419
|
});
|
|
425
420
|
|
|
421
|
+
this.addController(
|
|
422
|
+
new MediaQueryController(this._fullscreenMediaQuery, (matches) => {
|
|
423
|
+
this._fullscreen = matches;
|
|
424
|
+
})
|
|
425
|
+
);
|
|
426
|
+
|
|
426
427
|
this.addController(new VirtualKeyboardController(this));
|
|
427
428
|
}
|
|
428
429
|
|
|
@@ -584,6 +585,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
584
585
|
if (input) {
|
|
585
586
|
input.autocomplete = 'off';
|
|
586
587
|
input.setAttribute('role', 'combobox');
|
|
588
|
+
input.setAttribute('aria-haspopup', 'dialog');
|
|
587
589
|
input.setAttribute('aria-expanded', !!this.opened);
|
|
588
590
|
this._applyInputValue(this._selectedDate);
|
|
589
591
|
}
|
|
@@ -710,7 +712,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
710
712
|
}
|
|
711
713
|
|
|
712
714
|
if (this._focusOverlayOnOpen) {
|
|
713
|
-
this._overlayContent.
|
|
715
|
+
this._overlayContent.focusDateElement();
|
|
714
716
|
this._focusOverlayOnOpen = false;
|
|
715
717
|
} else {
|
|
716
718
|
this._focus();
|
|
@@ -719,8 +721,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
719
721
|
if (this._noInput && this.focusElement) {
|
|
720
722
|
this.focusElement.blur();
|
|
721
723
|
}
|
|
722
|
-
|
|
723
|
-
this._ignoreAnnounce = false;
|
|
724
724
|
}
|
|
725
725
|
|
|
726
726
|
// A hack needed for iOS to prevent dropdown from being clipped in an
|
|
@@ -765,8 +765,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
765
765
|
|
|
766
766
|
/** @protected */
|
|
767
767
|
_onOverlayClosed() {
|
|
768
|
-
this._ignoreAnnounce = true;
|
|
769
|
-
|
|
770
768
|
window.removeEventListener('scroll', this._boundOnScroll, true);
|
|
771
769
|
|
|
772
770
|
if (this._touchPrevented) {
|
|
@@ -891,15 +889,14 @@ export const DatePickerMixin = (subclass) =>
|
|
|
891
889
|
case 'ArrowUp':
|
|
892
890
|
// prevent scrolling the page with arrows
|
|
893
891
|
e.preventDefault();
|
|
894
|
-
|
|
895
892
|
if (this.opened) {
|
|
896
|
-
|
|
897
|
-
this.
|
|
893
|
+
// The overlay can be opened with ctrl + option + shift in VoiceOver
|
|
894
|
+
// and without this logic, it won't be possible to focus the dialog opened this way.
|
|
895
|
+
this._overlayContent.focusDateElement();
|
|
898
896
|
} else {
|
|
899
897
|
this._focusOverlayOnOpen = true;
|
|
900
898
|
this.open();
|
|
901
899
|
}
|
|
902
|
-
|
|
903
900
|
break;
|
|
904
901
|
case 'Enter': {
|
|
905
902
|
const parsedDate = this._getParsedDate();
|
|
@@ -945,8 +942,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
945
942
|
if (e.shiftKey) {
|
|
946
943
|
this._overlayContent.focusCancel();
|
|
947
944
|
} else {
|
|
948
|
-
this._overlayContent.
|
|
949
|
-
this._overlayContent.revealDate(this._focusedDate);
|
|
945
|
+
this._overlayContent.focusDate(this._focusedDate);
|
|
950
946
|
}
|
|
951
947
|
}
|
|
952
948
|
break;
|
|
@@ -994,13 +990,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
994
990
|
}
|
|
995
991
|
}
|
|
996
992
|
|
|
997
|
-
/** @private */
|
|
998
|
-
_announceFocusedDate(_focusedDate, opened, _ignoreAnnounce) {
|
|
999
|
-
if (opened && !_ignoreAnnounce) {
|
|
1000
|
-
this._overlayContent.announceFocusedDate();
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
993
|
/** @private */
|
|
1005
994
|
get _overlayContent() {
|
|
1006
995
|
return this.$.overlay.content.querySelector('#overlay-content');
|
|
@@ -3,24 +3,24 @@
|
|
|
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 '@polymer/iron-media-query/iron-media-query.js';
|
|
7
6
|
import '@vaadin/button/src/vaadin-button.js';
|
|
8
7
|
import './vaadin-month-calendar.js';
|
|
9
8
|
import './vaadin-infinite-scroller.js';
|
|
10
9
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
11
|
-
import { announce } from '@vaadin/component-base/src/a11y-announcer.js';
|
|
12
10
|
import { timeOut } from '@vaadin/component-base/src/async.js';
|
|
11
|
+
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
13
12
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
|
14
13
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
15
14
|
import { addListener, setTouchAction } from '@vaadin/component-base/src/gestures.js';
|
|
15
|
+
import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
|
|
16
16
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
17
|
-
import { dateEquals, extractDateParts, getClosestDate
|
|
17
|
+
import { dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @extends HTMLElement
|
|
21
21
|
* @private
|
|
22
22
|
*/
|
|
23
|
-
class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
23
|
+
class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
24
24
|
static get template() {
|
|
25
25
|
return html`
|
|
26
26
|
<style>
|
|
@@ -150,17 +150,8 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
150
150
|
z-index: 1;
|
|
151
151
|
padding: 8px;
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
#announcer {
|
|
155
|
-
display: inline-block;
|
|
156
|
-
position: fixed;
|
|
157
|
-
clip: rect(0, 0, 0, 0);
|
|
158
|
-
clip-path: inset(100%);
|
|
159
|
-
}
|
|
160
153
|
</style>
|
|
161
154
|
|
|
162
|
-
<div id="announcer" role="alert" aria-live="polite">[[i18n.calendar]]</div>
|
|
163
|
-
|
|
164
155
|
<div part="overlay-header" on-touchend="_preventDefault" desktop$="[[_desktopMode]]" aria-hidden="true">
|
|
165
156
|
<div part="label">[[_formatDisplayed(selectedDate, i18n.formatDate, label)]]</div>
|
|
166
157
|
<div part="clear-button" showclear$="[[_showClear(selectedDate)]]"></div>
|
|
@@ -190,9 +181,9 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
190
181
|
show-week-numbers="[[showWeekNumbers]]"
|
|
191
182
|
min-date="[[minDate]]"
|
|
192
183
|
max-date="[[maxDate]]"
|
|
193
|
-
focused$="[[_focused]]"
|
|
194
184
|
part="month"
|
|
195
185
|
theme$="[[theme]]"
|
|
186
|
+
on-keydown="__onMonthCalendarKeyDown"
|
|
196
187
|
>
|
|
197
188
|
</vaadin-month-calendar>
|
|
198
189
|
</template>
|
|
@@ -209,7 +200,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
209
200
|
<template>
|
|
210
201
|
<div
|
|
211
202
|
part="year-number"
|
|
212
|
-
role="button"
|
|
213
203
|
current$="[[_isCurrentYear(index)]]"
|
|
214
204
|
selected$="[[_isSelectedYear(index, selectedDate)]]"
|
|
215
205
|
>
|
|
@@ -226,12 +216,14 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
226
216
|
part="today-button"
|
|
227
217
|
theme="tertiary"
|
|
228
218
|
disabled="[[!_isTodayAllowed(minDate, maxDate)]]"
|
|
219
|
+
on-keydown="__onTodayButtonKeyDown"
|
|
229
220
|
>
|
|
230
221
|
[[i18n.today]]
|
|
231
222
|
</vaadin-button>
|
|
232
|
-
<vaadin-button id="cancelButton" part="cancel-button" theme="tertiary"
|
|
223
|
+
<vaadin-button id="cancelButton" part="cancel-button" theme="tertiary" on-keydown="__onCancelButtonKeyDown">
|
|
224
|
+
[[i18n.cancel]]
|
|
225
|
+
</vaadin-button>
|
|
233
226
|
</div>
|
|
234
|
-
<iron-media-query query="(min-width: 375px)" query-matches="{{_desktopMode}}"></iron-media-query>
|
|
235
227
|
`;
|
|
236
228
|
}
|
|
237
229
|
|
|
@@ -276,6 +268,11 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
276
268
|
|
|
277
269
|
_desktopMode: Boolean,
|
|
278
270
|
|
|
271
|
+
_desktopMediaQuery: {
|
|
272
|
+
type: String,
|
|
273
|
+
value: '(min-width: 375px)'
|
|
274
|
+
},
|
|
275
|
+
|
|
279
276
|
_translateX: {
|
|
280
277
|
observer: '_translateXChanged'
|
|
281
278
|
},
|
|
@@ -306,8 +303,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
306
303
|
*/
|
|
307
304
|
maxDate: Date,
|
|
308
305
|
|
|
309
|
-
_focused: Boolean,
|
|
310
|
-
|
|
311
306
|
/**
|
|
312
307
|
* Input label
|
|
313
308
|
*/
|
|
@@ -319,13 +314,15 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
319
314
|
return this.getAttribute('dir') === 'rtl';
|
|
320
315
|
}
|
|
321
316
|
|
|
317
|
+
get focusableDateElement() {
|
|
318
|
+
return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')]
|
|
319
|
+
.map((calendar) => calendar.focusableDateElement)
|
|
320
|
+
.find(Boolean);
|
|
321
|
+
}
|
|
322
|
+
|
|
322
323
|
ready() {
|
|
323
324
|
super.ready();
|
|
324
|
-
this.setAttribute('tabindex', 0);
|
|
325
|
-
this.addEventListener('keydown', this._onKeydown.bind(this));
|
|
326
325
|
addListener(this, 'tap', this._stopPropagation);
|
|
327
|
-
this.addEventListener('focus', this._onOverlayFocus.bind(this));
|
|
328
|
-
this.addEventListener('blur', this._onOverlayBlur.bind(this));
|
|
329
326
|
addListener(this.$.scrollers, 'track', this._track.bind(this));
|
|
330
327
|
addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this));
|
|
331
328
|
addListener(this.shadowRoot.querySelector('[part="today-button"]'), 'tap', this._onTodayTap.bind(this));
|
|
@@ -337,6 +334,12 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
337
334
|
'tap',
|
|
338
335
|
this._toggleYearScroller.bind(this)
|
|
339
336
|
);
|
|
337
|
+
|
|
338
|
+
this.addController(
|
|
339
|
+
new MediaQueryController(this._desktopMediaQuery, (matches) => {
|
|
340
|
+
this._desktopMode = matches;
|
|
341
|
+
})
|
|
342
|
+
);
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
/**
|
|
@@ -353,25 +356,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
353
356
|
setTouchAction(this.$.scrollers, 'pan-y');
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
announceFocusedDate() {
|
|
357
|
-
const focusedDate = this._currentlyFocusedDate();
|
|
358
|
-
let messages = [];
|
|
359
|
-
if (dateEquals(focusedDate, new Date())) {
|
|
360
|
-
messages.push(this.i18n.today);
|
|
361
|
-
}
|
|
362
|
-
messages = messages.concat([
|
|
363
|
-
this.i18n.weekdays[focusedDate.getDay()],
|
|
364
|
-
focusedDate.getDate(),
|
|
365
|
-
this.i18n.monthNames[focusedDate.getMonth()],
|
|
366
|
-
focusedDate.getFullYear()
|
|
367
|
-
]);
|
|
368
|
-
if (this.showWeekNumbers && this.i18n.firstDayOfWeek === 1) {
|
|
369
|
-
messages.push(this.i18n.week);
|
|
370
|
-
messages.push(getISOWeekNumber(focusedDate));
|
|
371
|
-
}
|
|
372
|
-
announce(messages.join(' '));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
359
|
/**
|
|
376
360
|
* Focuses the cancel button
|
|
377
361
|
*/
|
|
@@ -423,14 +407,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
423
407
|
}
|
|
424
408
|
}
|
|
425
409
|
|
|
426
|
-
_onOverlayFocus() {
|
|
427
|
-
this._focused = true;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
_onOverlayBlur() {
|
|
431
|
-
this._focused = false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
410
|
_initialPositionChanged(initialPosition) {
|
|
435
411
|
this.scrollToDate(initialPosition);
|
|
436
412
|
}
|
|
@@ -522,6 +498,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
522
498
|
this.$.monthScroller.position = targetPosition;
|
|
523
499
|
this._targetPosition = undefined;
|
|
524
500
|
this._repositionYearScroller();
|
|
501
|
+
this.__tryFocusDate();
|
|
525
502
|
return;
|
|
526
503
|
}
|
|
527
504
|
|
|
@@ -563,6 +540,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
563
540
|
|
|
564
541
|
this.$.monthScroller.position = this._targetPosition;
|
|
565
542
|
this._targetPosition = undefined;
|
|
543
|
+
this.__tryFocusDate();
|
|
566
544
|
}
|
|
567
545
|
|
|
568
546
|
setTimeout(this._repositionYearScroller.bind(this), 1);
|
|
@@ -703,150 +681,168 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
703
681
|
e.preventDefault();
|
|
704
682
|
}
|
|
705
683
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const isCancel = e.composedPath().indexOf(this.$.cancelButton) >= 0;
|
|
713
|
-
const isScroller = !isToday && !isCancel;
|
|
714
|
-
|
|
715
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
|
716
|
-
const navigationKeys = [
|
|
717
|
-
' ',
|
|
718
|
-
'ArrowDown',
|
|
719
|
-
'ArrowUp',
|
|
720
|
-
'ArrowRight',
|
|
721
|
-
'ArrowLeft',
|
|
722
|
-
'Enter',
|
|
723
|
-
'End',
|
|
724
|
-
'Escape',
|
|
725
|
-
'Home',
|
|
726
|
-
'PageUp',
|
|
727
|
-
'PageDown',
|
|
728
|
-
'Tab'
|
|
729
|
-
];
|
|
730
|
-
|
|
731
|
-
const eventKey = e.key;
|
|
732
|
-
if (eventKey === 'Tab') {
|
|
733
|
-
// We handle tabs here and don't want to bubble up.
|
|
734
|
-
e.stopPropagation();
|
|
735
|
-
|
|
736
|
-
const isFullscreen = this.hasAttribute('fullscreen');
|
|
737
|
-
const isShift = e.shiftKey;
|
|
738
|
-
|
|
739
|
-
if (isFullscreen) {
|
|
740
|
-
e.preventDefault();
|
|
741
|
-
} else if ((isShift && isScroller) || (!isShift && isCancel)) {
|
|
742
|
-
// Return focus back to the input field
|
|
743
|
-
e.preventDefault();
|
|
744
|
-
this.dispatchEvent(new CustomEvent('focus-input', { bubbles: true, composed: true }));
|
|
745
|
-
} else if (isShift && isToday) {
|
|
746
|
-
// Browser returns focus back to the scrollable area. We need to set
|
|
747
|
-
// the focused flag, and move the scroll to focused date.
|
|
748
|
-
this._focused = true;
|
|
749
|
-
setTimeout(() => this.revealDate(this.focusedDate), 1);
|
|
750
|
-
} else {
|
|
751
|
-
// Browser moves the focus out of the scroller, hence focused flag must
|
|
752
|
-
// set to false.
|
|
753
|
-
this._focused = false;
|
|
754
|
-
}
|
|
755
|
-
} else if (navigationKeys.includes(eventKey)) {
|
|
756
|
-
e.preventDefault();
|
|
757
|
-
e.stopPropagation();
|
|
758
|
-
switch (eventKey) {
|
|
759
|
-
case 'ArrowDown':
|
|
760
|
-
this._moveFocusByDays(7);
|
|
761
|
-
this.focus();
|
|
762
|
-
break;
|
|
763
|
-
case 'ArrowUp':
|
|
764
|
-
this._moveFocusByDays(-7);
|
|
765
|
-
this.focus();
|
|
766
|
-
break;
|
|
767
|
-
case 'ArrowRight':
|
|
768
|
-
if (isScroller) {
|
|
769
|
-
this._moveFocusByDays(this.__isRTL ? -1 : 1);
|
|
770
|
-
}
|
|
771
|
-
break;
|
|
772
|
-
case 'ArrowLeft':
|
|
773
|
-
if (isScroller) {
|
|
774
|
-
this._moveFocusByDays(this.__isRTL ? 1 : -1);
|
|
775
|
-
}
|
|
776
|
-
break;
|
|
777
|
-
case 'Enter':
|
|
778
|
-
if (isScroller || isCancel) {
|
|
779
|
-
this._close();
|
|
780
|
-
} else if (isToday) {
|
|
781
|
-
this._onTodayTap();
|
|
782
|
-
}
|
|
783
|
-
break;
|
|
784
|
-
case ' ':
|
|
785
|
-
if (isCancel) {
|
|
786
|
-
this._close();
|
|
787
|
-
} else if (isToday) {
|
|
788
|
-
this._onTodayTap();
|
|
789
|
-
} else {
|
|
790
|
-
var focusedDate = this.focusedDate;
|
|
791
|
-
if (dateEquals(focusedDate, this.selectedDate)) {
|
|
792
|
-
this.selectedDate = '';
|
|
793
|
-
this.focusedDate = focusedDate;
|
|
794
|
-
} else {
|
|
795
|
-
this.selectedDate = focusedDate;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
break;
|
|
799
|
-
case 'Home':
|
|
800
|
-
this._moveFocusInsideMonth(focus, 'minDate');
|
|
801
|
-
break;
|
|
802
|
-
case 'End':
|
|
803
|
-
this._moveFocusInsideMonth(focus, 'maxDate');
|
|
804
|
-
break;
|
|
805
|
-
case 'PageDown':
|
|
806
|
-
this._moveFocusByMonths(e.shiftKey ? 12 : 1);
|
|
807
|
-
break;
|
|
808
|
-
case 'PageUp':
|
|
809
|
-
this._moveFocusByMonths(e.shiftKey ? -12 : -1);
|
|
810
|
-
break;
|
|
811
|
-
case 'Escape':
|
|
812
|
-
this._cancel();
|
|
813
|
-
break;
|
|
814
|
-
default:
|
|
815
|
-
break;
|
|
816
|
-
}
|
|
684
|
+
__toggleDate(date) {
|
|
685
|
+
if (dateEquals(date, this.selectedDate)) {
|
|
686
|
+
this.selectedDate = '';
|
|
687
|
+
this.focusedDate = date;
|
|
688
|
+
} else {
|
|
689
|
+
this.selectedDate = date;
|
|
817
690
|
}
|
|
818
691
|
}
|
|
819
692
|
|
|
820
|
-
|
|
821
|
-
|
|
693
|
+
__onMonthCalendarKeyDown(event) {
|
|
694
|
+
let handled = false;
|
|
695
|
+
|
|
696
|
+
switch (event.key) {
|
|
697
|
+
case 'ArrowDown':
|
|
698
|
+
this._moveFocusByDays(7);
|
|
699
|
+
handled = true;
|
|
700
|
+
break;
|
|
701
|
+
case 'ArrowUp':
|
|
702
|
+
this._moveFocusByDays(-7);
|
|
703
|
+
handled = true;
|
|
704
|
+
break;
|
|
705
|
+
case 'ArrowRight':
|
|
706
|
+
this._moveFocusByDays(this.__isRTL ? -1 : 1);
|
|
707
|
+
handled = true;
|
|
708
|
+
break;
|
|
709
|
+
case 'ArrowLeft':
|
|
710
|
+
this._moveFocusByDays(this.__isRTL ? 1 : -1);
|
|
711
|
+
handled = true;
|
|
712
|
+
break;
|
|
713
|
+
case 'Enter':
|
|
714
|
+
this.selectedDate = this.focusedDate;
|
|
715
|
+
this._close();
|
|
716
|
+
handled = true;
|
|
717
|
+
break;
|
|
718
|
+
case ' ':
|
|
719
|
+
this.__toggleDate(this.focusedDate);
|
|
720
|
+
handled = true;
|
|
721
|
+
break;
|
|
722
|
+
case 'Home':
|
|
723
|
+
this._moveFocusInsideMonth(this.focusedDate, 'minDate');
|
|
724
|
+
handled = true;
|
|
725
|
+
break;
|
|
726
|
+
case 'End':
|
|
727
|
+
this._moveFocusInsideMonth(this.focusedDate, 'maxDate');
|
|
728
|
+
handled = true;
|
|
729
|
+
break;
|
|
730
|
+
case 'PageDown':
|
|
731
|
+
this._moveFocusByMonths(event.shiftKey ? 12 : 1);
|
|
732
|
+
handled = true;
|
|
733
|
+
break;
|
|
734
|
+
case 'PageUp':
|
|
735
|
+
this._moveFocusByMonths(event.shiftKey ? -12 : -1);
|
|
736
|
+
handled = true;
|
|
737
|
+
break;
|
|
738
|
+
case 'Escape':
|
|
739
|
+
this._cancel();
|
|
740
|
+
handled = true;
|
|
741
|
+
break;
|
|
742
|
+
default:
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (handled) {
|
|
747
|
+
event.preventDefault();
|
|
748
|
+
event.stopPropagation();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
__onTodayButtonKeyDown(event) {
|
|
753
|
+
if (this.hasAttribute('fullscreen')) {
|
|
754
|
+
event.stopPropagation();
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (event.key === 'Tab' && event.shiftKey) {
|
|
759
|
+
event.stopPropagation();
|
|
760
|
+
|
|
761
|
+
// Browser returns focus back to the calendar.
|
|
762
|
+
// We need to move the scroll to focused date.
|
|
763
|
+
setTimeout(() => this.revealDate(this.focusedDate), 1);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (event.key === 'Escape') {
|
|
767
|
+
this._cancel();
|
|
768
|
+
event.preventDefault();
|
|
769
|
+
event.stopPropagation();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
__onCancelButtonKeyDown(event) {
|
|
774
|
+
if (this.hasAttribute('fullscreen')) {
|
|
775
|
+
event.stopPropagation();
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (event.key === 'Tab' && !event.shiftKey) {
|
|
780
|
+
// Return focus back to the input field
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
event.stopPropagation();
|
|
783
|
+
this.dispatchEvent(new CustomEvent('focus-input', { bubbles: true, composed: true }));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (event.key === 'Escape') {
|
|
787
|
+
this._cancel();
|
|
788
|
+
event.preventDefault();
|
|
789
|
+
event.stopPropagation();
|
|
790
|
+
}
|
|
822
791
|
}
|
|
823
792
|
|
|
824
|
-
|
|
793
|
+
__tryFocusDate() {
|
|
794
|
+
const dateToFocus = this.__pendingDateFocus;
|
|
795
|
+
if (dateToFocus) {
|
|
796
|
+
// Check the date element with tabindex="0"
|
|
797
|
+
const dateElement = this.focusableDateElement;
|
|
798
|
+
|
|
799
|
+
if (dateElement && dateEquals(dateElement.date, this.__pendingDateFocus)) {
|
|
800
|
+
delete this.__pendingDateFocus;
|
|
801
|
+
dateElement.focus();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async focusDate(date, keepMonth) {
|
|
807
|
+
const dateToFocus = date || this.selectedDate || this.initialPosition || new Date();
|
|
825
808
|
this.focusedDate = dateToFocus;
|
|
826
|
-
|
|
809
|
+
if (!keepMonth) {
|
|
810
|
+
this._focusedMonthDate = dateToFocus.getDate();
|
|
811
|
+
}
|
|
812
|
+
await this.focusDateElement();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async focusDateElement() {
|
|
816
|
+
this.__pendingDateFocus = this.focusedDate;
|
|
817
|
+
|
|
818
|
+
await new Promise((resolve) => {
|
|
819
|
+
requestAnimationFrame(resolve);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
this.__tryFocusDate();
|
|
827
823
|
}
|
|
828
824
|
|
|
829
825
|
_focusClosestDate(focus) {
|
|
830
|
-
this.
|
|
826
|
+
this.focusDate(getClosestDate(focus, [this.minDate, this.maxDate]));
|
|
831
827
|
}
|
|
832
828
|
|
|
833
829
|
_moveFocusByDays(days) {
|
|
834
|
-
var focus = this.
|
|
830
|
+
var focus = this.focusedDate;
|
|
835
831
|
var dateToFocus = new Date(0, 0);
|
|
836
832
|
dateToFocus.setFullYear(focus.getFullYear());
|
|
837
833
|
dateToFocus.setMonth(focus.getMonth());
|
|
838
834
|
dateToFocus.setDate(focus.getDate() + days);
|
|
839
835
|
|
|
840
836
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
841
|
-
this.
|
|
837
|
+
this.focusDate(dateToFocus);
|
|
842
838
|
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
|
|
843
839
|
// Move to min or max date
|
|
844
840
|
if (days > 0) {
|
|
845
841
|
// down or right
|
|
846
|
-
this.
|
|
842
|
+
this.focusDate(this.maxDate);
|
|
847
843
|
} else {
|
|
848
844
|
// up or left
|
|
849
|
-
this.
|
|
845
|
+
this.focusDate(this.minDate);
|
|
850
846
|
}
|
|
851
847
|
} else {
|
|
852
848
|
// Move to closest allowed date
|
|
@@ -855,7 +851,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
855
851
|
}
|
|
856
852
|
|
|
857
853
|
_moveFocusByMonths(months) {
|
|
858
|
-
var focus = this.
|
|
854
|
+
var focus = this.focusedDate;
|
|
859
855
|
var dateToFocus = new Date(0, 0);
|
|
860
856
|
dateToFocus.setFullYear(focus.getFullYear());
|
|
861
857
|
dateToFocus.setMonth(focus.getMonth() + months);
|
|
@@ -868,15 +864,15 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
868
864
|
}
|
|
869
865
|
|
|
870
866
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
871
|
-
this.
|
|
867
|
+
this.focusDate(dateToFocus, true);
|
|
872
868
|
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
|
|
873
869
|
// Move to min or max date
|
|
874
870
|
if (months > 0) {
|
|
875
871
|
// pagedown
|
|
876
|
-
this.
|
|
872
|
+
this.focusDate(this.maxDate);
|
|
877
873
|
} else {
|
|
878
874
|
// pageup
|
|
879
|
-
this.
|
|
875
|
+
this.focusDate(this.minDate);
|
|
880
876
|
}
|
|
881
877
|
} else {
|
|
882
878
|
// Move to closest allowed date
|
|
@@ -897,10 +893,10 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
897
893
|
}
|
|
898
894
|
|
|
899
895
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
900
|
-
this.
|
|
896
|
+
this.focusDate(dateToFocus);
|
|
901
897
|
} else if (this._dateAllowed(focusedDate, this.minDate, this.maxDate)) {
|
|
902
898
|
// Move to minDate or maxDate
|
|
903
|
-
this.
|
|
899
|
+
this.focusDate(this[property]);
|
|
904
900
|
} else {
|
|
905
901
|
// Move to closest allowed date
|
|
906
902
|
this._focusClosestDate(focusedDate);
|
|
@@ -13,6 +13,8 @@ registerStyles('vaadin-date-picker-overlay', datePickerOverlayStyles, {
|
|
|
13
13
|
moduleId: 'vaadin-date-picker-overlay-styles'
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
let memoizedTemplate;
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* An element used internally by `<vaadin-date-picker>`. Not intended to be used separately.
|
|
18
20
|
*
|
|
@@ -23,6 +25,15 @@ class DatePickerOverlay extends DisableUpgradeMixin(PositionMixin(OverlayElement
|
|
|
23
25
|
static get is() {
|
|
24
26
|
return 'vaadin-date-picker-overlay';
|
|
25
27
|
}
|
|
28
|
+
|
|
29
|
+
static get template() {
|
|
30
|
+
if (!memoizedTemplate) {
|
|
31
|
+
memoizedTemplate = super.template.cloneNode(true);
|
|
32
|
+
memoizedTemplate.content.querySelector('[part~="overlay"]').removeAttribute('tabindex');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return memoizedTemplate;
|
|
36
|
+
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
customElements.define(DatePickerOverlay.is, DatePickerOverlay);
|
|
@@ -3,13 +3,11 @@
|
|
|
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 '@polymer/iron-media-query/iron-media-query.js';
|
|
7
6
|
import '@vaadin/input-container/src/vaadin-input-container.js';
|
|
8
7
|
import './vaadin-date-picker-overlay.js';
|
|
9
8
|
import './vaadin-date-picker-overlay-content.js';
|
|
10
9
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
11
10
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
12
|
-
import { addListener } from '@vaadin/component-base/src/gestures.js';
|
|
13
11
|
import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js';
|
|
14
12
|
import { InputController } from '@vaadin/field-base/src/input-controller.js';
|
|
15
13
|
import { LabelledInputController } from '@vaadin/field-base/src/labelled-input-controller.js';
|
|
@@ -144,8 +142,8 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
|
|
|
144
142
|
>
|
|
145
143
|
<slot name="prefix" slot="prefix"></slot>
|
|
146
144
|
<slot name="input"></slot>
|
|
147
|
-
<div id="clearButton" part="clear-button" slot="suffix"></div>
|
|
148
|
-
<div part="toggle-button" slot="suffix"
|
|
145
|
+
<div id="clearButton" part="clear-button" slot="suffix" aria-hidden="true"></div>
|
|
146
|
+
<div part="toggle-button" slot="suffix" aria-hidden="true" on-click="_toggle"></div>
|
|
149
147
|
</vaadin-input-container>
|
|
150
148
|
|
|
151
149
|
<div part="helper-text">
|
|
@@ -186,8 +184,6 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
|
|
|
186
184
|
></vaadin-date-picker-overlay-content>
|
|
187
185
|
</template>
|
|
188
186
|
</vaadin-date-picker-overlay>
|
|
189
|
-
|
|
190
|
-
<iron-media-query query="[[_fullscreenMediaQuery]]" query-matches="{{_fullscreen}}"> </iron-media-query>
|
|
191
187
|
`;
|
|
192
188
|
}
|
|
193
189
|
|
|
@@ -213,7 +209,9 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
|
|
|
213
209
|
})
|
|
214
210
|
);
|
|
215
211
|
this.addController(new LabelledInputController(this.inputElement, this._labelController));
|
|
216
|
-
|
|
212
|
+
|
|
213
|
+
const toggleButton = this.shadowRoot.querySelector('[part="toggle-button"]');
|
|
214
|
+
toggleButton.addEventListener('mousedown', (e) => e.preventDefault());
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
/** @private */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
|
7
7
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
8
|
+
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
|
|
8
9
|
import { addListener } from '@vaadin/component-base/src/gestures.js';
|
|
9
10
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
10
11
|
import { dateAllowed, dateEquals, getISOWeekNumber } from './vaadin-date-picker-helper.js';
|
|
@@ -13,7 +14,7 @@ import { dateAllowed, dateEquals, getISOWeekNumber } from './vaadin-date-picker-
|
|
|
13
14
|
* @extends HTMLElement
|
|
14
15
|
* @private
|
|
15
16
|
*/
|
|
16
|
-
class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
17
|
+
class MonthCalendar extends FocusMixin(ThemableMixin(PolymerElement)) {
|
|
17
18
|
static get template() {
|
|
18
19
|
return html`
|
|
19
20
|
<style>
|
|
@@ -21,15 +22,23 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
21
22
|
display: block;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
#monthGrid {
|
|
26
|
+
display: block;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#monthGrid thead,
|
|
30
|
+
#monthGrid tbody {
|
|
31
|
+
display: block;
|
|
32
|
+
width: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[part='weekdays'] {
|
|
26
36
|
display: flex;
|
|
27
|
-
flex-wrap: wrap;
|
|
28
37
|
flex-grow: 1;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
|
-
#days-container,
|
|
32
|
-
#weekdays-container {
|
|
40
|
+
#days-container tr,
|
|
41
|
+
#weekdays-container tr {
|
|
33
42
|
display: flex;
|
|
34
43
|
}
|
|
35
44
|
|
|
@@ -40,6 +49,11 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
40
49
|
flex-shrink: 0;
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
[part='date'] {
|
|
53
|
+
outline: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[part='week-number'][hidden],
|
|
43
57
|
[part='week-numbers'][hidden],
|
|
44
58
|
[part='weekday'][hidden] {
|
|
45
59
|
display: none;
|
|
@@ -47,8 +61,11 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
47
61
|
|
|
48
62
|
[part='weekday'],
|
|
49
63
|
[part='date'] {
|
|
64
|
+
display: block;
|
|
50
65
|
/* Would use calc(100% / 7) but it doesn't work nice on IE */
|
|
51
66
|
width: 14.285714286%;
|
|
67
|
+
padding: 0;
|
|
68
|
+
font-weight: normal;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
[part='weekday']:empty,
|
|
@@ -58,42 +75,59 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
58
75
|
}
|
|
59
76
|
</style>
|
|
60
77
|
|
|
61
|
-
<div part="month-header"
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
<div part="month-header" id="month-header" aria-hidden="true">[[_getTitle(month, i18n.monthNames)]]</div>
|
|
79
|
+
<table
|
|
80
|
+
id="monthGrid"
|
|
81
|
+
role="grid"
|
|
82
|
+
aria-labelledby="month-header"
|
|
83
|
+
on-touchend="_preventDefault"
|
|
84
|
+
on-touchstart="_onMonthGridTouchStart"
|
|
85
|
+
>
|
|
86
|
+
<thead id="weekdays-container">
|
|
87
|
+
<tr role="row" part="weekdays">
|
|
88
|
+
<th
|
|
89
|
+
part="weekday"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]"
|
|
92
|
+
></th>
|
|
66
93
|
<template
|
|
67
94
|
is="dom-repeat"
|
|
68
95
|
items="[[_getWeekDayNames(i18n.weekdays, i18n.weekdaysShort, showWeekNumbers, i18n.firstDayOfWeek)]]"
|
|
69
96
|
>
|
|
70
|
-
<
|
|
71
|
-
</template>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
<div id="days-container">
|
|
75
|
-
<div part="week-numbers" hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]">
|
|
76
|
-
<template is="dom-repeat" items="[[_getWeekNumbers(_days)]]">
|
|
77
|
-
<div part="week-number" role="heading" aria-label$="[[i18n.week]] [[item]]">[[item]]</div>
|
|
78
|
-
</template>
|
|
79
|
-
</div>
|
|
80
|
-
<div id="days">
|
|
81
|
-
<template is="dom-repeat" items="[[_days]]">
|
|
82
|
-
<!-- prettier-ignore -->
|
|
83
|
-
<div
|
|
84
|
-
part="date"
|
|
85
|
-
today$="[[_isToday(item)]]"
|
|
86
|
-
selected$="[[_dateEquals(item, selectedDate)]]"
|
|
87
|
-
focused$="[[_dateEquals(item, focusedDate)]]"
|
|
88
|
-
date="[[item]]"
|
|
89
|
-
disabled$="[[!_dateAllowed(item, minDate, maxDate)]]"
|
|
90
|
-
role$="[[_getRole(item)]]"
|
|
91
|
-
aria-label$="[[_getAriaLabel(item)]]"
|
|
92
|
-
aria-disabled$="[[_getAriaDisabled(item, minDate, maxDate)]]">[[_getDate(item)]]</div>
|
|
97
|
+
<th role="columnheader" part="weekday" scope="col" abbr$="[[item.weekDay]]">[[item.weekDayShort]]</th>
|
|
93
98
|
</template>
|
|
94
|
-
</
|
|
95
|
-
</
|
|
96
|
-
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody id="days-container">
|
|
102
|
+
<template is="dom-repeat" items="[[_weeks]]" as="week">
|
|
103
|
+
<tr role="row">
|
|
104
|
+
<td
|
|
105
|
+
part="week-number"
|
|
106
|
+
aria-hidden="true"
|
|
107
|
+
hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]"
|
|
108
|
+
>
|
|
109
|
+
[[__getWeekNumber(week)]]
|
|
110
|
+
</td>
|
|
111
|
+
<template is="dom-repeat" items="[[week]]">
|
|
112
|
+
<td
|
|
113
|
+
role="gridcell"
|
|
114
|
+
part="date"
|
|
115
|
+
date="[[item]]"
|
|
116
|
+
today$="[[_isToday(item)]]"
|
|
117
|
+
focused$="[[__isDayFocused(item, focusedDate)]]"
|
|
118
|
+
tabindex$="[[__getDayTabindex(item, focusedDate)]]"
|
|
119
|
+
selected$="[[__isDaySelected(item, selectedDate)]]"
|
|
120
|
+
disabled$="[[__isDayDisabled(item, minDate, maxDate)]]"
|
|
121
|
+
aria-selected$="[[__getDayAriaSelected(item, selectedDate)]]"
|
|
122
|
+
aria-disabled$="[[__getDayAriaDisabled(item, minDate, maxDate)]]"
|
|
123
|
+
aria-label$="[[__getDayAriaLabel(item)]]"
|
|
124
|
+
>[[_getDate(item)]]</td
|
|
125
|
+
>
|
|
126
|
+
</template>
|
|
127
|
+
</tr>
|
|
128
|
+
</template>
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
97
131
|
`;
|
|
98
132
|
}
|
|
99
133
|
|
|
@@ -162,6 +196,11 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
162
196
|
computed: '_getDays(month, i18n.firstDayOfWeek, minDate, maxDate)'
|
|
163
197
|
},
|
|
164
198
|
|
|
199
|
+
_weeks: {
|
|
200
|
+
type: Array,
|
|
201
|
+
computed: '_getWeeks(_days)'
|
|
202
|
+
},
|
|
203
|
+
|
|
165
204
|
disabled: {
|
|
166
205
|
type: Boolean,
|
|
167
206
|
reflectToAttribute: true,
|
|
@@ -171,7 +210,10 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
171
210
|
}
|
|
172
211
|
|
|
173
212
|
static get observers() {
|
|
174
|
-
return [
|
|
213
|
+
return [
|
|
214
|
+
'_showWeekNumbersChanged(showWeekNumbers, i18n.firstDayOfWeek)',
|
|
215
|
+
'__focusedDateChanged(focusedDate, _days)'
|
|
216
|
+
];
|
|
175
217
|
}
|
|
176
218
|
|
|
177
219
|
/** @protected */
|
|
@@ -180,12 +222,10 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
180
222
|
addListener(this.$.monthGrid, 'tap', this._handleTap.bind(this));
|
|
181
223
|
}
|
|
182
224
|
|
|
183
|
-
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
_dateAllowed(date, min, max) {
|
|
188
|
-
return dateAllowed(date, min, max);
|
|
225
|
+
get focusableDateElement() {
|
|
226
|
+
return [...this.shadowRoot.querySelectorAll('[part=date]')].find((datePart) => {
|
|
227
|
+
return dateEquals(datePart.date, this.focusedDate);
|
|
228
|
+
});
|
|
189
229
|
}
|
|
190
230
|
|
|
191
231
|
/* Returns true if all the dates in the month are out of the allowed range */
|
|
@@ -212,7 +252,7 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
212
252
|
return false;
|
|
213
253
|
}
|
|
214
254
|
|
|
215
|
-
return !
|
|
255
|
+
return !dateAllowed(firstDate, minDate, maxDate) && !dateAllowed(lastDate, minDate, maxDate);
|
|
216
256
|
}
|
|
217
257
|
|
|
218
258
|
_getTitle(month, monthNames) {
|
|
@@ -260,6 +300,14 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
260
300
|
return weekDayNames;
|
|
261
301
|
}
|
|
262
302
|
|
|
303
|
+
__focusedDateChanged(focusedDate, days) {
|
|
304
|
+
if (days.some((date) => dateEquals(date, focusedDate))) {
|
|
305
|
+
this.removeAttribute('aria-hidden');
|
|
306
|
+
} else {
|
|
307
|
+
this.setAttribute('aria-hidden', 'true');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
263
311
|
_getDate(date) {
|
|
264
312
|
return date ? date.getDate() : '';
|
|
265
313
|
}
|
|
@@ -278,7 +326,7 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
278
326
|
}
|
|
279
327
|
|
|
280
328
|
_isToday(date) {
|
|
281
|
-
return
|
|
329
|
+
return dateEquals(new Date(), date);
|
|
282
330
|
}
|
|
283
331
|
|
|
284
332
|
_getDays(month, firstDayOfWeek) {
|
|
@@ -308,25 +356,14 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
308
356
|
return days;
|
|
309
357
|
}
|
|
310
358
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return !acc && d ? d : acc;
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return getISOWeekNumber(date);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
_getWeekNumbers(dates) {
|
|
327
|
-
return dates
|
|
328
|
-
.map((date) => this._getWeekNumber(date, dates))
|
|
329
|
-
.filter((week, index, arr) => arr.indexOf(week) === index);
|
|
359
|
+
_getWeeks(days) {
|
|
360
|
+
return days.reduce((acc, day, i) => {
|
|
361
|
+
if (i % 7 === 0) {
|
|
362
|
+
acc.push([]);
|
|
363
|
+
}
|
|
364
|
+
acc[acc.length - 1].push(day);
|
|
365
|
+
return acc;
|
|
366
|
+
}, []);
|
|
330
367
|
}
|
|
331
368
|
|
|
332
369
|
_handleTap(e) {
|
|
@@ -340,11 +377,43 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
340
377
|
e.preventDefault();
|
|
341
378
|
}
|
|
342
379
|
|
|
343
|
-
|
|
344
|
-
|
|
380
|
+
__getWeekNumber(days) {
|
|
381
|
+
const date = days.reduce((acc, d) => {
|
|
382
|
+
return !acc && d ? d : acc;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return getISOWeekNumber(date);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
__isDayFocused(date, focusedDate) {
|
|
389
|
+
return dateEquals(date, focusedDate);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
__isDaySelected(date, selectedDate) {
|
|
393
|
+
return dateEquals(date, selectedDate);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
__getDayAriaSelected(date, selectedDate) {
|
|
397
|
+
if (this.__isDaySelected(date, selectedDate)) {
|
|
398
|
+
return 'true';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
__isDayDisabled(date, minDate, maxDate) {
|
|
403
|
+
return !dateAllowed(date, minDate, maxDate);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
__getDayAriaDisabled(date, min, max) {
|
|
407
|
+
if (date === undefined || min === undefined || max === undefined) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (this.__isDayDisabled(date, min, max)) {
|
|
412
|
+
return 'true';
|
|
413
|
+
}
|
|
345
414
|
}
|
|
346
415
|
|
|
347
|
-
|
|
416
|
+
__getDayAriaLabel(date) {
|
|
348
417
|
if (!date) {
|
|
349
418
|
return '';
|
|
350
419
|
}
|
|
@@ -365,11 +434,18 @@ class MonthCalendar extends ThemableMixin(PolymerElement) {
|
|
|
365
434
|
return ariaLabel;
|
|
366
435
|
}
|
|
367
436
|
|
|
368
|
-
|
|
369
|
-
if (date
|
|
370
|
-
return;
|
|
437
|
+
__getDayTabindex(date, focusedDate) {
|
|
438
|
+
if (this.__isDayFocused(date, focusedDate)) {
|
|
439
|
+
return '0';
|
|
371
440
|
}
|
|
372
|
-
|
|
441
|
+
|
|
442
|
+
return '-1';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
__getWeekNumbers(dates) {
|
|
446
|
+
return dates
|
|
447
|
+
.map((date) => this.__getWeekNumber(date, dates))
|
|
448
|
+
.filter((week, index, arr) => arr.indexOf(week) === index);
|
|
373
449
|
}
|
|
374
450
|
}
|
|
375
451
|
|