@vaadin/date-picker 23.0.0-alpha4 → 23.0.0-beta3
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 +15 -25
- package/src/vaadin-date-picker-overlay-content.js +173 -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-beta3",
|
|
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-beta3",
|
|
38
|
+
"@vaadin/component-base": "23.0.0-beta3",
|
|
39
|
+
"@vaadin/field-base": "23.0.0-beta3",
|
|
40
|
+
"@vaadin/input-container": "23.0.0-beta3",
|
|
41
|
+
"@vaadin/vaadin-lumo-styles": "23.0.0-beta3",
|
|
42
|
+
"@vaadin/vaadin-material-styles": "23.0.0-beta3",
|
|
43
|
+
"@vaadin/vaadin-overlay": "23.0.0-beta3",
|
|
44
|
+
"@vaadin/vaadin-themable-mixin": "23.0.0-beta3"
|
|
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-beta3",
|
|
49
|
+
"@vaadin/polymer-legacy-adapter": "23.0.0-beta3",
|
|
51
50
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
52
51
|
"sinon": "^9.2.0"
|
|
53
52
|
},
|
|
54
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "4c87216666541f9eb58f56c475964727822aad53"
|
|
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
|
|
|
@@ -582,7 +583,9 @@ export const DatePickerMixin = (subclass) =>
|
|
|
582
583
|
_inputElementChanged(input) {
|
|
583
584
|
super._inputElementChanged(input);
|
|
584
585
|
if (input) {
|
|
586
|
+
input.autocomplete = 'off';
|
|
585
587
|
input.setAttribute('role', 'combobox');
|
|
588
|
+
input.setAttribute('aria-haspopup', 'dialog');
|
|
586
589
|
input.setAttribute('aria-expanded', !!this.opened);
|
|
587
590
|
this._applyInputValue(this._selectedDate);
|
|
588
591
|
}
|
|
@@ -709,7 +712,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
709
712
|
}
|
|
710
713
|
|
|
711
714
|
if (this._focusOverlayOnOpen) {
|
|
712
|
-
this._overlayContent.
|
|
715
|
+
this._overlayContent.focusDateElement();
|
|
713
716
|
this._focusOverlayOnOpen = false;
|
|
714
717
|
} else {
|
|
715
718
|
this._focus();
|
|
@@ -718,8 +721,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
718
721
|
if (this._noInput && this.focusElement) {
|
|
719
722
|
this.focusElement.blur();
|
|
720
723
|
}
|
|
721
|
-
|
|
722
|
-
this._ignoreAnnounce = false;
|
|
723
724
|
}
|
|
724
725
|
|
|
725
726
|
// A hack needed for iOS to prevent dropdown from being clipped in an
|
|
@@ -764,8 +765,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
764
765
|
|
|
765
766
|
/** @protected */
|
|
766
767
|
_onOverlayClosed() {
|
|
767
|
-
this._ignoreAnnounce = true;
|
|
768
|
-
|
|
769
768
|
window.removeEventListener('scroll', this._boundOnScroll, true);
|
|
770
769
|
|
|
771
770
|
if (this._touchPrevented) {
|
|
@@ -890,15 +889,14 @@ export const DatePickerMixin = (subclass) =>
|
|
|
890
889
|
case 'ArrowUp':
|
|
891
890
|
// prevent scrolling the page with arrows
|
|
892
891
|
e.preventDefault();
|
|
893
|
-
|
|
894
892
|
if (this.opened) {
|
|
895
|
-
|
|
896
|
-
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();
|
|
897
896
|
} else {
|
|
898
897
|
this._focusOverlayOnOpen = true;
|
|
899
898
|
this.open();
|
|
900
899
|
}
|
|
901
|
-
|
|
902
900
|
break;
|
|
903
901
|
case 'Enter': {
|
|
904
902
|
const parsedDate = this._getParsedDate();
|
|
@@ -944,8 +942,7 @@ export const DatePickerMixin = (subclass) =>
|
|
|
944
942
|
if (e.shiftKey) {
|
|
945
943
|
this._overlayContent.focusCancel();
|
|
946
944
|
} else {
|
|
947
|
-
this._overlayContent.
|
|
948
|
-
this._overlayContent.revealDate(this._focusedDate);
|
|
945
|
+
this._overlayContent.focusDate(this._focusedDate);
|
|
949
946
|
}
|
|
950
947
|
}
|
|
951
948
|
break;
|
|
@@ -993,13 +990,6 @@ export const DatePickerMixin = (subclass) =>
|
|
|
993
990
|
}
|
|
994
991
|
}
|
|
995
992
|
|
|
996
|
-
/** @private */
|
|
997
|
-
_announceFocusedDate(_focusedDate, opened, _ignoreAnnounce) {
|
|
998
|
-
if (opened && !_ignoreAnnounce) {
|
|
999
|
-
this._overlayContent.announceFocusedDate();
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
993
|
/** @private */
|
|
1004
994
|
get _overlayContent() {
|
|
1005
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>
|
|
@@ -204,11 +195,11 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
204
195
|
buffer-size="12"
|
|
205
196
|
active="[[initialPosition]]"
|
|
206
197
|
part="years"
|
|
198
|
+
aria-hidden="true"
|
|
207
199
|
>
|
|
208
200
|
<template>
|
|
209
201
|
<div
|
|
210
202
|
part="year-number"
|
|
211
|
-
role="button"
|
|
212
203
|
current$="[[_isCurrentYear(index)]]"
|
|
213
204
|
selected$="[[_isSelectedYear(index, selectedDate)]]"
|
|
214
205
|
>
|
|
@@ -225,12 +216,14 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
225
216
|
part="today-button"
|
|
226
217
|
theme="tertiary"
|
|
227
218
|
disabled="[[!_isTodayAllowed(minDate, maxDate)]]"
|
|
219
|
+
on-keydown="__onTodayButtonKeyDown"
|
|
228
220
|
>
|
|
229
221
|
[[i18n.today]]
|
|
230
222
|
</vaadin-button>
|
|
231
|
-
<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>
|
|
232
226
|
</div>
|
|
233
|
-
<iron-media-query query="(min-width: 375px)" query-matches="{{_desktopMode}}"></iron-media-query>
|
|
234
227
|
`;
|
|
235
228
|
}
|
|
236
229
|
|
|
@@ -275,6 +268,11 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
275
268
|
|
|
276
269
|
_desktopMode: Boolean,
|
|
277
270
|
|
|
271
|
+
_desktopMediaQuery: {
|
|
272
|
+
type: String,
|
|
273
|
+
value: '(min-width: 375px)'
|
|
274
|
+
},
|
|
275
|
+
|
|
278
276
|
_translateX: {
|
|
279
277
|
observer: '_translateXChanged'
|
|
280
278
|
},
|
|
@@ -305,8 +303,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
305
303
|
*/
|
|
306
304
|
maxDate: Date,
|
|
307
305
|
|
|
308
|
-
_focused: Boolean,
|
|
309
|
-
|
|
310
306
|
/**
|
|
311
307
|
* Input label
|
|
312
308
|
*/
|
|
@@ -318,13 +314,15 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
318
314
|
return this.getAttribute('dir') === 'rtl';
|
|
319
315
|
}
|
|
320
316
|
|
|
317
|
+
get focusableDateElement() {
|
|
318
|
+
return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')]
|
|
319
|
+
.map((calendar) => calendar.focusableDateElement)
|
|
320
|
+
.find(Boolean);
|
|
321
|
+
}
|
|
322
|
+
|
|
321
323
|
ready() {
|
|
322
324
|
super.ready();
|
|
323
|
-
this.setAttribute('tabindex', 0);
|
|
324
|
-
this.addEventListener('keydown', this._onKeydown.bind(this));
|
|
325
325
|
addListener(this, 'tap', this._stopPropagation);
|
|
326
|
-
this.addEventListener('focus', this._onOverlayFocus.bind(this));
|
|
327
|
-
this.addEventListener('blur', this._onOverlayBlur.bind(this));
|
|
328
326
|
addListener(this.$.scrollers, 'track', this._track.bind(this));
|
|
329
327
|
addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this));
|
|
330
328
|
addListener(this.shadowRoot.querySelector('[part="today-button"]'), 'tap', this._onTodayTap.bind(this));
|
|
@@ -336,6 +334,12 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
336
334
|
'tap',
|
|
337
335
|
this._toggleYearScroller.bind(this)
|
|
338
336
|
);
|
|
337
|
+
|
|
338
|
+
this.addController(
|
|
339
|
+
new MediaQueryController(this._desktopMediaQuery, (matches) => {
|
|
340
|
+
this._desktopMode = matches;
|
|
341
|
+
})
|
|
342
|
+
);
|
|
339
343
|
}
|
|
340
344
|
|
|
341
345
|
/**
|
|
@@ -352,25 +356,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
352
356
|
setTouchAction(this.$.scrollers, 'pan-y');
|
|
353
357
|
}
|
|
354
358
|
|
|
355
|
-
announceFocusedDate() {
|
|
356
|
-
const focusedDate = this._currentlyFocusedDate();
|
|
357
|
-
let messages = [];
|
|
358
|
-
if (dateEquals(focusedDate, new Date())) {
|
|
359
|
-
messages.push(this.i18n.today);
|
|
360
|
-
}
|
|
361
|
-
messages = messages.concat([
|
|
362
|
-
this.i18n.weekdays[focusedDate.getDay()],
|
|
363
|
-
focusedDate.getDate(),
|
|
364
|
-
this.i18n.monthNames[focusedDate.getMonth()],
|
|
365
|
-
focusedDate.getFullYear()
|
|
366
|
-
]);
|
|
367
|
-
if (this.showWeekNumbers && this.i18n.firstDayOfWeek === 1) {
|
|
368
|
-
messages.push(this.i18n.week);
|
|
369
|
-
messages.push(getISOWeekNumber(focusedDate));
|
|
370
|
-
}
|
|
371
|
-
announce(messages.join(' '));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
359
|
/**
|
|
375
360
|
* Focuses the cancel button
|
|
376
361
|
*/
|
|
@@ -422,14 +407,6 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
422
407
|
}
|
|
423
408
|
}
|
|
424
409
|
|
|
425
|
-
_onOverlayFocus() {
|
|
426
|
-
this._focused = true;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
_onOverlayBlur() {
|
|
430
|
-
this._focused = false;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
410
|
_initialPositionChanged(initialPosition) {
|
|
434
411
|
this.scrollToDate(initialPosition);
|
|
435
412
|
}
|
|
@@ -521,6 +498,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
521
498
|
this.$.monthScroller.position = targetPosition;
|
|
522
499
|
this._targetPosition = undefined;
|
|
523
500
|
this._repositionYearScroller();
|
|
501
|
+
this.__tryFocusDate();
|
|
524
502
|
return;
|
|
525
503
|
}
|
|
526
504
|
|
|
@@ -562,6 +540,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
562
540
|
|
|
563
541
|
this.$.monthScroller.position = this._targetPosition;
|
|
564
542
|
this._targetPosition = undefined;
|
|
543
|
+
this.__tryFocusDate();
|
|
565
544
|
}
|
|
566
545
|
|
|
567
546
|
setTimeout(this._repositionYearScroller.bind(this), 1);
|
|
@@ -702,150 +681,168 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
702
681
|
e.preventDefault();
|
|
703
682
|
}
|
|
704
683
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const isCancel = e.composedPath().indexOf(this.$.cancelButton) >= 0;
|
|
712
|
-
const isScroller = !isToday && !isCancel;
|
|
713
|
-
|
|
714
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
|
715
|
-
const navigationKeys = [
|
|
716
|
-
' ',
|
|
717
|
-
'ArrowDown',
|
|
718
|
-
'ArrowUp',
|
|
719
|
-
'ArrowRight',
|
|
720
|
-
'ArrowLeft',
|
|
721
|
-
'Enter',
|
|
722
|
-
'End',
|
|
723
|
-
'Escape',
|
|
724
|
-
'Home',
|
|
725
|
-
'PageUp',
|
|
726
|
-
'PageDown',
|
|
727
|
-
'Tab'
|
|
728
|
-
];
|
|
729
|
-
|
|
730
|
-
const eventKey = e.key;
|
|
731
|
-
if (eventKey === 'Tab') {
|
|
732
|
-
// We handle tabs here and don't want to bubble up.
|
|
733
|
-
e.stopPropagation();
|
|
734
|
-
|
|
735
|
-
const isFullscreen = this.hasAttribute('fullscreen');
|
|
736
|
-
const isShift = e.shiftKey;
|
|
737
|
-
|
|
738
|
-
if (isFullscreen) {
|
|
739
|
-
e.preventDefault();
|
|
740
|
-
} else if ((isShift && isScroller) || (!isShift && isCancel)) {
|
|
741
|
-
// Return focus back to the input field
|
|
742
|
-
e.preventDefault();
|
|
743
|
-
this.dispatchEvent(new CustomEvent('focus-input', { bubbles: true, composed: true }));
|
|
744
|
-
} else if (isShift && isToday) {
|
|
745
|
-
// Browser returns focus back to the scrollable area. We need to set
|
|
746
|
-
// the focused flag, and move the scroll to focused date.
|
|
747
|
-
this._focused = true;
|
|
748
|
-
setTimeout(() => this.revealDate(this.focusedDate), 1);
|
|
749
|
-
} else {
|
|
750
|
-
// Browser moves the focus out of the scroller, hence focused flag must
|
|
751
|
-
// set to false.
|
|
752
|
-
this._focused = false;
|
|
753
|
-
}
|
|
754
|
-
} else if (navigationKeys.includes(eventKey)) {
|
|
755
|
-
e.preventDefault();
|
|
756
|
-
e.stopPropagation();
|
|
757
|
-
switch (eventKey) {
|
|
758
|
-
case 'ArrowDown':
|
|
759
|
-
this._moveFocusByDays(7);
|
|
760
|
-
this.focus();
|
|
761
|
-
break;
|
|
762
|
-
case 'ArrowUp':
|
|
763
|
-
this._moveFocusByDays(-7);
|
|
764
|
-
this.focus();
|
|
765
|
-
break;
|
|
766
|
-
case 'ArrowRight':
|
|
767
|
-
if (isScroller) {
|
|
768
|
-
this._moveFocusByDays(this.__isRTL ? -1 : 1);
|
|
769
|
-
}
|
|
770
|
-
break;
|
|
771
|
-
case 'ArrowLeft':
|
|
772
|
-
if (isScroller) {
|
|
773
|
-
this._moveFocusByDays(this.__isRTL ? 1 : -1);
|
|
774
|
-
}
|
|
775
|
-
break;
|
|
776
|
-
case 'Enter':
|
|
777
|
-
if (isScroller || isCancel) {
|
|
778
|
-
this._close();
|
|
779
|
-
} else if (isToday) {
|
|
780
|
-
this._onTodayTap();
|
|
781
|
-
}
|
|
782
|
-
break;
|
|
783
|
-
case ' ':
|
|
784
|
-
if (isCancel) {
|
|
785
|
-
this._close();
|
|
786
|
-
} else if (isToday) {
|
|
787
|
-
this._onTodayTap();
|
|
788
|
-
} else {
|
|
789
|
-
var focusedDate = this.focusedDate;
|
|
790
|
-
if (dateEquals(focusedDate, this.selectedDate)) {
|
|
791
|
-
this.selectedDate = '';
|
|
792
|
-
this.focusedDate = focusedDate;
|
|
793
|
-
} else {
|
|
794
|
-
this.selectedDate = focusedDate;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
break;
|
|
798
|
-
case 'Home':
|
|
799
|
-
this._moveFocusInsideMonth(focus, 'minDate');
|
|
800
|
-
break;
|
|
801
|
-
case 'End':
|
|
802
|
-
this._moveFocusInsideMonth(focus, 'maxDate');
|
|
803
|
-
break;
|
|
804
|
-
case 'PageDown':
|
|
805
|
-
this._moveFocusByMonths(e.shiftKey ? 12 : 1);
|
|
806
|
-
break;
|
|
807
|
-
case 'PageUp':
|
|
808
|
-
this._moveFocusByMonths(e.shiftKey ? -12 : -1);
|
|
809
|
-
break;
|
|
810
|
-
case 'Escape':
|
|
811
|
-
this._cancel();
|
|
812
|
-
break;
|
|
813
|
-
default:
|
|
814
|
-
break;
|
|
815
|
-
}
|
|
684
|
+
__toggleDate(date) {
|
|
685
|
+
if (dateEquals(date, this.selectedDate)) {
|
|
686
|
+
this.selectedDate = '';
|
|
687
|
+
this.focusedDate = date;
|
|
688
|
+
} else {
|
|
689
|
+
this.selectedDate = date;
|
|
816
690
|
}
|
|
817
691
|
}
|
|
818
692
|
|
|
819
|
-
|
|
820
|
-
|
|
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
|
+
}
|
|
821
791
|
}
|
|
822
792
|
|
|
823
|
-
|
|
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();
|
|
824
808
|
this.focusedDate = dateToFocus;
|
|
825
|
-
|
|
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();
|
|
826
823
|
}
|
|
827
824
|
|
|
828
825
|
_focusClosestDate(focus) {
|
|
829
|
-
this.
|
|
826
|
+
this.focusDate(getClosestDate(focus, [this.minDate, this.maxDate]));
|
|
830
827
|
}
|
|
831
828
|
|
|
832
829
|
_moveFocusByDays(days) {
|
|
833
|
-
var focus = this.
|
|
830
|
+
var focus = this.focusedDate;
|
|
834
831
|
var dateToFocus = new Date(0, 0);
|
|
835
832
|
dateToFocus.setFullYear(focus.getFullYear());
|
|
836
833
|
dateToFocus.setMonth(focus.getMonth());
|
|
837
834
|
dateToFocus.setDate(focus.getDate() + days);
|
|
838
835
|
|
|
839
836
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
840
|
-
this.
|
|
837
|
+
this.focusDate(dateToFocus);
|
|
841
838
|
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
|
|
842
839
|
// Move to min or max date
|
|
843
840
|
if (days > 0) {
|
|
844
841
|
// down or right
|
|
845
|
-
this.
|
|
842
|
+
this.focusDate(this.maxDate);
|
|
846
843
|
} else {
|
|
847
844
|
// up or left
|
|
848
|
-
this.
|
|
845
|
+
this.focusDate(this.minDate);
|
|
849
846
|
}
|
|
850
847
|
} else {
|
|
851
848
|
// Move to closest allowed date
|
|
@@ -854,7 +851,7 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
854
851
|
}
|
|
855
852
|
|
|
856
853
|
_moveFocusByMonths(months) {
|
|
857
|
-
var focus = this.
|
|
854
|
+
var focus = this.focusedDate;
|
|
858
855
|
var dateToFocus = new Date(0, 0);
|
|
859
856
|
dateToFocus.setFullYear(focus.getFullYear());
|
|
860
857
|
dateToFocus.setMonth(focus.getMonth() + months);
|
|
@@ -867,15 +864,15 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
867
864
|
}
|
|
868
865
|
|
|
869
866
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
870
|
-
this.
|
|
867
|
+
this.focusDate(dateToFocus, true);
|
|
871
868
|
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) {
|
|
872
869
|
// Move to min or max date
|
|
873
870
|
if (months > 0) {
|
|
874
871
|
// pagedown
|
|
875
|
-
this.
|
|
872
|
+
this.focusDate(this.maxDate);
|
|
876
873
|
} else {
|
|
877
874
|
// pageup
|
|
878
|
-
this.
|
|
875
|
+
this.focusDate(this.minDate);
|
|
879
876
|
}
|
|
880
877
|
} else {
|
|
881
878
|
// Move to closest allowed date
|
|
@@ -896,10 +893,10 @@ class DatePickerOverlayContent extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
896
893
|
}
|
|
897
894
|
|
|
898
895
|
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) {
|
|
899
|
-
this.
|
|
896
|
+
this.focusDate(dateToFocus);
|
|
900
897
|
} else if (this._dateAllowed(focusedDate, this.minDate, this.maxDate)) {
|
|
901
898
|
// Move to minDate or maxDate
|
|
902
|
-
this.
|
|
899
|
+
this.focusDate(this[property]);
|
|
903
900
|
} else {
|
|
904
901
|
// Move to closest allowed date
|
|
905
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
|
|