From 40cf767543f49b5a460b05b1117e52ba4ccc6da2 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Tue, 4 Aug 2026 19:47:50 -0300 Subject: [PATCH 1/2] Scheduler - A11y: announce the disabled time range in the aria-label of the Cell Templates demo --- .../CellTemplates/Angular/app/app.component.ts | 13 ++++++++++++- .../Demos/Scheduler/CellTemplates/React/App.tsx | 15 +++++++++++++-- .../Scheduler/CellTemplates/ReactJs/App.js | 13 +++++++++++-- .../Demos/Scheduler/CellTemplates/Vue/App.vue | 17 ++++++++++++++--- .../Scheduler/CellTemplates/jQuery/index.js | 13 ++++++++++++- 5 files changed, 62 insertions(+), 9 deletions(-) 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 ca161e9b2d63..a840c6b081c0 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts +++ b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts @@ -47,7 +47,12 @@ export class AppComponent { currentView = this.views[0]; - ariaDescription = () => { + formatHour = (hours: number) => new Date(2021, 0, 1, hours).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + }); + + disabledDatesDescription = () => { const disabledDates = this.holidays .filter((date) => !this.isWeekend(date)) .map((date) => new Date(date).toLocaleDateString('en-US', { @@ -66,6 +71,12 @@ export class AppComponent { return ''; }; + disabledTimeDescription = () => `The time range from ${this.formatHour(this.dinnerTime.from)} to ${this.formatHour(this.dinnerTime.to)} is disabled on all days`; + + ariaDescription = () => [this.disabledDatesDescription(), this.disabledTimeDescription()] + .filter(Boolean) + .join('. '); + constructor(public dataService: DataService) { this.dinnerTime = this.dataService.getDinnerTime(); this.holidays = this.dataService.getHolidays(); diff --git a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx index 8a7c20c8c4c1..27127bd6bc5a 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx +++ b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx @@ -4,7 +4,7 @@ import Scheduler from 'devextreme-react/scheduler'; import type { SchedulerTypes } from 'devextreme-react/scheduler'; import type { FormRef } from 'devextreme-react/form'; import notify from 'devextreme/ui/notify'; -import { data, holidays } from './data.ts'; +import { data, dinnerTime, holidays } from './data.ts'; import Utils from './utils.ts'; import DataCell from './DataCell.tsx'; import DataCellMonth from './DataCellMonth.tsx'; @@ -13,7 +13,12 @@ import TimeCell from './TimeCell.tsx'; const currentDate = new Date(2021, 3, 27); const views: SchedulerTypes.ViewType[] = ['workWeek', 'month']; -const ariaDescription = () => { +const formatHour = (hours: number) => new Date(2021, 0, 1, hours).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', +}); + +const disabledDatesDescription = () => { const disabledDates = holidays .filter((date) => !Utils.isWeekend(date)) .map((date) => new Date(date).toLocaleDateString('en-US', { @@ -32,6 +37,12 @@ const ariaDescription = () => { return ''; }; +const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; + +const ariaDescription = () => [disabledDatesDescription(), disabledTimeDescription()] + .filter(Boolean) + .join('. '); + const notifyDisableDate = () => { notify('Cannot create or move an appointment/event to disabled time/date regions.', 'warning', 1000); }; diff --git a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js index 5c3a51926885..9551dfb8f7cf 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js @@ -1,7 +1,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import Scheduler from 'devextreme-react/scheduler'; import notify from 'devextreme/ui/notify'; -import { data, holidays } from './data.js'; +import { data, dinnerTime, holidays } from './data.js'; import Utils from './utils.js'; import DataCell from './DataCell.js'; import DataCellMonth from './DataCellMonth.js'; @@ -10,7 +10,12 @@ import TimeCell from './TimeCell.js'; const currentDate = new Date(2021, 3, 27); const views = ['workWeek', 'month']; -const ariaDescription = () => { +const formatHour = (hours) => + new Date(2021, 0, 1, hours).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + }); +const disabledDatesDescription = () => { const disabledDates = holidays .filter((date) => !Utils.isWeekend(date)) .map((date) => @@ -29,6 +34,10 @@ const ariaDescription = () => { } return ''; }; +const disabledTimeDescription = () => + `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; +const ariaDescription = () => + [disabledDatesDescription(), disabledTimeDescription()].filter(Boolean).join('. '); const notifyDisableDate = () => { notify( 'Cannot create or move an appointment/event to disabled time/date regions.', diff --git a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue index c0d7267b9e17..0c6b06d5775c 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue +++ b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue @@ -47,7 +47,7 @@ import { computed, ref } from 'vue'; import { DxForm } from 'devextreme-vue'; import { DxScheduler, type DxSchedulerTypes } from 'devextreme-vue/scheduler'; import notify from 'devextreme/ui/notify'; -import { data, holidays } from './data.ts'; +import { data, dinnerTime, holidays } from './data.ts'; import Utils from './utils.ts'; import DataCell from './DataCell.vue'; import DataCellMonth from './DataCellMonth.vue'; @@ -61,7 +61,12 @@ const dataSource = data; const isMonthView = computed(() => currentView.value === 'month'); -const ariaDescription = computed(() => { +const formatHour = (hours: number) => new Date(2021, 0, 1, hours).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', +}); + +const disabledDatesDescription = () => { const disabledDates = holidays .filter((date) => !Utils.isWeekend(date)) .map((date) => new Date(date).toLocaleDateString('en-US', { @@ -78,7 +83,13 @@ const ariaDescription = computed(() => { return `${disabledDates.join(', ')} are disabled dates`; } return ''; -}); +}; + +const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; + +const ariaDescription = computed(() => [disabledDatesDescription(), disabledTimeDescription()] + .filter(Boolean) + .join('. ')); function onContentReady(e: DxSchedulerTypes.ContentReadyEvent) { setComponentAria(e.component?.$element()); diff --git a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js index 092da457e244..6c0cffcd8e55 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js @@ -92,7 +92,12 @@ const holidays = [ new Date(2021, 5, 6), ]; -const ariaDescription = () => { +const formatHour = (hours) => new Date(2021, 0, 1, hours).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', +}); + +const disabledDatesDescription = () => { const disabledDates = holidays .filter((date) => !isWeekend(date)) .map((date) => new Date(date).toLocaleDateString('en-US', { @@ -111,6 +116,12 @@ const ariaDescription = () => { return ''; }; +const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; + +const ariaDescription = () => [disabledDatesDescription(), disabledTimeDescription()] + .filter(Boolean) + .join('. '); + function notifyDisableDate() { DevExpress.ui.notify('Cannot create or move an appointment/event to disabled time/date regions.', 'warning', 1000); } From 14919a296f299813e2f602fb951a50697b484675 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Tue, 4 Aug 2026 20:15:13 -0300 Subject: [PATCH 2/2] Keep the generated ReactJs demo formatting in sync with the converter --- .../Scheduler/CellTemplates/Angular/app/app.component.ts | 6 +++++- apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx | 6 +++++- apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js | 7 +++++-- apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue | 6 +++++- apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js | 6 +++++- 5 files changed, 25 insertions(+), 6 deletions(-) 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 a840c6b081c0..8faf3b88f9cd 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts +++ b/apps/demos/Demos/Scheduler/CellTemplates/Angular/app/app.component.ts @@ -71,7 +71,11 @@ export class AppComponent { return ''; }; - disabledTimeDescription = () => `The time range from ${this.formatHour(this.dinnerTime.from)} to ${this.formatHour(this.dinnerTime.to)} is disabled on all days`; + disabledTimeDescription = () => { + const from = this.formatHour(this.dinnerTime.from); + const to = this.formatHour(this.dinnerTime.to); + return `The time range from ${from} to ${to} is disabled on all days`; + }; ariaDescription = () => [this.disabledDatesDescription(), this.disabledTimeDescription()] .filter(Boolean) diff --git a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx index 27127bd6bc5a..e327124941e3 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx +++ b/apps/demos/Demos/Scheduler/CellTemplates/React/App.tsx @@ -37,7 +37,11 @@ const disabledDatesDescription = () => { return ''; }; -const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; +const disabledTimeDescription = () => { + const from = formatHour(dinnerTime.from); + const to = formatHour(dinnerTime.to); + return `The time range from ${from} to ${to} is disabled on all days`; +}; const ariaDescription = () => [disabledDatesDescription(), disabledTimeDescription()] .filter(Boolean) diff --git a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js index 9551dfb8f7cf..17288306e2e7 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/ReactJs/App.js @@ -34,8 +34,11 @@ const disabledDatesDescription = () => { } return ''; }; -const disabledTimeDescription = () => - `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; +const disabledTimeDescription = () => { + const from = formatHour(dinnerTime.from); + const to = formatHour(dinnerTime.to); + return `The time range from ${from} to ${to} is disabled on all days`; +}; const ariaDescription = () => [disabledDatesDescription(), disabledTimeDescription()].filter(Boolean).join('. '); const notifyDisableDate = () => { diff --git a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue index 0c6b06d5775c..e0e94ef635be 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue +++ b/apps/demos/Demos/Scheduler/CellTemplates/Vue/App.vue @@ -85,7 +85,11 @@ const disabledDatesDescription = () => { return ''; }; -const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; +const disabledTimeDescription = () => { + const from = formatHour(dinnerTime.from); + const to = formatHour(dinnerTime.to); + return `The time range from ${from} to ${to} is disabled on all days`; +}; const ariaDescription = computed(() => [disabledDatesDescription(), disabledTimeDescription()] .filter(Boolean) diff --git a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js index 6c0cffcd8e55..b1699f385efb 100644 --- a/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js +++ b/apps/demos/Demos/Scheduler/CellTemplates/jQuery/index.js @@ -116,7 +116,11 @@ const disabledDatesDescription = () => { return ''; }; -const disabledTimeDescription = () => `The time range from ${formatHour(dinnerTime.from)} to ${formatHour(dinnerTime.to)} is disabled on all days`; +const disabledTimeDescription = () => { + const from = formatHour(dinnerTime.from); + const to = formatHour(dinnerTime.to); + return `The time range from ${from} to ${to} is disabled on all days`; +}; const ariaDescription = () => [disabledDatesDescription(), disabledTimeDescription()] .filter(Boolean)