Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(calendar): focusable disabled dates #1381

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/components/inputs/calendar/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,42 @@ export const combinedDisabledDates = () => {
`;
};
```

### Finding enabled dates

The next available date may be multiple days/month in the future/past.
For that we offer convenient helpers as

- `findNextEnabledDate()`
- `findPreviousEnabledDate()`
- `findNearestEnabledDate()`

```js preview-story
export const findingEnabledDates = () => {
function getCalendar(ev) {
return ev.target.parentElement.querySelector('.js-calendar');
}
return html`
<style>
.demo-calendar {
border: 1px solid #adadad;
box-shadow: 0 0 16px #ccc;
max-width: 500px;
}
</style>
<lion-calendar
class="demo-calendar js-calendar"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
></lion-calendar>
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findNextEnabledDate())}">
focus findNextEnabledDate
</button>
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findPreviousEnabledDate())}">
focus findPreviousEnabledDate
</button>
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findNearestEnabledDate())}">
focus findNearestEnabledDate
</button>
`;
};
```
150 changes: 81 additions & 69 deletions packages/calendar/src/LionCalendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ import { isSameDate } from './utils/isSameDate.js';
* @typedef {import('../types/day').Month} Month
*/

const isDayButton = /** @param {HTMLElement} el */ el =>
el.classList.contains('calendar__day-button');

/**
* @param {HTMLElement} el
* @returns {boolean}
*/
function isDisabledDayButton(el) {
return el.getAttribute('aria-disabled') === 'true';
}

/**
* @customElement lion-calendar
*/
Expand Down Expand Up @@ -199,19 +210,19 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
}

goToNextMonth() {
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month' });
}

goToPreviousMonth() {
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month' });
}

goToNextYear() {
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear' });
}

goToPreviousYear() {
this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear' });
}

/**
Expand All @@ -224,9 +235,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
}

focusCentralDate() {
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector(
'button[tabindex="0"]',
));
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector('[tabindex="0"]'));
button.focus();
this.__focusedDate = this.centralDate;
}
Expand Down Expand Up @@ -308,13 +317,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
requestUpdateInternal(name, oldValue) {
super.requestUpdateInternal(name, oldValue);

const map = {
disableDates: () => this.__disableDatesChanged(),
centralDate: () => this.__centralDateChanged(),
__focusedDate: () => this.__focusedDateChanged(),
};
if (map[name]) {
map[name]();
if (name === '__focusedDate') {
this.__focusedDateChanged();
}

const updateDataOn = ['centralDate', 'minDate', 'maxDate', 'selectedDate', 'disableDates'];
Expand Down Expand Up @@ -342,10 +346,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
*/
__calculateInitialCentralDate() {
if (this.centralDate === this.__today && this.selectedDate) {
// initialised with selectedDate only if user didn't provide another one
// initialized with selectedDate only if user didn't provide another one
this.centralDate = this.selectedDate;
} else {
this.__ensureValidCentralDate();
}
/** @type {Date} */
this.__initialCentralDate = this.centralDate;
Expand Down Expand Up @@ -611,15 +613,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
return data;
}

/**
* @private
*/
__disableDatesChanged() {
if (this.__connectedCallbackDone) {
this.__ensureValidCentralDate();
}
}

/**
* @param {Date} selectedDate
* @private
Expand All @@ -636,15 +629,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
);
}

/**
* @private
*/
__centralDateChanged() {
if (this.__connectedCallbackDone) {
this.__ensureValidCentralDate();
}
}

/**
* @private
*/
Expand All @@ -654,15 +638,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
}
}

/**
* @private
*/
__ensureValidCentralDate() {
if (!this.__isEnabledDate(this.centralDate)) {
this.centralDate = this.__findBestEnabledDateFor(this.centralDate);
}
}

/**
* @param {Date} date
* @private
Expand Down Expand Up @@ -715,16 +690,40 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
);
}

/**
* @param {Date} [date]
* @returns
*/
findNextEnabledDate(date) {
const _date = date || this.centralDate;
return this.__findBestEnabledDateFor(_date, { mode: 'future' });
}

/**
* @param {Date} [date]
* @returns
*/
findPreviousEnabledDate(date) {
const _date = date || this.centralDate;
return this.__findBestEnabledDateFor(_date, { mode: 'past' });
}

