@vaadin/date-picker
Advanced tools
Comparing version 23.0.0-beta1 to 23.0.0-beta2
{ | ||
"name": "@vaadin/date-picker", | ||
"version": "23.0.0-beta1", | ||
"version": "23.0.0-beta2", | ||
"publishConfig": { | ||
@@ -38,19 +38,19 @@ "access": "public" | ||
"@polymer/polymer": "^3.2.0", | ||
"@vaadin/button": "23.0.0-beta1", | ||
"@vaadin/component-base": "23.0.0-beta1", | ||
"@vaadin/field-base": "23.0.0-beta1", | ||
"@vaadin/input-container": "23.0.0-beta1", | ||
"@vaadin/vaadin-lumo-styles": "23.0.0-beta1", | ||
"@vaadin/vaadin-material-styles": "23.0.0-beta1", | ||
"@vaadin/vaadin-overlay": "23.0.0-beta1", | ||
"@vaadin/vaadin-themable-mixin": "23.0.0-beta1" | ||
"@vaadin/button": "23.0.0-beta2", | ||
"@vaadin/component-base": "23.0.0-beta2", | ||
"@vaadin/field-base": "23.0.0-beta2", | ||
"@vaadin/input-container": "23.0.0-beta2", | ||
"@vaadin/vaadin-lumo-styles": "23.0.0-beta2", | ||
"@vaadin/vaadin-material-styles": "23.0.0-beta2", | ||
"@vaadin/vaadin-overlay": "23.0.0-beta2", | ||
"@vaadin/vaadin-themable-mixin": "23.0.0-beta2" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/dialog": "23.0.0-beta1", | ||
"@vaadin/polymer-legacy-adapter": "23.0.0-beta1", | ||
"@vaadin/dialog": "23.0.0-beta2", | ||
"@vaadin/polymer-legacy-adapter": "23.0.0-beta2", | ||
"@vaadin/testing-helpers": "^0.3.2", | ||
"sinon": "^9.2.0" | ||
}, | ||
"gitHead": "467244b76021176c109df675799b07029b293e58" | ||
"gitHead": "a276f7a0fd00e5459b87267468e0dd0d4fb6f7f3" | ||
} |
@@ -323,7 +323,2 @@ /** | ||
/** @private */ | ||
_ignoreAnnounce: { | ||
value: true | ||
}, | ||
/** @private */ | ||
_focusOverlayOnOpen: Boolean, | ||
@@ -339,4 +334,3 @@ | ||
'_selectedDateChanged(_selectedDate, i18n.formatDate)', | ||
'_focusedDateChanged(_focusedDate, i18n.formatDate)', | ||
'_announceFocusedDate(_focusedDate, opened, _ignoreAnnounce)' | ||
'_focusedDateChanged(_focusedDate, i18n.formatDate)' | ||
]; | ||
@@ -589,2 +583,3 @@ } | ||
input.setAttribute('role', 'combobox'); | ||
input.setAttribute('aria-haspopup', 'dialog'); | ||
input.setAttribute('aria-expanded', !!this.opened); | ||
@@ -716,3 +711,3 @@ this._applyInputValue(this._selectedDate); | ||
if (this._focusOverlayOnOpen) { | ||
this._overlayContent.focus(); | ||
this._overlayContent.focusDateElement(); | ||
this._focusOverlayOnOpen = false; | ||
@@ -726,4 +721,2 @@ } else { | ||
} | ||
this._ignoreAnnounce = false; | ||
} | ||
@@ -773,4 +766,2 @@ | ||
_onOverlayClosed() { | ||
this._ignoreAnnounce = true; | ||
window.removeEventListener('scroll', this._boundOnScroll, true); | ||
@@ -900,6 +891,6 @@ | ||
e.preventDefault(); | ||
if (this.opened) { | ||
this._overlayContent.focus(); | ||
this._overlayContent._onKeydown(e); | ||
// The overlay can be opened with ctrl + option + shift in VoiceOver | ||
// and without this logic, it won't be possible to focus the dialog opened this way. | ||
this._overlayContent.focusDateElement(); | ||
} else { | ||
@@ -909,3 +900,2 @@ this._focusOverlayOnOpen = true; | ||
} | ||
break; | ||
@@ -956,4 +946,3 @@ case 'Enter': { | ||
} else { | ||
this._overlayContent.focus(); | ||
this._overlayContent.revealDate(this._focusedDate); | ||
this._overlayContent.focusDate(this._focusedDate); | ||
} | ||
@@ -1007,9 +996,2 @@ } | ||
/** @private */ | ||
_announceFocusedDate(_focusedDate, opened, _ignoreAnnounce) { | ||
if (opened && !_ignoreAnnounce) { | ||
this._overlayContent.announceFocusedDate(); | ||
} | ||
} | ||
/** @private */ | ||
get _overlayContent() { | ||
@@ -1016,0 +998,0 @@ return this.$.overlay.content.querySelector('#overlay-content'); |
@@ -11,3 +11,2 @@ /** | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { announce } from '@vaadin/component-base/src/a11y-announcer.js'; | ||
import { timeOut } from '@vaadin/component-base/src/async.js'; | ||
@@ -18,3 +17,3 @@ import { Debouncer } from '@vaadin/component-base/src/debounce.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { dateEquals, extractDateParts, getClosestDate, getISOWeekNumber } from './vaadin-date-picker-helper.js'; | ||
import { dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js'; | ||
@@ -155,13 +154,4 @@ /** | ||
} | ||
#announcer { | ||
display: inline-block; | ||
position: fixed; | ||
clip: rect(0, 0, 0, 0); | ||
clip-path: inset(100%); | ||
} | ||
</style> | ||
<div id="announcer" role="alert" aria-live="polite">[[i18n.calendar]]</div> | ||
<div part="overlay-header" on-touchend="_preventDefault" desktop$="[[_desktopMode]]" aria-hidden="true"> | ||
@@ -196,5 +186,5 @@ <div part="label">[[_formatDisplayed(selectedDate, i18n.formatDate, label)]]</div> | ||
max-date="[[maxDate]]" | ||
focused$="[[_focused]]" | ||
part="month" | ||
theme$="[[theme]]" | ||
on-keydown="__onMonthCalendarKeyDown" | ||
> | ||
@@ -216,3 +206,2 @@ </vaadin-month-calendar> | ||
part="year-number" | ||
role="button" | ||
current$="[[_isCurrentYear(index)]]" | ||
@@ -234,6 +223,9 @@ selected$="[[_isSelectedYear(index, selectedDate)]]" | ||
disabled="[[!_isTodayAllowed(minDate, maxDate)]]" | ||
on-keydown="__onTodayButtonKeyDown" | ||
> | ||
[[i18n.today]] | ||
</vaadin-button> | ||
<vaadin-button id="cancelButton" part="cancel-button" theme="tertiary"> [[i18n.cancel]] </vaadin-button> | ||
<vaadin-button id="cancelButton" part="cancel-button" theme="tertiary" on-keydown="__onCancelButtonKeyDown"> | ||
[[i18n.cancel]] | ||
</vaadin-button> | ||
</div> | ||
@@ -315,4 +307,2 @@ <iron-media-query query="(min-width: 375px)" query-matches="{{_desktopMode}}"></iron-media-query> | ||
_focused: Boolean, | ||
/** | ||
@@ -329,9 +319,11 @@ * Input label | ||
get focusableDateElement() { | ||
return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')] | ||
.map((calendar) => calendar.focusableDateElement) | ||
.find(Boolean); | ||
} | ||
ready() { | ||
super.ready(); | ||
this.setAttribute('tabindex', 0); | ||
this.addEventListener('keydown', this._onKeydown.bind(this)); | ||
addListener(this, 'tap', this._stopPropagation); | ||
this.addEventListener('focus', this._onOverlayFocus.bind(this)); | ||
this.addEventListener('blur', this._onOverlayBlur.bind(this)); | ||
addListener(this.$.scrollers, 'track', this._track.bind(this)); | ||
@@ -364,21 +356,2 @@ addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this)); | ||
announceFocusedDate() { | ||
const focusedDate = this._currentlyFocusedDate(); | ||
let messages = []; | ||
if (dateEquals(focusedDate, new Date())) { | ||
messages.push(this.i18n.today); | ||
} | ||
messages = messages.concat([ | ||
this.i18n.weekdays[focusedDate.getDay()], | ||
focusedDate.getDate(), | ||
this.i18n.monthNames[focusedDate.getMonth()], | ||
focusedDate.getFullYear() | ||
]); | ||
if (this.showWeekNumbers && this.i18n.firstDayOfWeek === 1) { | ||
messages.push(this.i18n.week); | ||
messages.push(getISOWeekNumber(focusedDate)); | ||
} | ||
announce(messages.join(' ')); | ||
} | ||
/** | ||
@@ -435,10 +408,2 @@ * Focuses the cancel button | ||
_onOverlayFocus() { | ||
this._focused = true; | ||
} | ||
_onOverlayBlur() { | ||
this._focused = false; | ||
} | ||
_initialPositionChanged(initialPosition) { | ||
@@ -535,2 +500,3 @@ this.scrollToDate(initialPosition); | ||
this._repositionYearScroller(); | ||
this.__tryFocusDate(); | ||
return; | ||
@@ -577,2 +543,3 @@ } | ||
this._targetPosition = undefined; | ||
this.__tryFocusDate(); | ||
} | ||
@@ -718,131 +685,149 @@ | ||
_onKeydown(e) { | ||
var focus = this._currentlyFocusedDate(); | ||
__toggleDate(date) { | ||
if (dateEquals(date, this.selectedDate)) { | ||
this.selectedDate = ''; | ||
this.focusedDate = date; | ||
} else { | ||
this.selectedDate = date; | ||
} | ||
} | ||
// Cannot use (today/cancel).focused flag because vaadin-text-field removes it | ||
// previously in the keydown event. | ||
const isToday = e.composedPath().indexOf(this.$.todayButton) >= 0; | ||
const isCancel = e.composedPath().indexOf(this.$.cancelButton) >= 0; | ||
const isScroller = !isToday && !isCancel; | ||
__onMonthCalendarKeyDown(event) { | ||
let handled = false; | ||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | ||
const navigationKeys = [ | ||
' ', | ||
'ArrowDown', | ||
'ArrowUp', | ||
'ArrowRight', | ||
'ArrowLeft', | ||
'Enter', | ||
'End', | ||
'Escape', | ||
'Home', | ||
'PageUp', | ||
'PageDown', | ||
'Tab' | ||
]; | ||
switch (event.key) { | ||
case 'ArrowDown': | ||
this._moveFocusByDays(7); | ||
handled = true; | ||
break; | ||
case 'ArrowUp': | ||
this._moveFocusByDays(-7); | ||
handled = true; | ||
break; | ||
case 'ArrowRight': | ||
this._moveFocusByDays(this.__isRTL ? -1 : 1); | ||
handled = true; | ||
break; | ||
case 'ArrowLeft': | ||
this._moveFocusByDays(this.__isRTL ? 1 : -1); | ||
handled = true; | ||
break; | ||
case 'Enter': | ||
this.selectedDate = this.focusedDate; | ||
this._close(); | ||
handled = true; | ||
break; | ||
case ' ': | ||
this.__toggleDate(this.focusedDate); | ||
handled = true; | ||
break; | ||
case 'Home': | ||
this._moveFocusInsideMonth(this.focusedDate, 'minDate'); | ||
handled = true; | ||
break; | ||
case 'End': | ||
this._moveFocusInsideMonth(this.focusedDate, 'maxDate'); | ||
handled = true; | ||
break; | ||
case 'PageDown': | ||
this._moveFocusByMonths(event.shiftKey ? 12 : 1); | ||
handled = true; | ||
break; | ||
case 'PageUp': | ||
this._moveFocusByMonths(event.shiftKey ? -12 : -1); | ||
handled = true; | ||
break; | ||
case 'Escape': | ||
this._cancel(); | ||
handled = true; | ||
break; | ||
default: | ||
break; | ||
} | ||
const eventKey = e.key; | ||
if (eventKey === 'Tab') { | ||
// We handle tabs here and don't want to bubble up. | ||
e.stopPropagation(); | ||
if (handled) { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
} | ||
} | ||
const isFullscreen = this.hasAttribute('fullscreen'); | ||
const isShift = e.shiftKey; | ||
__onTodayButtonKeyDown(event) { | ||
if (this.hasAttribute('fullscreen')) { | ||
event.stopPropagation(); | ||
return; | ||
} | ||
if (isFullscreen) { | ||
e.preventDefault(); | ||
} else if ((isShift && isScroller) || (!isShift && isCancel)) { | ||
// Return focus back to the input field | ||
e.preventDefault(); | ||
this.dispatchEvent(new CustomEvent('focus-input', { bubbles: true, composed: true })); | ||
} else if (isShift && isToday) { | ||
// Browser returns focus back to the scrollable area. We need to set | ||
// the focused flag, and move the scroll to focused date. | ||
this._focused = true; | ||
setTimeout(() => this.revealDate(this.focusedDate), 1); | ||
} else { | ||
// Browser moves the focus out of the scroller, hence focused flag must | ||
// set to false. | ||
this._focused = false; | ||
} | ||
} else if (navigationKeys.includes(eventKey)) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
switch (eventKey) { | ||
case 'ArrowDown': | ||
this._moveFocusByDays(7); | ||
this.focus(); | ||
break; | ||
case 'ArrowUp': | ||
this._moveFocusByDays(-7); | ||
this.focus(); | ||
break; | ||
case 'ArrowRight': | ||
if (isScroller) { | ||
this._moveFocusByDays(this.__isRTL ? -1 : 1); | ||
} | ||
break; | ||
case 'ArrowLeft': | ||
if (isScroller) { | ||
this._moveFocusByDays(this.__isRTL ? 1 : -1); | ||
} | ||
break; | ||
case 'Enter': | ||
if (isScroller || isCancel) { | ||
this._close(); | ||
} else if (isToday) { | ||
this._onTodayTap(); | ||
} | ||
break; | ||
case ' ': | ||
if (isCancel) { | ||
this._close(); | ||
} else if (isToday) { | ||
this._onTodayTap(); | ||
} else { | ||
var focusedDate = this.focusedDate; | ||
if (dateEquals(focusedDate, this.selectedDate)) { | ||
this.selectedDate = ''; | ||
this.focusedDate = focusedDate; | ||
} else { | ||
this.selectedDate = focusedDate; | ||
} | ||
} | ||
break; | ||
case 'Home': | ||
this._moveFocusInsideMonth(focus, 'minDate'); | ||
break; | ||
case 'End': | ||
this._moveFocusInsideMonth(focus, 'maxDate'); | ||
break; | ||
case 'PageDown': | ||
this._moveFocusByMonths(e.shiftKey ? 12 : 1); | ||
break; | ||
case 'PageUp': | ||
this._moveFocusByMonths(e.shiftKey ? -12 : -1); | ||
break; | ||
case 'Escape': | ||
this._cancel(); | ||
break; | ||
default: | ||
break; | ||
} | ||
if (event.key === 'Tab' && event.shiftKey) { | ||
event.stopPropagation(); | ||
// Browser returns focus back to the calendar. | ||
// We need to move the scroll to focused date. | ||
setTimeout(() => this.revealDate(this.focusedDate), 1); | ||
} | ||
if (event.key === 'Escape') { | ||
this._cancel(); | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
} | ||
} | ||
_currentlyFocusedDate() { | ||
return this.focusedDate || this.selectedDate || this.initialPosition || new Date(); | ||
__onCancelButtonKeyDown(event) { | ||
if (this.hasAttribute('fullscreen')) { | ||
event.stopPropagation(); | ||
return; | ||
} | ||
if (event.key === 'Tab' && !event.shiftKey) { | ||
// Return focus back to the input field | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
this.dispatchEvent(new CustomEvent('focus-input', { bubbles: true, composed: true })); | ||
} | ||
if (event.key === 'Escape') { | ||
this._cancel(); | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
} | ||
} | ||
_focusDate(dateToFocus) { | ||
__tryFocusDate() { | ||
const dateToFocus = this.__pendingDateFocus; | ||
if (dateToFocus) { | ||
// Check the date element with tabindex="0" | ||
const dateElement = this.focusableDateElement; | ||
if (dateElement && dateEquals(dateElement.date, this.__pendingDateFocus)) { | ||
delete this.__pendingDateFocus; | ||
dateElement.focus(); | ||
} | ||
} | ||
} | ||
async focusDate(date, keepMonth) { | ||
const dateToFocus = date || this.selectedDate || this.initialPosition || new Date(); | ||
this.focusedDate = dateToFocus; | ||
this._focusedMonthDate = dateToFocus.getDate(); | ||
if (!keepMonth) { | ||
this._focusedMonthDate = dateToFocus.getDate(); | ||
} | ||
await this.focusDateElement(); | ||
} | ||
async focusDateElement() { | ||
this.__pendingDateFocus = this.focusedDate; | ||
await new Promise((resolve) => { | ||
requestAnimationFrame(resolve); | ||
}); | ||
this.__tryFocusDate(); | ||
} | ||
_focusClosestDate(focus) { | ||
this._focusDate(getClosestDate(focus, [this.minDate, this.maxDate])); | ||
this.focusDate(getClosestDate(focus, [this.minDate, this.maxDate])); | ||
} | ||
_moveFocusByDays(days) { | ||
var focus = this._currentlyFocusedDate(); | ||
var focus = this.focusedDate; | ||
var dateToFocus = new Date(0, 0); | ||
@@ -854,3 +839,3 @@ dateToFocus.setFullYear(focus.getFullYear()); | ||
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) { | ||
this._focusDate(dateToFocus); | ||
this.focusDate(dateToFocus); | ||
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) { | ||
@@ -860,6 +845,6 @@ // Move to min or max date | ||
// down or right | ||
this._focusDate(this.maxDate); | ||
this.focusDate(this.maxDate); | ||
} else { | ||
// up or left | ||
this._focusDate(this.minDate); | ||
this.focusDate(this.minDate); | ||
} | ||
@@ -873,3 +858,3 @@ } else { | ||
_moveFocusByMonths(months) { | ||
var focus = this._currentlyFocusedDate(); | ||
var focus = this.focusedDate; | ||
var dateToFocus = new Date(0, 0); | ||
@@ -887,3 +872,3 @@ dateToFocus.setFullYear(focus.getFullYear()); | ||
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) { | ||
this.focusedDate = dateToFocus; | ||
this.focusDate(dateToFocus, true); | ||
} else if (this._dateAllowed(focus, this.minDate, this.maxDate)) { | ||
@@ -893,6 +878,6 @@ // Move to min or max date | ||
// pagedown | ||
this._focusDate(this.maxDate); | ||
this.focusDate(this.maxDate); | ||
} else { | ||
// pageup | ||
this._focusDate(this.minDate); | ||
this.focusDate(this.minDate); | ||
} | ||
@@ -918,6 +903,6 @@ } else { | ||
if (this._dateAllowed(dateToFocus, this.minDate, this.maxDate)) { | ||
this._focusDate(dateToFocus); | ||
this.focusDate(dateToFocus); | ||
} else if (this._dateAllowed(focusedDate, this.minDate, this.maxDate)) { | ||
// Move to minDate or maxDate | ||
this._focusDate(this[property]); | ||
this.focusDate(this[property]); | ||
} else { | ||
@@ -924,0 +909,0 @@ // Move to closest allowed date |
@@ -16,2 +16,4 @@ /** | ||
let memoizedTemplate; | ||
/** | ||
@@ -27,4 +29,13 @@ * An element used internally by `<vaadin-date-picker>`. Not intended to be used separately. | ||
} | ||
static get template() { | ||
if (!memoizedTemplate) { | ||
memoizedTemplate = super.template.cloneNode(true); | ||
memoizedTemplate.content.querySelector('[part~="overlay"]').removeAttribute('tabindex'); | ||
} | ||
return memoizedTemplate; | ||
} | ||
} | ||
customElements.define(DatePickerOverlay.is, DatePickerOverlay); |
@@ -12,3 +12,2 @@ /** | ||
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; | ||
import { addListener } from '@vaadin/component-base/src/gestures.js'; | ||
import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js'; | ||
@@ -148,4 +147,4 @@ import { InputController } from '@vaadin/field-base/src/input-controller.js'; | ||
<slot name="input"></slot> | ||
<div id="clearButton" part="clear-button" slot="suffix"></div> | ||
<div part="toggle-button" slot="suffix" role="button"></div> | ||
<div id="clearButton" part="clear-button" slot="suffix" aria-hidden="true"></div> | ||
<div part="toggle-button" slot="suffix" aria-hidden="true" on-click="_toggle"></div> | ||
</vaadin-input-container> | ||
@@ -218,3 +217,5 @@ | ||
this.addController(new LabelledInputController(this.inputElement, this._labelController)); | ||
addListener(this.shadowRoot.querySelector('[part="toggle-button"]'), 'tap', this._toggle.bind(this)); | ||
const toggleButton = this.shadowRoot.querySelector('[part="toggle-button"]'); | ||
toggleButton.addEventListener('mousedown', (e) => e.preventDefault()); | ||
} | ||
@@ -221,0 +222,0 @@ |
@@ -8,2 +8,3 @@ /** | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js'; | ||
import { addListener } from '@vaadin/component-base/src/gestures.js'; | ||
@@ -17,3 +18,3 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
*/ | ||
class MonthCalendar extends ThemableMixin(PolymerElement) { | ||
class MonthCalendar extends FocusMixin(ThemableMixin(PolymerElement)) { | ||
static get template() { | ||
@@ -26,11 +27,19 @@ return html` | ||
[part='weekdays'], | ||
#days { | ||
#monthGrid { | ||
display: block; | ||
} | ||
#monthGrid thead, | ||
#monthGrid tbody { | ||
display: block; | ||
width: 100%; | ||
} | ||
[part='weekdays'] { | ||
display: flex; | ||
flex-wrap: wrap; | ||
flex-grow: 1; | ||
} | ||
#days-container, | ||
#weekdays-container { | ||
#days-container tr, | ||
#weekdays-container tr { | ||
display: flex; | ||
@@ -46,2 +55,7 @@ } | ||
[part='date'] { | ||
outline: none; | ||
} | ||
[part='week-number'][hidden], | ||
[part='week-numbers'][hidden], | ||
@@ -54,4 +68,7 @@ [part='weekday'][hidden] { | ||
[part='date'] { | ||
display: block; | ||
/* Would use calc(100% / 7) but it doesn't work nice on IE */ | ||
width: 14.285714286%; | ||
padding: 0; | ||
font-weight: normal; | ||
} | ||
@@ -66,7 +83,17 @@ | ||
<div part="month-header" role="heading">[[_getTitle(month, i18n.monthNames)]]</div> | ||
<div id="monthGrid" on-touchend="_preventDefault" on-touchstart="_onMonthGridTouchStart"> | ||
<div id="weekdays-container"> | ||
<div hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]" part="weekday"></div> | ||
<div part="weekdays"> | ||
<div part="month-header" id="month-header" aria-hidden="true">[[_getTitle(month, i18n.monthNames)]]</div> | ||
<table | ||
id="monthGrid" | ||
role="grid" | ||
aria-labelledby="month-header" | ||
on-touchend="_preventDefault" | ||
on-touchstart="_onMonthGridTouchStart" | ||
> | ||
<thead id="weekdays-container"> | ||
<tr role="row" part="weekdays"> | ||
<th | ||
part="weekday" | ||
aria-hidden="true" | ||
hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]" | ||
></th> | ||
<template | ||
@@ -76,29 +103,36 @@ is="dom-repeat" | ||
> | ||
<div part="weekday" role="heading" aria-label$="[[item.weekDay]]">[[item.weekDayShort]]</div> | ||
<th role="columnheader" part="weekday" scope="col" abbr$="[[item.weekDay]]">[[item.weekDayShort]]</th> | ||
</template> | ||
</div> | ||
</div> | ||
<div id="days-container"> | ||
<div part="week-numbers" hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]"> | ||
<template is="dom-repeat" items="[[_getWeekNumbers(_days)]]"> | ||
<div part="week-number" role="heading" aria-label$="[[i18n.week]] [[item]]">[[item]]</div> | ||
</template> | ||
</div> | ||
<div id="days"> | ||
<template is="dom-repeat" items="[[_days]]"> | ||
<!-- prettier-ignore --> | ||
<div | ||
part="date" | ||
today$="[[_isToday(item)]]" | ||
selected$="[[_dateEquals(item, selectedDate)]]" | ||
focused$="[[_dateEquals(item, focusedDate)]]" | ||
date="[[item]]" | ||
disabled$="[[!_dateAllowed(item, minDate, maxDate)]]" | ||
role$="[[_getRole(item)]]" | ||
aria-label$="[[_getAriaLabel(item)]]" | ||
aria-disabled$="[[_getAriaDisabled(item, minDate, maxDate)]]">[[_getDate(item)]]</div> | ||
</template> | ||
</div> | ||
</div> | ||
</div> | ||
</tr> | ||
</thead> | ||
<tbody id="days-container"> | ||
<template is="dom-repeat" items="[[_weeks]]" as="week"> | ||
<tr role="row"> | ||
<td | ||
part="week-number" | ||
aria-hidden="true" | ||
hidden$="[[!_showWeekSeparator(showWeekNumbers, i18n.firstDayOfWeek)]]" | ||
> | ||
[[__getWeekNumber(week)]] | ||
</td> | ||
<template is="dom-repeat" items="[[week]]"> | ||
<td | ||
role="gridcell" | ||
part="date" | ||
date="[[item]]" | ||
today$="[[_isToday(item)]]" | ||
focused$="[[__isDayFocused(item, focusedDate)]]" | ||
tabindex$="[[__getDayTabindex(item, focusedDate)]]" | ||
selected$="[[__isDaySelected(item, selectedDate)]]" | ||
disabled$="[[__isDayDisabled(item, minDate, maxDate)]]" | ||
aria-selected$="[[__getDayAriaSelected(item, selectedDate)]]" | ||
aria-disabled$="[[__getDayAriaDisabled(item, minDate, maxDate)]]" | ||
aria-label$="[[__getDayAriaLabel(item)]]" | ||
>[[_getDate(item)]]</td | ||
> | ||
</template> | ||
</tr> | ||
</template> | ||
</tbody> | ||
</table> | ||
`; | ||
@@ -172,2 +206,7 @@ } | ||
_weeks: { | ||
type: Array, | ||
computed: '_getWeeks(_days)' | ||
}, | ||
disabled: { | ||
@@ -182,3 +221,6 @@ type: Boolean, | ||
static get observers() { | ||
return ['_showWeekNumbersChanged(showWeekNumbers, i18n.firstDayOfWeek)']; | ||
return [ | ||
'_showWeekNumbersChanged(showWeekNumbers, i18n.firstDayOfWeek)', | ||
'__focusedDateChanged(focusedDate, _days)' | ||
]; | ||
} | ||
@@ -192,10 +234,8 @@ | ||
_dateEquals(date1, date2) { | ||
return dateEquals(date1, date2); | ||
get focusableDateElement() { | ||
return [...this.shadowRoot.querySelectorAll('[part=date]')].find((datePart) => { | ||
return dateEquals(datePart.date, this.focusedDate); | ||
}); | ||
} | ||
_dateAllowed(date, min, max) { | ||
return dateAllowed(date, min, max); | ||
} | ||
/* Returns true if all the dates in the month are out of the allowed range */ | ||
@@ -225,3 +265,3 @@ _isDisabled(month, minDate, maxDate) { | ||
return !this._dateAllowed(firstDate, minDate, maxDate) && !this._dateAllowed(lastDate, minDate, maxDate); | ||
return !dateAllowed(firstDate, minDate, maxDate) && !dateAllowed(lastDate, minDate, maxDate); | ||
} | ||
@@ -274,2 +314,10 @@ | ||
__focusedDateChanged(focusedDate, days) { | ||
if (days.some((date) => dateEquals(date, focusedDate))) { | ||
this.removeAttribute('aria-hidden'); | ||
} else { | ||
this.setAttribute('aria-hidden', 'true'); | ||
} | ||
} | ||
_getDate(date) { | ||
@@ -293,3 +341,3 @@ return date ? date.getDate() : ''; | ||
_isToday(date) { | ||
return this._dateEquals(new Date(), date); | ||
return dateEquals(new Date(), date); | ||
} | ||
@@ -324,23 +372,12 @@ | ||
_getWeekNumber(date, days) { | ||
if (date === undefined || days === undefined) { | ||
return; | ||
} | ||
if (!date) { | ||
// Get the first non-null date from the days array. | ||
date = days.reduce((acc, d) => { | ||
return !acc && d ? d : acc; | ||
}); | ||
} | ||
return getISOWeekNumber(date); | ||
_getWeeks(days) { | ||
return days.reduce((acc, day, i) => { | ||
if (i % 7 === 0) { | ||
acc.push([]); | ||
} | ||
acc[acc.length - 1].push(day); | ||
return acc; | ||
}, []); | ||
} | ||
_getWeekNumbers(dates) { | ||
return dates | ||
.map((date) => this._getWeekNumber(date, dates)) | ||
.filter((week, index, arr) => arr.indexOf(week) === index); | ||
} | ||
_handleTap(e) { | ||
@@ -357,7 +394,39 @@ if (!this.ignoreTaps && !this._notTapping && e.target.date && !e.target.hasAttribute('disabled')) { | ||
_getRole(date) { | ||
return date ? 'button' : 'presentation'; | ||
__getWeekNumber(days) { | ||
const date = days.reduce((acc, d) => { | ||
return !acc && d ? d : acc; | ||
}); | ||
return getISOWeekNumber(date); | ||
} | ||
_getAriaLabel(date) { | ||
__isDayFocused(date, focusedDate) { | ||
return dateEquals(date, focusedDate); | ||
} | ||
__isDaySelected(date, selectedDate) { | ||
return dateEquals(date, selectedDate); | ||
} | ||
__getDayAriaSelected(date, selectedDate) { | ||
if (this.__isDaySelected(date, selectedDate)) { | ||
return 'true'; | ||
} | ||
} | ||
__isDayDisabled(date, minDate, maxDate) { | ||
return !dateAllowed(date, minDate, maxDate); | ||
} | ||
__getDayAriaDisabled(date, min, max) { | ||
if (date === undefined || min === undefined || max === undefined) { | ||
return; | ||
} | ||
if (this.__isDayDisabled(date, min, max)) { | ||
return 'true'; | ||
} | ||
} | ||
__getDayAriaLabel(date) { | ||
if (!date) { | ||
@@ -383,10 +452,17 @@ return ''; | ||
_getAriaDisabled(date, min, max) { | ||
if (date === undefined || min === undefined || max === undefined) { | ||
return; | ||
__getDayTabindex(date, focusedDate) { | ||
if (this.__isDayFocused(date, focusedDate)) { | ||
return '0'; | ||
} | ||
return this._dateAllowed(date, min, max) ? 'false' : 'true'; | ||
return '-1'; | ||
} | ||
__getWeekNumbers(dates) { | ||
return dates | ||
.map((date) => this.__getWeekNumber(date, dates)) | ||
.filter((week, index, arr) => arr.indexOf(week) === index); | ||
} | ||
} | ||
customElements.define(MonthCalendar.is, MonthCalendar); |
153001
4093
+ Added@vaadin/button@23.0.0-beta2(transitive)
+ Added@vaadin/component-base@23.0.0-beta2(transitive)
+ Added@vaadin/field-base@23.0.0-beta2(transitive)
+ Added@vaadin/icon@23.0.0-beta2(transitive)
+ Added@vaadin/input-container@23.0.0-beta2(transitive)
+ Added@vaadin/vaadin-lumo-styles@23.0.0-beta2(transitive)
+ Added@vaadin/vaadin-material-styles@23.0.0-beta2(transitive)
+ Added@vaadin/vaadin-overlay@23.0.0-beta2(transitive)
+ Added@vaadin/vaadin-themable-mixin@23.0.0-beta2(transitive)
- Removed@vaadin/button@23.0.0-beta1(transitive)
- Removed@vaadin/component-base@23.0.0-beta1(transitive)
- Removed@vaadin/field-base@23.0.0-beta1(transitive)
- Removed@vaadin/icon@23.0.0-beta1(transitive)
- Removed@vaadin/input-container@23.0.0-beta1(transitive)
- Removed@vaadin/vaadin-lumo-styles@23.0.0-beta1(transitive)
- Removed@vaadin/vaadin-material-styles@23.0.0-beta1(transitive)
- Removed@vaadin/vaadin-overlay@23.0.0-beta1(transitive)
- Removed@vaadin/vaadin-themable-mixin@23.0.0-beta1(transitive)
Updated@vaadin/button@23.0.0-beta2