From 19bd42e3d3d1c3a2321178d7698875a1c95c847f Mon Sep 17 00:00:00 2001 From: Joshua Barbosa <44610149+tongsonbarbs@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:09:23 +0800 Subject: [PATCH] Scheduler a11y: Disabled time ranges are not supported (#28202) --- .../Angular/app/app.component.html | 1 + .../Angular/app/app.component.ts | 27 ++++++++++++++++++ .../Scheduler/CellTemplates/React/App.tsx | 27 ++++++++++++++++++ .../Scheduler/CellTemplates/ReactJs/App.js | 25 +++++++++++++++++ .../Demos/Scheduler/CellTemplates/Vue/App.vue | 28 +++++++++++++++++++ .../Scheduler/CellTemplates/jQuery/index.js | 26 +++++++++++++++++ 6 files changed, 134 insertions(+) diff --git a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.html b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.html index af3ff308ddb8..4a2c254befea 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.html +++ b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.html @@ -11,6 +11,7 @@ dataCellTemplate="dataCellTemplate" dateCellTemplate="dateCellTemplate" timeCellTemplate="timeCellTemplate" + (onContentReady)="onContentReady($event)" (onAppointmentFormOpening)="onAppointmentFormOpening($event)" (onAppointmentAdding)="onAppointmentAdding($event)" (onAppointmentUpdating)="onAppointmentUpdating($event)" diff --git a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts index 20101e3397a8..d31dae2a10e7 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts +++ b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts @@ -44,12 +44,34 @@ export class AppComponent { currentView = this.views[0]; + ariaDescription = () => { + const disabledDates = this.holidays + .filter(date => !this.isWeekend(date)) + .map(date => new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }) + ); + if (disabledDates?.length === 1) { + return `${disabledDates} is a disabled date`; + } + if (disabledDates?.length > 1) { + return `${disabledDates.join(', ')} are disabled dates`; + } + }; + constructor(public dataService: DataService) { this.dataSource = new DataSource({ store: dataService.getData(), }); } + onContentReady = (e: DxSchedulerTypes.ContentReadyEvent) => { + this.setComponentAria(e.component?.$element()); + }; + onOptionChanged = (e: DxSchedulerTypes.OptionChangedEvent) => { if (e.name === 'currentView') { this.currentView = e.value; @@ -148,6 +170,11 @@ export class AppComponent { const endDateEditor = form.getEditor('endDate'); endDateEditor.option('disabledDates', holidays); }; + + setComponentAria(element): void { + const prevAria = element?.attr('aria-label') || ''; + element?.attr('aria-label', `${prevAria} ${this.ariaDescription()}`); + } } @NgModule({ diff --git a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx index c86610486c49..5a932c9390f4 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx +++ b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx @@ -13,11 +13,32 @@ import TimeCell from './TimeCell.tsx'; const currentDate = new Date(2021, 3, 27); const views: SchedulerTypes.ViewType[] = ['workWeek', 'month']; +const ariaDescription = () => { + const disabledDates = holidays + .filter((date) => !Utils.isWeekend(date)) + .map((date) => new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }) + ); + if (disabledDates?.length === 1) { + return `${disabledDates} is a disabled date`; + } + if (disabledDates?.length > 1) { + return `${disabledDates.join(', ')} are disabled dates`; + } +}; const notifyDisableDate = () => { notify('Cannot create or move an appointment/event to disabled time/date regions.', 'warning', 1000); }; +const onContentReady = (e: SchedulerTypes.ContentReadyEvent) => { + setComponentAria(e.component?.$element()); +} + const applyDisableDatesToDateEditors = (form: ReturnType) => { const startDateEditor = form.getEditor('startDate'); startDateEditor?.option('disabledDates', holidays); @@ -53,6 +74,11 @@ const onAppointmentUpdating = (e: SchedulerTypes.AppointmentUpdatingEvent) => { } }; +const setComponentAria = (element) => { + const prevAria = element?.attr('aria-label') || ''; + element?.attr('aria-label', `${prevAria} ${ariaDescription()}`); +} + const App = () => { const [currentView, setCurrentView] = useState(views[0]); @@ -81,6 +107,7 @@ const App = () => { dataCellComponent={DataCellComponent} dateCellRender={renderDateCell} timeCellComponent={TimeCell} + onContentReady={onContentReady} onAppointmentFormOpening={onAppointmentFormOpening} onAppointmentAdding={onAppointmentAdding} onAppointmentUpdating={onAppointmentUpdating} diff --git a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js index e416c38f15f5..0d465da1f6b9 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js @@ -11,6 +11,23 @@ import TimeCell from './TimeCell.js'; const currentDate = new Date(2021, 3, 27); const views = ['workWeek', 'month']; +const ariaDescription = () => { + const disabledDates = holidays + .filter((date) => !Utils.isWeekend(date)) + .map((date) => + new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + })); + if (disabledDates?.length === 1) { + return `${disabledDates} is a disabled date`; + } + if (disabledDates?.length > 1) { + return `${disabledDates.join(', ')} are disabled dates`; + } +}; const notifyDisableDate = () => { notify( 'Cannot create or move an appointment/event to disabled time/date regions.', @@ -18,6 +35,9 @@ const notifyDisableDate = () => { 1000, ); }; +const onContentReady = (e) => { + setComponentAria(e.component?.$element()); +}; const applyDisableDatesToDateEditors = (form) => { const startDateEditor = form.getEditor('startDate'); startDateEditor?.option('disabledDates', holidays); @@ -48,6 +68,10 @@ const onAppointmentUpdating = (e) => { notifyDisableDate(); } }; +const setComponentAria = (element) => { + const prevAria = element?.attr('aria-label') || ''; + element?.attr('aria-label', `${prevAria} ${ariaDescription()}`); +}; const App = () => { const [currentView, setCurrentView] = useState(views[0]); const DataCellComponent = useMemo( @@ -79,6 +103,7 @@ const App = () => { dataCellComponent={DataCellComponent} dateCellRender={renderDateCell} timeCellComponent={TimeCell} + onContentReady={onContentReady} onAppointmentFormOpening={onAppointmentFormOpening} onAppointmentAdding={onAppointmentAdding} onAppointmentUpdating={onAppointmentUpdating} diff --git a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue index 2fc978ea6559..86f4d2c2a832 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue +++ b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue @@ -12,6 +12,7 @@ data-cell-template="dataCellTemplate" date-cell-template="dateCellTemplate" time-cell-template="timeCellTemplate" + :on-content-ready="onContentReady" :on-appointment-form-opening="onAppointmentFormOpening" :on-appointment-adding="onAppointmentAdding" :on-appointment-updating="onAppointmentUpdating" @@ -59,6 +60,28 @@ const dataSource = data; const isMonthView = computed(() => currentView.value === 'month'); +const ariaDescription = computed(() => { + const disabledDates = holidays + .filter((date) => !Utils.isWeekend(date)) + .map((date) => new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }) + ); + if (disabledDates?.length === 1) { + return `${disabledDates} is a disabled date`; + } + if (disabledDates?.length > 1) { + return `${disabledDates.join(', ')} are disabled dates`; + } +}); + +function onContentReady(e: DxSchedulerTypes.ContentReadyEvent) { + setComponentAria(e.component?.$element()); +} + function onAppointmentFormOpening(e: DxSchedulerTypes.AppointmentFormOpeningEvent) { const startDate = new Date(e.appointmentData.startDate); if (!Utils.isValidAppointmentDate(startDate)) { @@ -91,4 +114,9 @@ function applyDisableDatesToDateEditors(form) { const endDateEditor = form.getEditor('endDate'); endDateEditor.option('disabledDates', holidays); } + +function setComponentAria(element) { + const prevAria = element?.attr('aria-label') || ''; + element?.attr('aria-label', `${prevAria} ${ariaDescription.value}`); +} diff --git a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js index 05be5edeac31..2814d8a92360 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js @@ -57,6 +57,10 @@ $(() => { return itemElement.append(element); }, + onContentReady(e) { + setComponentAria(e.component.$element()); + }, + onAppointmentFormOpening(e) { const startDate = new Date(e.appointmentData.startDate); if (!isValidAppointmentDate(startDate)) { @@ -87,6 +91,23 @@ const holidays = [ new Date(2021, 3, 29), new Date(2021, 5, 6), ]; +const ariaDescription = () => { + const disabledDates = holidays + .filter(date => !isWeekend(date)) + .map(date => new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }) + ); + if (disabledDates?.length === 1) { + return `${disabledDates} is a disabled date`; + } + if (disabledDates?.length > 1) { + return `${disabledDates.join(', ')} are disabled dates`; + } +}; function notifyDisableDate() { DevExpress.ui.notify('Cannot create or move an appointment/event to disabled time/date regions.', 'warning', 1000); @@ -156,3 +177,8 @@ function applyDisableDatesToDateEditors(form) { const endDateEditor = form.getEditor('endDate'); endDateEditor.option('disabledDates', holidays); } + +function setComponentAria(element) { + const prevAria = element?.attr('aria-label') || ''; + element?.attr('aria-label', `${prevAria} ${ariaDescription()}`); +}