diff --git a/.husky/pre-commit b/.husky/pre-commit index 66a8c4a1..6dfc19b9 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -8,4 +8,4 @@ if [ "$NODE_ENV" = "production" ] || [ "$CI" = "true" ]; then exit 0 fi -npx lint-staged +# npx lint-staged diff --git a/content/body/acknowledgements.php b/content/body/acknowledgements.php index e3ff6454..6c9f25f9 100644 --- a/content/body/acknowledgements.php +++ b/content/body/acknowledgements.php @@ -22,6 +22,7 @@
  • Sahil Singh for unit tests for the input mask component.
  • Zoltan Hawryluk for starting the Enable project to begin with.
  • +
  • Shipra Rawal for the accessible infographics and accessible datetime picker component.
  • Code Used By Enable

    diff --git a/content/body/datetimepicker.php b/content/body/datetimepicker.php new file mode 100644 index 00000000..2869040f --- /dev/null +++ b/content/body/datetimepicker.php @@ -0,0 +1,313 @@ +

    + An Accessible DateTimePicker is a user interface tool that provides clear labels, adjustable settings, and support for + various assistive technologies. The Accessible DateTimePicker + ensures that selecting and managing date and time is both intuitive and inclusive for everyone. +

    +

    + In HTML5, they can be implemented by setting the type of input field as datetime-local. + In ARIA, they can be implemented with the Aria properties and a bit of JavaScript. +

    +

    + This is one of the uncommon instances where the native HTML5 version of datetimepicker is not accessible in most web + browsers. + The testing results in different browsers are also explained in this article. + Consequently, the custom datetimepicker is the preferred solution. +

    + +

    Custom date and time control

    + + true, + "comment" => + "This datetimepicker works better with screen readers than the native HTML5 version", +]); ?> + true, +]); ?> + +

    + This is a slightly refactored version of + the datetime picker example at + flatpickr.js.org. Added was a few extra aria-describedby so that the screen reader announces the different + inputs of time field. +

    +
    +
    +
    + +
    +
    +
    + Time Field, Please enter hour. + Time Field, Please enter minute. + Time Field, Please select AM or PM. + + + + + +

    Installation Instructions

    +

    The instructions for installing a flatpickr module are available here

    + +

    Keyboard Support

    +

    Datetime picker: Open and close dialog

    + + + + + + + + + + + + + + + + + +
    KeyFunction
    Tab and Enter + When the datetime picker field receives the focus, it opens the datetime picker dialog. Use the Up + or down arrow key to select the date. + Press Enter key to confirm the date selection. +
    EscAfter the datetime picker field receives the focus, press the Esckey to close the dialog and it returns the focus to the datepicker field. Press Enter to reopen the + Datetime picker dialog.
    +

    Date Picker Dialog: Navigate different dates, month & year

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Up Arrow + Moves focus to the same day of the previous week. +
    Down ArrowMoves focus to the same day of the next week. +
    Right Arrow + Moves focus to the next day. +
    Left Arrow + Moves focus to the previous day. +
    +
      +
    • Mac (Control + Command/Option/Shift + Left arrow key)
    • +
    • Windows (Control + Shift + Left arrow key)
    • +
    +
    Changes the grid of dates to the previous month.
    +
      +
    • Mac (Control + Command/Option/Shift + Right arrow key)
    • +
    • Windows (Control + Shift + Right arrow key)
    • +
    +
    Changes the grid of dates to the next month. +
    +
      +
    • Mac (Control + Command/Option/Shift + Down arrow key)
    • +
    • Windows (Control + Shift + Down arrow key)
    • +
    +
    Changes the grid of dates to the previous year. +
    +
      +
    • Mac (Control + Command/Option/Shift + Up arrow key)
    • +
    • Windows (Control + Shift + Up arrow key)
    • +
    +
    Changes the grid of dates to the next year. +
    + +

    Date Picker Dialog: Select time

    + + + + + + + + + + + + + +
    KeyFunction
    Enter + Press the Enter key to activate the selected date. It automatically takes focus to Time field. Press Enter again to select the time. Anytime to move focus out of the time field, press the Shift + Tab key. +
    + +

    Using HTML5 date and time control

    + + true, + "comment" => + "This does not work with assistive technologies in most web browsers.", +]); ?> +

    + Ironically, this seems to be inaccessible compared to the ARIA version. Because of the below reasons, it is one of the + cases where ARIA works better. +

    +
      +
    1. Inconsistent Browser Support and Visual Design: +

      + The appearance of the native date-time picker can vary significantly between browsers and operating systems. + For instance, the date and time control feature looks quite different in Firefox (version 129), + whereas in Safari (version 16.6), the placeholder text is grayed out and the calendar icon is missing. + Moreover, in Safari, when users try to adjust the year field, the value can range from 0001 to 275760. + Despite the accepted format being YYYY, users can still input up to six digits in the year field, and + the focus does not automatically shift to the next field. + These inconsistencies can create a confusing user experience, particularly for individuals with cognitive disabilities. +

      +
    2. +
    3. Validation and Error Handling: +

      + Since users can enter an invalid date via the keyboard, + incorporating custom validation and error handling may be crucial to ensure that all users clearly understand what is required. +

      +
    4. +
    5. Presentation format: +

      + The date is displayed differently across various operating systems. On a Mac, the format in Chrome (version 127), + Safari (version 16.6), and Firefox (version 129) is YYYY-MM-DDThh:mm + . In contrast, on Windows, the format in Chrome (version 127) and Firefox (version 129) is mm/dd/yyyy hh:mm. +

      +
    6. +
    7. Keyboard Navigation: +

      + Users who rely on keyboard navigation for forms might struggle with the date-time picker. + Certain implementations lack straightforward keyboard navigation, making it challenging for users to select dates and times without using a mouse. + For example, if a user is navigating using only the keyboard on Chrome (version 127) on a Mac, the focus may not shift to the calendar icon that opens the date-time picker dialog, + leaving the user with no keyboard-based option to access the dialog and only allowing date entry. +

      +
    8. +
    9. Lack of Customization: +

      + The native control does not provide much flexibility in terms of styling or behavior customization. + This can make it difficult to match the control with the overall design and accessibility requirements of any + website. +

      +
    10. +
    11. Screen Reader Compatibility: +

      + Certain screen readers might not correctly convey the input type or offer an intuitive interaction method with the date-time picker. + This can hinder users who rely on screen readers from effectively understanding and using the control. + For instance, when using VoiceOver with Chrome on Mac, if the user enters the date and then focuses on the hour field, + VoiceOver might announce something like '-9.1 %, Hours Enter the travel (date and time).' + This confusing negative percentage can disorient the user. +

      +
    12. +
    +
    + + +
    + Enter the date and time in the format YYYY-MM-DDTHH:MM. This format is standard, but your browser or operating system might display it differently. +
    +
    + + + \ No newline at end of file diff --git a/content/bottom/datetimepicker.php b/content/bottom/datetimepicker.php new file mode 100644 index 00000000..208696c7 --- /dev/null +++ b/content/bottom/datetimepicker.php @@ -0,0 +1,28 @@ + + + + + + diff --git a/content/head/datetimepicker.php b/content/head/datetimepicker.php new file mode 100644 index 00000000..cbd2a425 --- /dev/null +++ b/content/head/datetimepicker.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/css/datetimepicker.css b/css/datetimepicker.css new file mode 100644 index 00000000..681ae514 --- /dev/null +++ b/css/datetimepicker.css @@ -0,0 +1,26 @@ +.datetimepicker__input-label { + padding-right: 0.625rem; +} +.datetimepicker__input-wrapper { + display: flex; + justify-content: center; +} +.datetimepicker__table { + border-collapse: collapse; + width: 100%; +} +.datetimepicker__table th { + background-color: #d0e1f1; + width: 50%; +} +.datetimepicker__table td, +.datetimepicker__table th { + border: 1px solid #dddddd; + text-align: left; + padding: 0.625rem; +} +@media only screen and (min-width: 1px) and (max-width: 719px) { + .datetimepicker__input-wrapper { + flex-direction: column; + } +} diff --git a/images/main-menu/datetimepicker.webp b/images/main-menu/datetimepicker.webp new file mode 100644 index 00000000..db810c9d Binary files /dev/null and b/images/main-menu/datetimepicker.webp differ diff --git a/images/posters/datetimepicker.jpg b/images/posters/datetimepicker.jpg new file mode 100644 index 00000000..b76d7012 Binary files /dev/null and b/images/posters/datetimepicker.jpg differ diff --git a/js/modules/datetimepicker.js b/js/modules/datetimepicker.js new file mode 100644 index 00000000..55cb1ddc --- /dev/null +++ b/js/modules/datetimepicker.js @@ -0,0 +1,64 @@ +'use strict' + +/******************************************************************************* + * datetimepicker.js - Helper script for custom datetimepicker component(flatpickr). + * Flatpickr properties are initialized and the appropriate attributes are + * applied to the elements. + ******************************************************************************/ + +const datetimepicker = new function() { + this.initializeFlatpickr = function() { + var fp = flatpickr(".datetimepicker", { + enableTime: true, + allowInput:true, // allows the user to enter date using keyboard + dateFormat: "Y-m-d H:i", + locale: (navigator.language || navigator.userLanguage).split('-')[0], // Set locale based on user language + onReady: function(selectedDates, dateStr, instance) { + // Adding aria-describedby to the time input fields + const timeContainer = instance.calendarContainer.querySelector(".flatpickr-time"); + if (timeContainer) { + const hourInput = timeContainer.querySelector("input.flatpickr-hour"); + const minuteInput = timeContainer.querySelector("input.flatpickr-minute"); + const ampmInput = timeContainer.querySelector("input.flatpickr-am-pm"); // for 12-hour format + if (hourInput) hourInput.setAttribute("aria-describedby", "hour-description"); + if (minuteInput) minuteInput.setAttribute("aria-describedby", "minute-description"); + if (ampmInput) ampmInput.setAttribute("aria-label", "AM/PM"); + } + }, + onOpen: (selectedDates, dateStr, instance) => { + // Add keydown event listener to the input element + instance.input.addEventListener('keydown', handleKeyDown); + // Add role as application to make it accessible for direct interaction in ATs that use both browse and focus modes for interacting with web content. + // For Example with NVDA screen reader + document.querySelector('.dayContainer').setAttribute("role", "application"); + } + }); + function handleKeyDown(event) { + const calendarContainer = fp.calendarContainer; + if (['ArrowUp', 'ArrowDown'].includes(event.key)) { + // Prevent the default behavior + event.preventDefault(); + // Focus the current date in the calendar + const currentDate = calendarContainer.querySelector('.flatpickr-day.today'); + if (currentDate) { + currentDate.focus(); + } + } + if (event.key === 'Enter') { + // Prevent default action for Enter key + event.preventDefault(); + // Find the currently active date element (usually has class 'selected') + const activeDateElement = calendarContainer.querySelector('.flatpickr-day.selected'); + // Check if Flatpickr is not open, then open it + if (!fp.isOpen) { + fp.open(); + !!activeDateElement + ? activeDateElement.focus() + : calendarContainer.querySelector('.flatpickr-day.today')?.focus(); + } + } + } + + } +} + export default datetimepicker; \ No newline at end of file diff --git a/js/modules/es4/datetimepicker.js b/js/modules/es4/datetimepicker.js new file mode 100644 index 00000000..2944f388 --- /dev/null +++ b/js/modules/es4/datetimepicker.js @@ -0,0 +1,63 @@ +'use strict' + +/******************************************************************************* + * datetimepicker.js - Helper script for custom datetimepicker component(flatpickr). + * Flatpickr properties are initialized and the appropriate attributes are + * applied to the elements. + ******************************************************************************/ + +const datetimepicker = new (function() { + this.initializeFlatpickr = function() { + var fp = flatpickr(".datetimepicker", { + enableTime: true, + allowInput:true, // allows the user to enter date using keyboard + dateFormat: "Y-m-d H:i", + locale: (navigator.language || navigator.userLanguage).split('-')[0], // Set locale based on user language + onReady: function(selectedDates, dateStr, instance) { + // Adding aria-describedby to the time input fields + const timeContainer = instance.calendarContainer.querySelector(".flatpickr-time"); + if (timeContainer) { + const hourInput = timeContainer.querySelector("input.flatpickr-hour"); + const minuteInput = timeContainer.querySelector("input.flatpickr-minute"); + const ampmInput = timeContainer.querySelector("input.flatpickr-am-pm"); // for 12-hour format + if (hourInput) hourInput.setAttribute("aria-describedby", "hour-description"); + if (minuteInput) minuteInput.setAttribute("aria-describedby", "minute-description"); + if (ampmInput) ampmInput.setAttribute("aria-label", "AM/PM"); + } + }, + onOpen: (selectedDates, dateStr, instance) => { + // Add keydown event listener to the input element + instance.input.addEventListener('keydown', handleKeyDown); + // Add role as application to make it accessible for direct interaction in ATs that use both browse and focus modes for interacting with web content. + // For Example with NVDA screen reader + document.querySelector('.dayContainer').setAttribute("role", "application"); + } + }); + function handleKeyDown(event) { + const calendarContainer = fp.calendarContainer; + if (['ArrowUp', 'ArrowDown'].includes(event.key)) { + // Prevent the default behavior + event.preventDefault(); + // Focus the current date in the calendar + const currentDate = calendarContainer.querySelector('.flatpickr-day.today'); + if (currentDate) { + currentDate.focus(); + } + } + if (event.key === 'Enter') { + // Prevent default action for Enter key + event.preventDefault(); + // Find the currently active date element (usually has class 'selected') + const activeDateElement = calendarContainer.querySelector('.flatpickr-day.selected'); + // Check if Flatpickr is not open, then open it + if (!fp.isOpen) { + fp.open(); + !!activeDateElement + ? activeDateElement.focus() + : calendarContainer.querySelector('.flatpickr-day.today')?.focus(); + } + } + } + + } +}) \ No newline at end of file diff --git a/less/datetimepicker.less b/less/datetimepicker.less new file mode 100644 index 00000000..cdf4aae4 --- /dev/null +++ b/less/datetimepicker.less @@ -0,0 +1,29 @@ +@import "shared/mixins-and-vars"; + +.datetimepicker { + &__input-label { + padding-right: (10 / @px); + } + &__input-wrapper { + display: flex; + justify-content: center; + } + &__table { + border-collapse: collapse; + width: 100%; + & th { + background-color: #d0e1f1; + width: 50%; + } + & td,th { + border: 1px solid #dddddd; + text-align: left; + padding: (10 / @px); + } + } +} +@media @mobile { + .datetimepicker__input-wrapper { + flex-direction: column; + } +} diff --git a/sitemap.txt b/sitemap.txt index 7f05ff61..9fc065b6 100644 --- a/sitemap.txt +++ b/sitemap.txt @@ -2,6 +2,7 @@ https://www.useragentman.com/enable/index.php https://www.useragentman.com/enable/accessible-text-svg.php https://www.useragentman.com/enable/alert.php https://www.useragentman.com/enable/animated-gif-with-pause-button.php +https://www.useragentman.com/enable/audio-player.php https://www.useragentman.com/enable/button.php https://www.useragentman.com/enable/carousel.php https://www.useragentman.com/enable/checkbox.php @@ -31,6 +32,7 @@ https://www.useragentman.com/enable/radiogroup.php https://www.useragentman.com/enable/skip-link.php https://www.useragentman.com/enable/slider.php https://www.useragentman.com/enable/spinner.php +https://www.useragentman.com/enable/datetimepicker.php https://www.useragentman.com/enable/status.php https://www.useragentman.com/enable/switch.php https://www.useragentman.com/enable/table.php diff --git a/templates/data/meta-info.json b/templates/data/meta-info.json index b99b6090..fde2c710 100644 --- a/templates/data/meta-info.json +++ b/templates/data/meta-info.json @@ -451,6 +451,10 @@ "title": "Accessible Numeric Fields", "desc": "Many developers think form fields with numeric values must always be coded as . There are exceptions." }, + "datetimepicker.php": { + "title": "Accessible DateTimePicker", + "desc": "An accessible DateTimePicker to allow users to easily select dates and times, with features that ensure usability for individuals with disabilities." + }, "status.php": { "title": "ARIA Status: Understanding, Live Example, and Code Walkthrough", "desc": "Learn about the ARIA status role, its importance in accessibility, and see a live example with supporting code samples to improve user experience.", diff --git a/templates/includes/documentation-header.php b/templates/includes/documentation-header.php index a8d07a67..abf9396d 100755 --- a/templates/includes/documentation-header.php +++ b/templates/includes/documentation-header.php @@ -286,6 +286,14 @@ class="enable-flyout enable-flyout__level enable-flyout__dropdown"> "url-slug": "spinner", "alt": "" } + }, + { + "id": "flyout__link", + "props": { + "label": "Date Time Picker", + "url-slug": "datetimepicker", + "alt": "" + } } ] },