/**
* @param {Date} [date]
* @returns
*/
findNearestEnabledDate(date) {
const _date = date || this.centralDate;
return this.__findBestEnabledDateFor(_date, { mode: 'both' });
}

/**
* @param {Event} ev
* @private
*/
__clickDateDelegation(ev) {
const isDayButton = /** @param {HTMLElement} el */ el =>
el.classList.contains('calendar__day-button');

const el = /** @type {HTMLElement & { date: Date }} */ (ev.target);
if (isDayButton(el)) {
if (isDayButton(el) && !isDisabledDayButton(el)) {
this.__dateSelectedByUser(el.date);
}
}
Expand All @@ -733,9 +732,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
* @private
*/
__focusDateDelegation() {
const isDayButton = /** @param {HTMLElement} el */ el =>
el.classList.contains('calendar__day-button');

if (
!this.__focusedDate &&
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
Expand All @@ -749,9 +745,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
* @private
*/
__blurDateDelegation() {
const isDayButton = /** @param {HTMLElement} el */ el =>
el.classList.contains('calendar__day-button');

setTimeout(() => {
if (
this.shadowRoot?.activeElement &&
Expand All @@ -762,42 +755,65 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
}, 1);
}

/**
* @param {HTMLElement & { date: Date }} el
* @private
*/
__dayButtonSelection(el) {
if (isDayButton(el)) {
this.__dateSelectedByUser(el.date);
}
}

/**
* @param {KeyboardEvent} ev
* @private
*/
__keyboardNavigationEvent(ev) {
const preventedKeys = ['ArrowUp', 'ArrowDown', 'PageDown', 'PageUp'];
const preventedKeys = [
'ArrowLeft',
'ArrowUp',
'ArrowRight',
'ArrowDown',
'PageDown',
'PageUp',
' ',
'Enter',
];

if (preventedKeys.includes(ev.key)) {
ev.preventDefault();
}

switch (ev.key) {
case ' ':
case 'Enter':
this.__dayButtonSelection(/** @type {HTMLElement & { date: Date }} */ (ev.target));
break;
case 'ArrowUp':
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date' });
break;
case 'ArrowDown':
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date' });
break;
case 'ArrowLeft':
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date' });
break;
case 'ArrowRight':
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date' });
break;
case 'PageDown':
if (ev.altKey === true) {
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear', mode: 'future' });
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear' });
} else {
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month', mode: 'future' });
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month' });
}
break;
case 'PageUp':
if (ev.altKey === true) {
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear', mode: 'past' });
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear' });
} else {
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month', mode: 'past' });
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month' });
}
break;
case 'Tab':
Expand All @@ -813,11 +829,10 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
* @param {Object} opts
* @param {string} opts.dateType
* @param {string} opts.type
* @param {string} opts.mode
* @private
*/
__modifyDate(modify, { dateType, type, mode }) {
let tmpDate = new Date(this.centralDate);
__modifyDate(modify, { dateType, type }) {
const tmpDate = new Date(this.centralDate);
// if we're not working with days, reset
// day count to first day of the month
if (type !== 'Date') {
Expand All @@ -830,9 +845,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
const maxDays = new Date(tmpDate.getFullYear(), tmpDate.getMonth() + 1, 0).getDate();
tmpDate.setDate(Math.min(this.centralDate.getDate(), maxDays));
}
if (!this.__isEnabledDate(tmpDate)) {
tmpDate = this.__findBestEnabledDateFor(tmpDate, { mode });
}
this[dateType] = tmpDate;
}

Expand Down
26 changes: 25 additions & 1 deletion packages/calendar/src/calendarStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ export const calendarStyle = css`
padding: 0;
min-width: 40px;
min-height: 40px;
/** give div[role=button][aria-disabled] same display type as native btn */
display: inline-flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}

.calendar__day-button:focus {
border: 1px solid blue;
outline: none;
}

.calendar__day-button__text {
Expand All @@ -77,9 +87,23 @@ export const calendarStyle = css`
border: 1px solid green;
}

.calendar__day-button[disabled] {
.calendar__day-button[aria-disabled='true'] {
background-color: #fff;
color: #eee;
outline: none;
}

.u-sr-only {
position: absolute;
top: 0;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(100%);
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
border: 0;
margin: 0;
padding: 0;
}
`;
Loading