From 5d16101ffdee81f6b0c6e9d037195aa37e446d03 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 4 Dec 2018 01:49:40 -0600 Subject: [PATCH 001/137] Add Initial Cut of Date Picker Example (pull #839) For issue #34, add a directory for datepicker examples and a first cut of code. --- examples/datepicker/css/datepicker.css | 148 ++++++ examples/datepicker/datepicker.html | 651 ++++++++++++++++++++++++ examples/datepicker/js/dateInput.js | 78 +++ examples/datepicker/js/datepicker.js | 610 ++++++++++++++++++++++ examples/datepicker/js/datepickerDay.js | 110 ++++ 5 files changed, 1597 insertions(+) create mode 100644 examples/datepicker/css/datepicker.css create mode 100644 examples/datepicker/datepicker.html create mode 100644 examples/datepicker/js/dateInput.js create mode 100644 examples/datepicker/js/datepicker.js create mode 100644 examples/datepicker/js/datepickerDay.js diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css new file mode 100644 index 0000000000..ad1084b1b3 --- /dev/null +++ b/examples/datepicker/css/datepicker.css @@ -0,0 +1,148 @@ +* { + margin: 0; + padding: 0; + /* border:0; */ +} +.datepicker{ + margin-top:1em; +} +.datepickerButton{ + border-style:none; + text-align:left; + background-color:white; +} +label{ + display:block; +} + +.datepickerInput{ + margin-top:1em; + width:20%; +} + +.datepickerDialog { + width:45%; + clear:both; + display:none; + border:3px solid rgb(130, 177, 230); + margin-top:1em; + border-radius:5px; + padding:0; +} +.dialogButtonGroup{ + text-align:right; + margin-top:1em; + margin-bottom:1em; + margin-right:1em; +} +.dialogButton{ + font-size:0.85em; + padding:5px; + border-radius:5px; + margin-left:1em; + width:5em; + background-color:hsl(216, 89%, 72%); + color:white; + outline:none; + border: 1px solid #EEE; +} +.prev-year, .prev-month, .next-month, .next-year { + padding:4px; + width:2em; +} + +.prev-year:focus, .prev-month:focus, .next-month:focus, .next-year:focus{ + color:black; + border: 1px solid black; + background:hsl(217, 100%, 92%); + border-radius:10px; + outline:0; +} +.dialogButton:focus{ + border:1px solid black; + outline:0; +} +.dates{ + width:100%; + padding-left:1em; + padding-right:1em; + padding-top:1em; +} +th[scope="col"],.dateRow td{ + text-align:center; +} + +.fa-calendar-alt { + color:hsl(216, 89%, 72%); +} +.header { + cursor:default; + background-color:hsl(216, 89%, 72%); + padding:7px; + font-weight:bold; + text-transform:uppercase; + color:white; + display: flex; + justify-content: space-around; +} +.header button{ + border-style:none; + background:transparent; +} + +.header span { + display:inline-block; +} + +.dateRow{ + border:1px solid black; +} + + +.cell { + outline:0; + border:0; + padding:0; + margin:0; + height: 40px; + width:40px; +} +.dateCell{ + padding:0; + margin:0; + line-height: inherit; + height:100%; + width: 100%; + font-size: 15px; + border:1px solid rgba(164, 164, 164, 0.961); + background:white; + border-radius:5px; +} +.dateCell::-moz-focus-inner{ + border:0; +} +.dateCell:focus{ + outline:0; + padding:0; + background-color:hsl(216, 89%, 72%); + border:1px solid black; +} +.lastFocused { + outline:0; + padding:0; + background-color:hsl(216, 80%, 92%); + border:1px solid rgb(100, 100, 100); +} +.disabled, .dateCell:disabled,{ + cursor: not-allowed; + color: #afafaf; +} + +.dateCell:disabled{ + border:1.3px solid #EEE; + background:#EEE; + cursor: not-allowed; +} +.fa-lg { + color:white; +} \ No newline at end of file diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html new file mode 100644 index 0000000000..76c42a3243 --- /dev/null +++ b/examples/datepicker/datepicker.html @@ -0,0 +1,651 @@ + + + + +Date Picker Combobox Example | WAI-ARIA Authoring Practices 1.1 + + + + + + + + + + + + + + + + + +
+

Date Picker Combobox Example

+

+ The following command and input examples demonstrate the + date picker design pattern which is an example of a combobox that opens a dialog box. + The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the calendar button. + The date picker dialog uses a grid pattern to show and select a date, and buttons for changing the month and year shown in the grid. For compatibility with mobile browsers, keyboard focus is moved to the calender button when the date picker dialog box closes, rather than the text input control. In some mobile browsers moving focus to the input control causes the date picker dialog to open automatically, which is avoided by moving focus to the calendar button. The accessible name for the calender button and the value of the textbox value are updated when the date picker dialog closes. +

+
+

Example

+ + +
+ + + +
+ +
+ +
+

Keyboard Support

+ +
+

Date Input

+ + + + + + + + + + + + + +
KeyFunction
Down Arrow +
    +
  • Open the calendar dialog.
  • +
  • Move focus to current date.
  • +
+
+
+ +
+

Calendar Button

+ + + + + + + + + + + + + +
KeyFunction
Space,
Return
+
    +
  • Toggles calendar dialog.
  • +
  • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, for compatibility with mobile browsers.
  • +
+
+
+ +
+

Next/Previous Buttons (for month and year)

+ + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space,
Return
Change the month for selecting a date.
ESCClose the dialog and move focus back to the calendar button.
TABMove focus to next button or grid inside the dialog.
Shift +
TAB
Move focus to previous button or grid inside the dialog.
+
+ +
+

Calendar Grid

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space,
Return
+
    +
  • Select the date, close the dialog box and move focus to the calendar button.
  • +
  • Focus is move to calendar button for compatibility with mobile browsers.
  • +
+
Up ArrowMove the focus to the same day of the previous week.
Down ArrowMove the focus to the same day of the next week.
Right ArrowMove the focus to the next day.
Left ArrowMove the focus to the previous day.
PageUp +
    +
  • Change the calendar to the previous month.
  • +
  • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
  • +
+
Shift+
PageUp
+
    +
  • Change the calendar to the previous Year.
  • +
  • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
  • +
+
PageDown +
    +
  • Change the calendar to the next month.
  • +
  • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
  • +
+
Shift+
PageDown
+
    +
  • Change the calendar to the next Year.
  • +
  • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
  • +
+
TABMove focus to next button inside the dialog.
Shift +
TAB
Move focus to previous button inside the dialog.
ESC +
    +
  • Close the dialog and move focus back to calendar button.
  • +
  • Focus is move to calendar button for compatibility with mobile browsers.
  • +
+
+
+

OK and Cancel Buttons

+ + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space,
Return
+
    +
  • If it's "Cancel" button, closes the calendar dialog, moves focus to calendar button, does not update date in date input.
  • +
  • If it's "OK" button, closes the calendar dialog, moves focus to calendar button, does update date in date input.
  • +
+
TABMove focus to next button inside the dialog.
Shift +
TAB
Move focus to previous button or grid inside the dialog.
ESC +
    +
  • Closes the calendar dialog, moves focus to calendar button, does not update date in date input.
  • +
  • Focus is move to calendar button for compatibility with mobile browsers.
  • +
+
+
+
+ +
+

Role, Property, State, and Tabindex Attributes

+
+

Date Input

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
role="combobox"div +
    +
  • Identifies that the input has a combobox.
  • +
  • Required accessible name comes from the aria-label attribute.
  • +
+
aria-label="Date"div + Defines an accessible name, "Date", for the combobox. +
aria-haspopup="dialog"div + Identifies that the combobox opens a dialog box. +
aria-expanded="false"div + aria-expanded="false" when the date picker dialog box is closed. +
aria-expanded="true"div + aria-expanded="true" when the date picker dialog box is open. +
aria-controls="IDREF"input[type=text] + Provides a reference from the textbox and date picker dialog box. +
+
+ + +
+

Calendar Button

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
aria-haspopup="true"button + Indicate that the input has a popup context menu. +
aria-expanded="true"button +
    +
  • Indicate the popup context menu is currently expanded.
  • +
  • When the calendar dialog closed, the aria-expanded attribute is removed from element.
  • +
+
aria-label="String"button +
  • Initial value of accessible name is Pick a Date.
  • +
  • When user selected date, the accessible name will update to current date.
  • +
    +
    + + +
    +

    Next/Previous Buttons (for month and year)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    status + span + +
      +
    • Identifies the element that contains the current month and year as a polite live region.
    • +
    • When the month and year changes, the new value will automatically spoke by screen reader, and will not interpurt the current speech.
    • +
    +
    aira-control="IDREF"button + Provide a reference for screen reader to navigate to the reference content. +
    aira-label="String"button + The accessible name of the button is the month and year the button will change the calendar too. +
    +
    + +
    +

    Calendar Grid

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    dialog + div + + Identifies the element that serves as the dialog container. +
    + aria-labelledby="IDREF" + + div + + The accessible name for the dialog is current month and year. +
    grid + table + +
      +
    • Identifies the element that serves as the grid widget container.
    • +
    • Since the grid role is applied to a table element, the row , rowheader, colheader, and gridcell roles do not need to be specified because they are implied by tr, th, and td tags.
    • +
    +
    aira-labelledby="IDREF"table +
      +
    • Define the accessible name for the grid using current month and year.
    • +
    +
    aira-label="String"th +
      +
    • Define the accessible name as a day of week for column headers.
    • +
    +
    + td > button + +
      +
    • The button element is use to Identify the dates in calendar grid.
    • +
    • Accessible name for the button is the date which is defined by the text content of the button element.
    • +
    +
    + tabindex="0" + + td > button + + Identify the currently selected date and make it part of tab sequence of the dialog box. +
    + tabindex="-1" + + td > button + + Exclude the button from tab sequence to support grid cell navigation. +
    aria-selected="true"td > button + Identifies the currently selected date. +
    disabledtd > button + Identifies buttons that represent dates that are not part of current month but are displayed visually. +
    +
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    +
    + + + diff --git a/examples/datepicker/js/dateInput.js b/examples/datepicker/js/dateInput.js new file mode 100644 index 0000000000..4400b17d5f --- /dev/null +++ b/examples/datepicker/js/dateInput.js @@ -0,0 +1,78 @@ +var DateInput = function (domNode,buttonNode, datepicker) { + this.domNode = domNode; + this.buttonNode = buttonNode; + this.datepicker = datepicker; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +DateInput.prototype.init = function () { + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); + this.buttonNode.addEventListener('keydown', this.handleButtonKeyDown.bind(this)); +}; + + +DateInput.prototype.handleKeyDown = function (event) { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.DOWN: + this.datepicker.open(this.domNode.parentElement); + flag = true; + break; + case this.keyCode.ESC: + this.datepicker.close(this.domNode.parentElement); + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DateInput.prototype.handleButtonClick = function () { + if (this.domNode.getAttribute('aria-expanded') === 'true') { + this.datepicker.close(this.domNode.parentElement); + } + else { + this.datepicker.open(this.domNode.parentElement); + } +}; + +DateInput.prototype.handleButtonKeyDown = function (event) { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.handleButtonClick(); + flag = true; + break; + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } + +}; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js new file mode 100644 index 0000000000..422db33d93 --- /dev/null +++ b/examples/datepicker/js/datepicker.js @@ -0,0 +1,610 @@ +window.addEventListener('load' , function () { + + var datepickerInput = document.getElementsByClassName('datepickerInput'); + var datepickerButton = document.getElementsByClassName('datepickerButton'); + var datepickerDialog = document.getElementsByClassName('datepickerDialog'); + var dp = new DatePicker(datepickerInput, datepickerButton, datepickerDialog); + dp.init(); + +}); + +var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + +var DatePicker = function (inputNode,buttonNode,dialogNode) { + this.body = document; + this.inputNode = inputNode[0]; + this.buttonNode = buttonNode[0]; + this.dialogNode = dialogNode[0]; + this.monthIndex = null; + this.month = null; + this.year = null; + this.lastMonthDates = null; + this.today = null; + + var header = this.dialogNode.children[0]; + this.prevYear = header.children[0]; + this.prevMonth = header.children[1]; + this.nextMonth = header.children[3]; + this.nextYear = header.children[4]; + + this.datesInMonth = null; + this.dates = null; + + this.datesArray = []; + this.datesArrayDOM = []; + + this.headerButtonClicked = false; + + this.selectDate = null; + + + this.dialogButton = document.getElementsByClassName('dialogButton'); + var today = new Date(); + this.today = today.getDate(); + this.lastFocused = null; + + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); + +}; +DatePicker.prototype.init = function () { + + this.monthIndex = new Date().getMonth(); + this.year = new Date().getFullYear(); + this.today = new Date().getDate(); + this.datesInMonth = [31, (((this.year % 4 === 0) && (this.year % 100 !== 0) && (this.year % 400 === 0)) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30 ,31, 30, 31]; + this.lastMonthDates = this.datesInMonth[this.monthIndex - 1]; + this.month = months[this.monthIndex]; + this.dates = this.datesInMonth[this.monthIndex]; + + this.body.addEventListener('click', this.handleBodyClick.bind(this)); + + + var di = new DateInput(this.inputNode, this.buttonNode, this); + di.init(); + + + for (var i = 0; i < this.dialogButton.length; i++) { + this.dialogButton[i].addEventListener('click', this.handleDialogButton.bind(this)); + this.dialogButton[i].addEventListener('keydown', this.handleDialogButton.bind(this)); + } + + this.prevMonth.addEventListener('click',this.handlePrevMonthButton.bind(this)); + this.nextMonth.addEventListener('click',this.handleNextMonthButton.bind(this)); + this.prevYear.addEventListener('click',this.handlePrevYearButton.bind(this)); + this.nextYear.addEventListener('click',this.handleNextYearButton.bind(this)); + + this.prevMonth.addEventListener('keydown',this.handlePrevMonthButton.bind(this)); + this.nextMonth.addEventListener('keydown',this.handleNextMonthButton.bind(this)); + this.prevYear.addEventListener('keydown', this.handlePrevYearButton.bind(this)); + this.nextYear.addEventListener('keydown', this.handleNextYearButton.bind(this)); + + + this.updateCalendar(this.month, this.year); + for (var i = 0;i < this.datesArray.length;i++) { + var dc = new DatePickerDay(this.datesArray[i], this); + dc.init(); + this.datesArrayDOM.push(dc); + } + + this.lastFocused = this.datesArray[this.today - 1]; +}; + +DatePicker.prototype.handleBodyClick = function () { + if (this.inputNode.hasAttribute('aria-expanded')) { + if (this.lastFocused) { + this.lastFocused.focus(); + this.lastFocused.tabIndex = 0; + } + else { + this.datesArray[this.today - 1].focus(); + this.datesArray[this.today - 1].tabIndex = 0; + } + } +}; +DatePicker.prototype.open = function (node) { + this.dialogNode.style.display = 'block'; + node.setAttribute('aria-expanded', 'true'); + if (this.lastFocused) { + this.lastFocused.focus(); + this.lastFocused.tabIndex = 0; + } + else { + this.datesArray[this.today - 1].focus(); + this.datesArray[this.today - 1].tabIndex = 0; + } + + +}; + +DatePicker.prototype.close = function (node) { + this.dialogNode.style.display = 'none'; + node.setAttribute('aria-expanded','false'); + this.buttonNode.focus(); +}; + +DatePicker.prototype.handleDialogButton = function (event) { + var tgt = event.currentTarget; + var flag = false; + if (event.type === 'keydown') { + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.TAB: + if (tgt.value === 'ok') { + this.prevYear.focus(); + if (event.shiftKey) { + this.dialogButton[0].focus(); + } + } + else if (tgt.value === 'cancel') { + this.dialogButton[1].focus(); + if (event.shiftKey) { + if (this.lastFocused) { + this.lastFocused.focus(); + } + else { + this.datesArray[this.today - 1].focus(); + } + } + } + flag = true; + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + if (tgt.value === 'ok') { + for (var i = 0; i < this.datesArrayDOM.length; i++) { + if (this.datesArrayDOM[i].domNode.classList.contains('lastFocused')) { + this.setSelectDate(this.datesArrayDOM[i]); + } + } + } + else if (tgt.value === 'cancel') { + this.close(this.inputNode.parentElement); + } + case this.keyCode.ESC: + this.close(this.inputNode.parentElement); + flag = true; + break; + } + } + else if (event.type === 'click') { + this.close(this.inputNode); + flag = true; + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; +DatePicker.prototype.handleNextYearButton = function (event) { + var type = event.type; + if (type === 'keydown') { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(this.inputNode.parentElement); + flag = true; + break; + case this.keyCode.TAB: + if (event.shiftKey) { + this.nextMonth.focus(); + } + else { + this.lastFocused ? this.lastFocused.focus() : this.datesArray[this.today - 1].focus(); + } + flag = true; + + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToNextYear(); + flag = true; + break; + } + } + else if (type === 'click') { + this.moveToNextYear(); + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; +DatePicker.prototype.handlePrevYearButton = function (event) { + var type = event.type; + if (type === 'keydown') { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(this.inputNode.parentElement); + flag = true; + break; + case this.keyCode.TAB: + if (event.shiftKey) { + this.dialogButton[1].focus(); + } + else { + this.prevMonth.focus(); + } + flag = true; + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToPrevYear(); + flag = true; + break; + } + } + else if (type === 'click') { + this.moveToPrevYear(); + flag = true; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; +DatePicker.prototype.handleNextMonthButton = function (event) { + var type = event.type; + if (type === 'keydown') { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(this.inputNode.parentElement); + flag = true; + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToNextMonth(); + flag = true; + break; + } + } + else if (type === 'click') { + this.moveToNextMonth(); + flag = true; + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; +DatePicker.prototype.handlePrevMonthButton = function (event) { + var type = event.type; + if (type === 'keydown') { + var tgt = event.currentTarget, + char = event.key, + flag = false; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(this.inputNode.parentElement); + flag = true; + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToPrevMonth(); + flag = true; + break; + } + } + else if (type === 'click') { + this.moveToPrevMonth(); + flag = true; + } + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; +DatePicker.prototype.moveToNextYear = function (dateCell) { + + this.year++; + this.headerButtonClicked = true; + this.updateDates(); + if (this.selectDate === null) { + this.datesArray[0].focus(); + } + else { + this.datesArray[parseInt(this.selectDate) - 1].focus(); + } +}; + + +DatePicker.prototype.moveToPrevYear = function (dateCell) { + + this.year--; + this.headerButtonClicked = true; + this.updateDates(); + if (this.selectDate == null) { + this.datesArray[0].focus(); + } + else { + this.datesArray[parseInt(this.selectDate) - 1].focus(); + } + +}; +DatePicker.prototype.moveToPrevMonth = function (dateCell) { + + this.monthIndex--; + if (this.monthIndex < 0) { + this.monthIndex = 11; + this.year--; + } + this.headerButtonClicked = true; + this.updateDates(); + if (this.selectDate == null) { + this.datesArray[0].focus(); + } + else { + this.datesArray[parseInt(this.selectDate) - 1].focus(); + } + +}; +DatePicker.prototype.moveToNextMonth = function (dateCell) { + + this.monthIndex++; + if (this.monthIndex > 11) { + this.monthIndex = 0; + this.year++; + } + this.headerButtonClicked = true; + this.updateDates(); + if (this.selectDate == null) { + this.datesArray[0].focus(); + } + else { + this.datesArray[parseInt(this.selectDate) - 1].focus(); + } + +}; + +DatePicker.prototype.setFocusToNewMonthYear = function (row, cellPos) { + if (row[cellPos[0]].children[cellPos[1]] === undefined || row[cellPos[0]].children[cellPos[1]].classList.contains('disabled') || row[cellPos[0]].innerHTML === '') { + if (cellPos[0] < 1) { + this.setFocusDate(row[cellPos[0] + 1].children[cellPos[1]].children[0]); + } + else { + cellPos[0]--; + console.log(cellPos); + this.setFocusDate(row[cellPos[0]].children[cellPos[1]].children[0]); + } + } + else { + this.setFocusDate(row[cellPos[0]].children[cellPos[1]].children[0]); + } +}; +DatePicker.prototype.setUpForNewMonthYear = function (row, dateCell) { + var weekNum, dayNum, cell; + for (var i = 0; i < row.length;i++) { + for (var j = 0; j < 7; j++) { + if (row[i].children[j] === undefined) { + break; + } + else { + if (row[i].children[j].children[0] === this.lastFocused) { + weekNum = i; + dayNum = j; + break; + } + } + } + } + cell = [weekNum, dayNum]; + return cell; +}; + + +DatePicker.prototype.setSelectDate = function (dateCell) { + for (var i = 0;i < this.datesArrayDOM.length;i++) { + this.datesArrayDOM[i].domNode.setAttribute('aria-selected', 'false'); + this.datesArrayDOM[i].domNode.tabIndex = '-1'; + if (this.datesArrayDOM[i] === dateCell) { + this.datesArrayDOM[i].domNode.tabIndex = '0'; + this.datesArrayDOM[i].domNode.focus(); + this.datesArrayDOM[i].domNode.setAttribute('aria-selected','true'); + } + } + this.selectDate = dateCell.domNode.innerHTML; + + var numberOfMonth = null; + var numberOfDate = null; + if ((this.monthIndex + 1).toString().length === 1) { + numberOfMonth = '0' + (this.monthIndex + 1); + } + else { + numberOfMonth = this.monthIndex + 1; + } + + if (this.selectDate.length === 1) { + numberOfDate = '0' + this.selectDate; + } + else { + numberOfDate = this.selectDate; + } + document.getElementById('id-date-1').value = numberOfMonth + '/' + numberOfDate + '/' + this.year; + this.buttonNode.setAttribute('aria-label', this.month + ' ' + numberOfDate + ' ' + this.year); + this.close(this.inputNode.parentElement); +}; + + +DatePicker.prototype.setFocusDate = function (button) { + for (var i = 0; i < this.datesArray.length; i++) { + var dc = this.datesArray[i]; + dc.classList.remove('lastFocused'); + if (dc === button) { + dc.tabIndex = 0; + dc.focus(); + dc.classList.add('lastFocused'); + this.lastFocused = dc; + } + else { + dc.tabIndex = -1; + } + } +}; + +DatePicker.prototype.setFocusToNextDay = function (dateCell) { + var nextDate = false; + var nextIndex = this.datesArray.indexOf(dateCell.domNode) + 1; + if (nextIndex > this.datesArray.length - 1) { + this.moveToNextMonth(); + nextIndex = 0; + } + nextDate = this.datesArray[nextIndex]; + + this.setFocusDate(nextDate); +}; + +DatePicker.prototype.setFocusToNextWeek = function (dateCell) { + var downDate = false; + var downIndex = this.datesArray.indexOf(dateCell.domNode) + 7; + if (downIndex > this.datesArray.length - 1) { + this.moveToNextMonth(); + downIndex = 0; + } + downDate = this.datesArray[downIndex]; + this.setFocusDate(downDate); +}; +DatePicker.prototype.setFocusToPrevWeek = function (dateCell) { + var upDate = false; + var upIndex = this.datesArray.indexOf(dateCell.domNode) - 7; + if (upIndex < 0) { + this.moveToPrevMonth(); + upIndex = this.datesArray.length - 1; + } + upDate = this.datesArray[upIndex]; + this.setFocusDate(upDate); +}; +DatePicker.prototype.setFocusToPrevDay = function (dateCell) { + var prevDate = false; + prevIndex = this.datesArray.indexOf(dateCell.domNode) - 1; + + if (prevIndex < 0) { + this.moveToPrevMonth(); + prevIndex = this.datesArray.length - 1; + } + prevDate = this.datesArray[prevIndex]; + this.setFocusDate(prevDate); +}; + +DatePicker.prototype.updateDates = function () { + + this.lastMonthDates = this.dates; + this.datesInMonth[1] = (((this.year % 4 === 0) && (this.year % 100 !== 0) && (this.year % 400 === 0)) ? 29 : 28); + this.month = months[this.monthIndex]; // show the string of the month + this.dates = this.datesInMonth[this.monthIndex]; // show the number of dates in that month + this.datesArray = []; + this.datesArrayDOM = []; + + this.updateCalendar(this.month, this.year); +}; +DatePicker.prototype.updateCalendar = function (month, year) { + + document.querySelector('.month-year-label').innerHTML = month + ' ' + year; + var firstDateOfMonth = new Date(this.year, this.monthIndex, 1); + var startDay = firstDateOfMonth.getDay(); + + var tbody = document.querySelector('.curr'); + tbody.innerHTML = ''; + for (var i = 0; i < 6;i++) { + var row = tbody.insertRow(i); + row.classList.add('dateRow'); + } + + var tableRow = document.getElementsByClassName('dateRow'); + for (var i = 0; i < tableRow.length;i++) { + for (var j = 0;j < 7; j++) { + var cell = document.createElement('td'); + var cellButton = document.createElement('button'); + cell.appendChild(cellButton); + tableRow[i].appendChild(cell); + cellButton.classList.add('dateCell'); + cell.classList.add('cell'); + } + } + var dateCells = document.querySelectorAll('.dateCell'); + var cells = document.querySelectorAll('.cell'); + + + for (var i = startDay - 1; i >= 0; i--) { + dateCells[i].innerHTML = this.lastMonthDates; + dateCells[i].setAttribute('value', '0'); + this.lastMonthDates--; + } + + for (var i = 1;i <= this.dates;i++) { + dateCells[startDay].innerHTML = i; + dateCells[startDay].setAttribute('value', i); + startDay++; + } + + if (tbody.rows[tableRow.length - 1].cells[0].querySelector('button').innerHTML === '') { + tbody.rows[tableRow.length - 1].innerHTML = ''; + tbody.rows[tableRow.length - 1].style.height = '40px'; + } + var j = 1; + for (var i = startDay; i < dateCells.length; i++) { + dateCells[i].innerHTML = j; + dateCells[i].setAttribute('value', '0'); + j++; + } + + + for (var i = 0;i < dateCells.length;i++) { + if (dateCells[i].getAttribute('value') === '0') { + dateCells[i].disabled = true; + dateCells[i].setAttribute('tabIndex', '1'); + cells[i].classList.add('disabled'); + } + else { + this.datesArray.push(dateCells[i]); + } + } + + if (this.headerButtonClicked) { // if the calendar toggled to previous month/year + for (var i = 0;i < this.datesArray.length;i++) { + var dc = new DatePickerDay(this.datesArray[i], this); + dc.init(); + this.datesArrayDOM.push(dc); + } + } + this.headerButtonClicked = false; + return true; +}; + + diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js new file mode 100644 index 0000000000..0ef40f8c55 --- /dev/null +++ b/examples/datepicker/js/datepickerDay.js @@ -0,0 +1,110 @@ +var DatePickerDay = function (domNode,datepicker) { + + this.domNode = domNode; + this.datepicker = datepicker; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +DatePickerDay.prototype.init = function () { + this.domNode.tabIndex = -1; + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + +}; + +DatePickerDay.prototype.handleKeyDown = function (event) { + var tgt = event.currentTarget, + char = event.key, + flag = false, + clickEvent; + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + switch (event.keyCode) { + case this.keyCode.ESC: + this.datepicker.close(this.datepicker.inputNode.parentElement); + break; + case this.keyCode.TAB: + this.datepicker.dialogButton[0].focus(); + if (event.shiftKey) { + this.datepicker.nextYear.focus(); + } + flag = true; + break; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.datepicker.setSelectDate(this); + flag = true; + break; + case this.keyCode.RIGHT: + this.datepicker.setFocusToNextDay(this); + flag = true; + break; + case this.keyCode.LEFT: + this.datepicker.setFocusToPrevDay(this); + flag = true; + break; + case this.keyCode.UP: + this.datepicker.setFocusToPrevWeek(this); + flag = true; + break; + case this.keyCode.DOWN: + this.datepicker.setFocusToNextWeek(this); + flag = true; + break; + case this.keyCode.PAGEUP: + var row = document.getElementsByClassName('dateRow'); + var cell = this.datepicker.setUpForNewMonthYear(row, this); + if (event.shiftKey) { + this.datepicker.moveToPrevYear(this); + } + else { + this.datepicker.moveToPrevMonth(this); + } + console.log(cell); + var newRow = document.getElementsByClassName('dateRow'); + this.datepicker.setFocusToNewMonthYear(newRow, cell); + console.log(cell); + + flag = true; + break; + case this.keyCode.PAGEDOWN: + var row = document.getElementsByClassName('dateRow'); + var cell = this.datepicker.setUpForNewMonthYear(row, this); + if (event.shiftKey) { + this.datepicker.moveToNextYear(this); + } + else { + this.datepicker.moveToNextMonth(this); + } + this.datepicker.setFocusToNewMonthYear(row, cell); + flag = true; + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } + +}; +DatePickerDay.prototype.handleClick = function (event) { + this.datepicker.setSelectDate(this); + event.stopPropagation(); + event.preventDefault(); + +}; From c725a871369a577c4310b4e9591f85b81c3cbca5 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 4 Dec 2018 00:26:35 -0800 Subject: [PATCH 002/137] Date Picker Example: Add link to development issue --- examples/datepicker/datepicker.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 76c42a3243..2be3165f59 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -2,7 +2,7 @@ -Date Picker Combobox Example | WAI-ARIA Authoring Practices 1.1 +Date Picker Example Using ARIA 1.1 Combobox | WAI-ARIA Authoring Practices 1.1 @@ -28,7 +28,12 @@
    -

    Date Picker Combobox Example

    +

    Date Picker Example Using ARIA 1.1 Combobox

    +

    + NOTE: This example page is work in progress. + Please provide feedback in + issue 34. +

    The following command and input examples demonstrate the date picker design pattern which is an example of a combobox that opens a dialog box. From 621cb78f4dcbe7339e134d94ea00fa6af657b153 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 10 Dec 2018 13:46:49 -0600 Subject: [PATCH 003/137] updated datepicker example and documentation --- examples/datepicker/css/datepicker.css | 15 +- examples/datepicker/datepicker.html | 455 +++++++++++++++---------- examples/datepicker/js/datepicker.js | 71 ++-- 3 files changed, 323 insertions(+), 218 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index ad1084b1b3..207ba7af27 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -29,12 +29,14 @@ label{ border-radius:5px; padding:0; } + .dialogButtonGroup{ text-align:right; margin-top:1em; margin-bottom:1em; margin-right:1em; } + .dialogButton{ font-size:0.85em; padding:5px; @@ -46,12 +48,19 @@ label{ outline:none; border: 1px solid #EEE; } -.prev-year, .prev-month, .next-month, .next-year { + +.prevYear, +.prevMonth, +.nextMonth, +.nextYear { padding:4px; width:2em; } -.prev-year:focus, .prev-month:focus, .next-month:focus, .next-year:focus{ +.prevYear:focus, +.prevMonth:focus, +.nextMonth:focus, +.nextMear:focus{ color:black; border: 1px solid black; background:hsl(217, 100%, 92%); @@ -145,4 +154,4 @@ th[scope="col"],.dateRow td{ } .fa-lg { color:white; -} \ No newline at end of file +} diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 2be3165f59..9816c18721 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -35,8 +35,7 @@

    Date Picker Example Using ARIA 1.1 Combobox

    issue 34.

    - The following command and input examples demonstrate the - date picker design pattern which is an example of a combobox that opens a dialog box. + The date picker is an example of the combobox design pattern that opens a dialog box. The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the calendar button. The date picker dialog uses a grid pattern to show and select a date, and buttons for changing the month and year shown in the grid. For compatibility with mobile browsers, keyboard focus is moved to the calender button when the date picker dialog box closes, rather than the text input control. In some mobile browsers moving focus to the input control causes the date picker dialog to open automatically, which is avoided by moving focus to the calendar button. The accessible name for the calender button and the value of the textbox value are updated when the date picker dialog closes.

    @@ -50,6 +49,7 @@

    Example

    role="combobox" aria-expanded="false" aria-haspopup="dialog" + aria-owns="id-datepicker-1" aria-label="Date"> @@ -65,47 +65,60 @@

    Example

    aria-haspopup="true" title="Select to change date" > - + - + @@ -381,10 +379,18 @@

    Combobox

    • Identifies the texbox as having a combobox behavior.
    • -
    • Accessible name comes from the label element.
    • +
    • Accessible name is defined using the aria-label attribute.
    + + + aria-label=Date + button + + Defines the accessible name of the combobox (e.g. "Date"). + + aria-haspopup=dialog @@ -526,8 +532,8 @@

    Dialog (Date Picker)

    -

    Buttons (Next/Previous Month and Year)

    - +

    Buttons (Next/Previous Month and Year)

    +
    @@ -584,8 +590,8 @@

    Buttons (Next/Previous Month and Year)

    -

    Grid (Calendar)

    -
    Role
    +

    Grid (Dates of a Month)

    +
    @@ -610,7 +616,7 @@

    Grid (Calendar)

    - + - + - + diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js index 0ef40f8c55..1eaf5bc0e7 100644 --- a/examples/datepicker/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -35,13 +35,15 @@ DatePickerDay.prototype.handleKeyDown = function (event) { return str.length === 1 && str.match(/\S/); } switch (event.keyCode) { + case this.keyCode.ESC: this.datepicker.close(this.datepicker.inputNode.parentElement); break; + case this.keyCode.TAB: this.datepicker.dialogButton[0].focus(); if (event.shiftKey) { - this.datepicker.nextYear.focus(); + this.datepicker.nextYearNode.focus(); } flag = true; break; From 60e13a52d3a0c793b389a3eb8b36d65aa27dd747 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 10 Dec 2018 16:10:07 -0600 Subject: [PATCH 006/137] made updates to the documentation --- examples/datepicker/datepicker.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index fd9404d73e..4fb523ffa7 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -651,7 +651,7 @@

    Grid (Dates of a Month)

    th
    @@ -708,7 +708,7 @@

    Grid (Dates of a Month)

    From 46c8f8236efd3669613a57e58015d1e4118c9e94 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 10 Dec 2018 16:12:31 -0600 Subject: [PATCH 007/137] made updates to the documentation --- examples/datepicker/datepicker.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 4fb523ffa7..74007c98e9 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -165,7 +165,7 @@

    Combobox

    -

    Button (Calendar Icon)

    +

    Button (Open Date Picker)

    Role
    aira-labelledby="IDREF"aira-labelledby=IDREF table
      @@ -620,7 +626,7 @@

      Grid (Calendar)

    aira-label="String"aira-label=String th
      @@ -639,13 +645,13 @@

      Grid (Calendar)

    colheadercolumnheader - th + th - Identifies the th element as having a role of columnheader. + The deafult role for a th[scope="col"] element is columnheader.
    - The deafult role for a th[scope="col"] element is columnheader. + The default role for a th[scope="col"] element is columnheader.
    • Identifies the currently selected date in the textbox.
    • -
    • All other dates do not have have an aria-selected attribute.
    • +
    • The aria-selected attribute is only set on the button representing the date in the textbox, all other buttons do not have an aria-selected attribute.
    @@ -244,7 +244,7 @@

    Button (Next/Previous Month and Year)

    -

    Grid (Days in a Month)

    +

    Grid (Days in Month)

    @@ -590,7 +590,7 @@

    Buttons (Next/Previous Month and Year)

    -

    Grid (Dates of a Month)

    +

    Grid (Days in Month)

    From bbe5d1e172fc1d4d8d820fa9baaf2628aa3c6033 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 10 Dec 2018 20:54:38 -0600 Subject: [PATCH 008/137] made updated code --- examples/datepicker/css/datepicker.css | 163 +++++++++++++----------- examples/datepicker/datepicker.html | 2 +- examples/datepicker/js/dateInput.js | 8 +- examples/datepicker/js/datepicker.js | 49 +++---- examples/datepicker/js/datepickerDay.js | 2 +- 5 files changed, 120 insertions(+), 104 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index a27e115f67..a1b9bb03d7 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -1,11 +1,7 @@ -* { - margin: 0; - padding: 0; - /* border:0; */ -} .datepicker{ margin-top:1em; } + .datepicker button.icon { border-style:none; text-align:left; @@ -25,29 +21,35 @@ label{ width:45%; clear:both; display:none; - border:3px solid rgb(130, 177, 230); + border:3px solid hsl(216, 80%, 55%); margin-top:1em; border-radius:5px; padding:0; } -.dialogButtonGroup{ - text-align:right; - margin-top:1em; - margin-bottom:1em; - margin-right:1em; +.datepickerDialog button::-moz-focus-inner { + border:0; } -.dialogButton{ - font-size:0.85em; - padding:5px; - border-radius:5px; - margin-left:1em; - width:5em; - background-color:hsl(216, 89%, 72%); - color:white; - outline:none; - border: 1px solid #EEE; + +.header { + cursor:default; + background-color:hsl(216, 80%, 55%); + padding:7px; + font-weight:bold; + text-transform:uppercase; + color: white; + display: flex; + justify-content: space-around; +} + +.header button { + border-style:none; + background:transparent; +} + +.header span { + display:inline-block; } .prevYear, @@ -56,52 +58,60 @@ label{ .nextYear { padding:4px; width:2em; + color: white; } .prevYear:focus, .prevMonth:focus, .nextMonth:focus, -.nextMear:focus{ - color:black; - border: 1px solid black; - background:hsl(217, 100%, 92%); - border-radius:10px; - outline:0; +.nextYear:focus{ + color: black; + padding: 2px; + border: 2px solid black; + background: hsl(216, 80%, 92%); + border-radius: 4px; + outline: 0; } -.dialogButton:focus{ - border:1px solid black; - outline:0; + +.dialogButtonGroup{ + text-align:right; + margin-top:1em; + margin-bottom:1em; + margin-right:1em; } -.dates{ - width:100%; - padding-left:1em; - padding-right:1em; - padding-top:1em; + +.dialogButton{ + padding:5px; + margin-left:1em; + width:5em; + background-color: hsl(216, 80%, 92%); + font-size:0.85em; + color:black; + outline: none; + border: 1px solid hsl(216, 80%, 92%); + border-radius:5px; } -th[scope="col"],.dateRow td{ - text-align:center; + +.dialogButton:focus{ + padding: 4px; + border: 2px solid black; } .fa-calendar-alt { color:hsl(216, 89%, 72%); } -.header { - cursor:default; - background-color:hsl(216, 89%, 72%); - padding:7px; - font-weight:bold; - text-transform:uppercase; - color:white; - display: flex; - justify-content: space-around; -} -.header button{ - border-style:none; - background:transparent; + + +table.dates { + width:100%; + padding-left:1em; + padding-right:1em; + padding-top:1em; } -.header span { - display:inline-block; +table.dates th, +table.dates td{ + text-align:center; } .dateRow{ @@ -109,7 +119,7 @@ th[scope="col"],.dateRow td{ } -.cell { +.date { outline:0; border:0; padding:0; @@ -117,42 +127,47 @@ th[scope="col"],.dateRow td{ height: 40px; width:40px; } -.dateCell{ + +.dateCell { padding:0; margin:0; line-height: inherit; height:100%; width: 100%; - font-size: 15px; - border:1px solid rgba(164, 164, 164, 0.961); - background:white; + border: 1px solid #EEE; border-radius:5px; + font-size: 15px; + background: #EEE; } -.dateCell::-moz-focus-inner{ - border:0; -} -.dateCell:focus{ - outline:0; + +.dateCell:focus, +.dateCell:hover { padding:0; - background-color:hsl(216, 89%, 72%); - border:1px solid black; + background-color:hsl(216, 80%, 92%); } -.lastFocused { + +.dateCell:focus { + border-width: 2px; + border-color: rgb(100, 100, 100); outline:0; - padding:0; +} + +.dateCell[aria-selected] { background-color:hsl(216, 80%, 92%); - border:1px solid rgb(100, 100, 100); + border-color: rgb(100, 100, 100); } -.disabled, .dateCell:disabled,{ + + +.disabled { cursor: not-allowed; color: #afafaf; } -.dateCell:disabled{ - border:1.3px solid #EEE; - background:#EEE; +.dateCell:disabled { + color: #777; + background-color: #FFF; + border: none; cursor: not-allowed; } -.fa-lg { - color:white; -} + + diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 74007c98e9..dd4e6d0afe 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -122,7 +122,7 @@

    Example

    - +
    diff --git a/examples/datepicker/js/dateInput.js b/examples/datepicker/js/dateInput.js index 4400b17d5f..e605deb7dc 100644 --- a/examples/datepicker/js/dateInput.js +++ b/examples/datepicker/js/dateInput.js @@ -35,11 +35,11 @@ DateInput.prototype.handleKeyDown = function (event) { } switch (event.keyCode) { case this.keyCode.DOWN: - this.datepicker.open(this.domNode.parentElement); + this.datepicker.open(); flag = true; break; case this.keyCode.ESC: - this.datepicker.close(this.domNode.parentElement); + this.datepicker.close(); } if (flag) { event.stopPropagation(); @@ -49,10 +49,10 @@ DateInput.prototype.handleKeyDown = function (event) { DateInput.prototype.handleButtonClick = function () { if (this.domNode.getAttribute('aria-expanded') === 'true') { - this.datepicker.close(this.domNode.parentElement); + this.datepicker.close(); } else { - this.datepicker.open(this.domNode.parentElement); + this.datepicker.open(); } }; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index f9166d68d9..9b85cc5d01 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -3,8 +3,9 @@ var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; -var DatePicker = function (inputNode,buttonNode,dialogNode) { +var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { this.body = document; + this.comboboxNode = comboboxNode; this.inputNode = inputNode; this.buttonNode = buttonNode; this.dialogNode = dialogNode; @@ -16,6 +17,8 @@ var DatePicker = function (inputNode,buttonNode,dialogNode) { this.nextMonthNode = dialogNode.querySelector('.nextMonth'); this.nextYearNode = dialogNode.querySelector('.nextYear'); + this.tbodyNode = dialogNode.querySelector('table.dates tbody'); + this.monthIndex = null; this.month = null; this.year = null; @@ -109,9 +112,10 @@ DatePicker.prototype.handleBodyClick = function () { } } }; -DatePicker.prototype.open = function (node) { + +DatePicker.prototype.open = function () { this.dialogNode.style.display = 'block'; - node.setAttribute('aria-expanded', 'true'); + this.comboboxNode.setAttribute('aria-expanded', 'true'); if (this.lastFocused) { this.lastFocused.focus(); this.lastFocused.tabIndex = 0; @@ -126,7 +130,7 @@ DatePicker.prototype.open = function (node) { DatePicker.prototype.close = function (node) { this.dialogNode.style.display = 'none'; - node.setAttribute('aria-expanded','false'); + this.comboboxNode.setAttribute('aria-expanded','false'); this.buttonNode.focus(); }; @@ -168,16 +172,16 @@ DatePicker.prototype.handleDialogButton = function (event) { } } else if (tgt.value === 'cancel') { - this.close(this.inputNode.parentElement); + this.close(); } case this.keyCode.ESC: - this.close(this.inputNode.parentElement); + this.close(); flag = true; break; } } else if (event.type === 'click') { - this.close(this.inputNode); + this.close(); flag = true; } if (flag) { @@ -196,7 +200,7 @@ DatePicker.prototype.handleNextYearButton = function (event) { } switch (event.keyCode) { case this.keyCode.ESC: - this.close(this.inputNode.parentElement); + this.close(); flag = true; break; case this.keyCode.TAB: @@ -235,7 +239,7 @@ DatePicker.prototype.handlePrevYearButton = function (event) { } switch (event.keyCode) { case this.keyCode.ESC: - this.close(this.inputNode.parentElement); + this.close(); flag = true; break; case this.keyCode.TAB: @@ -275,7 +279,7 @@ DatePicker.prototype.handleNextMonthButton = function (event) { } switch (event.keyCode) { case this.keyCode.ESC: - this.close(this.inputNode.parentElement); + this.close(); flag = true; break; case this.keyCode.RETURN: @@ -305,7 +309,7 @@ DatePicker.prototype.handlePrevMonthButton = function (event) { } switch (event.keyCode) { case this.keyCode.ESC: - this.close(this.inputNode.parentElement); + this.close(); flag = true; break; case this.keyCode.RETURN: @@ -424,7 +428,7 @@ DatePicker.prototype.setUpForNewMonthYear = function (row, dateCell) { DatePicker.prototype.setSelectDate = function (dateCell) { for (var i = 0;i < this.datesArrayDOM.length;i++) { - this.datesArrayDOM[i].domNode.setAttribute('aria-selected', 'false'); + this.datesArrayDOM[i].domNode.removeAttribute('aria-selected'); this.datesArrayDOM[i].domNode.tabIndex = '-1'; if (this.datesArrayDOM[i] === dateCell) { this.datesArrayDOM[i].domNode.tabIndex = '0'; @@ -451,18 +455,16 @@ DatePicker.prototype.setSelectDate = function (dateCell) { } document.getElementById('id-date-1').value = numberOfMonth + '/' + numberOfDate + '/' + this.year; this.buttonNode.setAttribute('aria-label', this.month + ' ' + numberOfDate + ' ' + this.year); - this.close(this.inputNode.parentElement); + this.close(); }; DatePicker.prototype.setFocusDate = function (button) { for (var i = 0; i < this.datesArray.length; i++) { var dc = this.datesArray[i]; - dc.classList.remove('lastFocused'); if (dc === button) { dc.tabIndex = 0; dc.focus(); - dc.classList.add('lastFocused'); this.lastFocused = dc; } else { @@ -532,10 +534,9 @@ DatePicker.prototype.updateCalendar = function (month, year) { var firstDateOfMonth = new Date(this.year, this.monthIndex, 1); var startDay = firstDateOfMonth.getDay(); - var tbody = document.querySelector('.curr'); - tbody.innerHTML = ''; + this.tbodyNode.innerHTML = ''; for (var i = 0; i < 6;i++) { - var row = tbody.insertRow(i); + var row = this.tbodyNode.insertRow(i); row.classList.add('dateRow'); } @@ -547,11 +548,11 @@ DatePicker.prototype.updateCalendar = function (month, year) { cell.appendChild(cellButton); tableRow[i].appendChild(cell); cellButton.classList.add('dateCell'); - cell.classList.add('cell'); + cell.classList.add('date'); } } var dateCells = document.querySelectorAll('.dateCell'); - var cells = document.querySelectorAll('.cell'); + var cells = document.querySelectorAll('.date'); for (var i = startDay - 1; i >= 0; i--) { @@ -566,9 +567,9 @@ DatePicker.prototype.updateCalendar = function (month, year) { startDay++; } - if (tbody.rows[tableRow.length - 1].cells[0].querySelector('button').innerHTML === '') { - tbody.rows[tableRow.length - 1].innerHTML = ''; - tbody.rows[tableRow.length - 1].style.height = '40px'; + if (this.tbodyNode.rows[tableRow.length - 1].cells[0].querySelector('button').innerHTML === '') { + this.tbodyNode.rows[tableRow.length - 1].innerHTML = ''; + this.tbodyNode.rows[tableRow.length - 1].style.height = '40px'; } var j = 1; for (var i = startDay; i < dateCells.length; i++) { @@ -610,7 +611,7 @@ window.addEventListener('load' , function () { var dpInput = dp.querySelector('input'); var dpButton = dp.querySelector('button'); var dpDialog = document.getElementById(dp.getAttribute('aria-owns')); - var datePicker = new DatePicker(dpInput, dpButton, dpDialog); + var datePicker = new DatePicker(dp, dpInput, dpButton, dpDialog); datePicker.init(); } ); diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js index 1eaf5bc0e7..715f855fbc 100644 --- a/examples/datepicker/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -37,7 +37,7 @@ DatePickerDay.prototype.handleKeyDown = function (event) { switch (event.keyCode) { case this.keyCode.ESC: - this.datepicker.close(this.datepicker.inputNode.parentElement); + this.datepicker.close(); break; case this.keyCode.TAB: From d90147f5dbe8c2d9f6a8f18577d1b04f374de2af Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 13 Dec 2018 20:16:06 -0600 Subject: [PATCH 009/137] updated example --- examples/datepicker/css/datepicker.css | 36 +- examples/datepicker/js/datepicker.js | 956 +++++++++++++----------- examples/datepicker/js/datepickerDay.js | 101 ++- 3 files changed, 586 insertions(+), 507 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index a1b9bb03d7..e6c7fa0da3 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -57,30 +57,29 @@ label{ .nextMonth, .nextYear { padding:4px; - width:2em; + width:24px; + height:24px; color: white; } .prevYear:focus, .prevMonth:focus, .nextMonth:focus, -.nextYear:focus{ - color: black; +.nextYear:focus { padding: 2px; - border: 2px solid black; - background: hsl(216, 80%, 92%); + border: 2px solid white; border-radius: 4px; outline: 0; } -.dialogButtonGroup{ +.dialogButtonGroup { text-align:right; margin-top:1em; margin-bottom:1em; margin-right:1em; } -.dialogButton{ +.dialogButton { padding:5px; margin-left:1em; width:5em; @@ -92,7 +91,7 @@ label{ border-radius:5px; } -.dialogButton:focus{ +.dialogButton:focus { padding: 4px; border: 2px solid black; } @@ -114,12 +113,11 @@ table.dates td{ text-align:center; } -.dateRow{ +.dateRow { border:1px solid black; } - -.date { +.dateCell { outline:0; border:0; padding:0; @@ -128,7 +126,7 @@ table.dates td{ width:40px; } -.dateCell { +.dateButton { padding:0; margin:0; line-height: inherit; @@ -140,30 +138,32 @@ table.dates td{ background: #EEE; } -.dateCell:focus, -.dateCell:hover { +.dateButton:focus, +.dateButton:hover { padding:0; background-color:hsl(216, 80%, 92%); } -.dateCell:focus { +.dateButton:focus { border-width: 2px; border-color: rgb(100, 100, 100); outline:0; } -.dateCell[aria-selected] { - background-color:hsl(216, 80%, 92%); +.dateButton[aria-selected] { border-color: rgb(100, 100, 100); } +.dateButton[tabindex="0"] { + background-color:hsl(216, 80%, 92%); +} .disabled { cursor: not-allowed; color: #afafaf; } -.dateCell:disabled { +.dateButton:disabled { color: #777; background-color: #FFF; border: none; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 9b85cc5d01..d5c4bf1f68 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -1,9 +1,8 @@ -var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { + var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + this.body = document; this.comboboxNode = comboboxNode; this.inputNode = inputNode; @@ -17,28 +16,27 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { this.nextMonthNode = dialogNode.querySelector('.nextMonth'); this.nextYearNode = dialogNode.querySelector('.nextYear'); + this.okButtonNode = dialogNode.querySelector('button[value="ok"]'); + this.cancelButtonNode = dialogNode.querySelector('button[value="cancel"]'); + this.tbodyNode = dialogNode.querySelector('table.dates tbody'); - this.monthIndex = null; - this.month = null; - this.year = null; - this.lastMonthDates = null; - this.today = null; + this.lastRowNode = null; + + var date = new Date(); - this.datesInMonth = null; - this.dates = null; + this.year = date.getFullYear(); + this.month = date.getMonth(); + this.day = date.getDate() - 1; - this.datesArray = []; - this.datesArrayDOM = []; + this.daysInCurrentMonth = this.daysInMonth(); + this.daysInLastMonth = this.daysInLastMonth(); - this.headerButtonClicked = false; + this.days = []; - this.selectDate = null; + this.selectedDay = new Date(this.year, this.month, this.day); - this.dialogButton = document.getElementsByClassName('dialogButton'); - var today = new Date(); - this.today = today.getDate(); - this.lastFocused = null; + this.currentDay = null; this.keyCode = Object.freeze({ @@ -57,210 +55,322 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { }); }; -DatePicker.prototype.init = function () { - this.monthIndex = new Date().getMonth(); - this.year = new Date().getFullYear(); - this.today = new Date().getDate(); - this.datesInMonth = [31, (((this.year % 4 === 0) && (this.year % 100 !== 0) && (this.year % 400 === 0)) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30 ,31, 30, 31]; - this.lastMonthDates = this.datesInMonth[this.monthIndex - 1]; - this.month = months[this.monthIndex]; - this.dates = this.datesInMonth[this.monthIndex]; - this.body.addEventListener('click', this.handleBodyClick.bind(this)); +DatePicker.prototype.init = function () { + this.body.addEventListener('click', this.handleBodyClick.bind(this)); var di = new DateInput(this.inputNode, this.buttonNode, this); di.init(); + this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); + this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); - for (var i = 0; i < this.dialogButton.length; i++) { - this.dialogButton[i].addEventListener('click', this.handleDialogButton.bind(this)); - this.dialogButton[i].addEventListener('keydown', this.handleDialogButton.bind(this)); - } + this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); + this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); - this.prevMonthNode.addEventListener('click',this.handlePrevMonthButton.bind(this)); + this.prevMonthNode.addEventListener('click',this.handlePreviousMonthButton.bind(this)); this.nextMonthNode.addEventListener('click',this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('click',this.handlePrevYearButton.bind(this)); + this.prevYearNode.addEventListener('click',this.handlePreviousYearButton.bind(this)); this.nextYearNode.addEventListener('click',this.handleNextYearButton.bind(this)); - this.prevMonthNode.addEventListener('keydown',this.handlePrevMonthButton.bind(this)); + this.prevMonthNode.addEventListener('keydown',this.handlePreviousMonthButton.bind(this)); this.nextMonthNode.addEventListener('keydown',this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('keydown', this.handlePrevYearButton.bind(this)); + this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); + // Create Grid of Dates - this.updateCalendar(this.month, this.year); - for (var i = 0; i < this.datesArray.length; i++) { - var dc = new DatePickerDay(this.datesArray[i], this); - dc.init(); - this.datesArrayDOM.push(dc); + this.tbodyNode.innerHTML = ''; + var index = 0; + for (var i = 0; i < 6;i++) { + var row = this.tbodyNode.insertRow(i); + this.lastRowNode = row; + row.classList.add('dateRow'); + for (var j = 0;j < 7; j++) { + var cell = document.createElement('td'); + cell.classList.add('dateCell'); + var cellButton = document.createElement('button'); + cellButton.classList.add('dateButton'); + cell.appendChild(cellButton); + row.appendChild(cell); + var dpDay = new DatePickerDay(cellButton, this, index, i, j); + dpDay.init(); + this.days.push(dpDay); + index++; + } } - this.lastFocused = this.datesArray[this.today - 1]; + this.updateGrid(); + this.setFocusDay(); }; -DatePicker.prototype.handleBodyClick = function () { - if (this.inputNode.hasAttribute('aria-expanded')) { - if (this.lastFocused) { - this.lastFocused.focus(); - this.lastFocused.tabIndex = 0; - } - else { - this.datesArray[this.today - 1].focus(); - this.datesArray[this.today - 1].tabIndex = 0; +DatePicker.prototype.updateGrid = function (year, month) { + + var i; + + if (typeof year !== 'number') year = this.year; + if (typeof month !== 'number') month = this.month; + + this.MonthYearNode.innerHTML = month + ' ' + year; + + this.daysInCurrentMonth = this.daysInMonth(year, month); + + var lastMonth = month -1; + var lastYear = year; + if (lastMonth < 0) { + lastMonth = 11; + lastYear = year-1; + } + + var daysInLastMonth = this.daysInMonth(lastYear, lastMonth); + this.daysInLastMonth = daysInLastMonth; + + var nextMonth = month + 1; + var nextYear = year; + if (nextMonth > 11) { + nextMonth = 1; + nextYear = year+1; + } + + var firstDayOfMonth = new Date(year, month, 1); + var dayOfWeek = firstDayOfMonth.getDay(); + + for (i = dayOfWeek - 1; i >= 0; i--) { + daysInLastMonth--; + this.days[i].updateDay(true, lastYear, lastMonth, daysInLastMonth); + } + + for (var i = 0; i < this.daysInCurrentMonth; i++) { + var dpDay = this.days[dayOfWeek+i]; + dpDay.updateDay(false, year, month, i); + if ((this.selectedDay.getFullYear() === year) && + (this.selectedDay.getMonth() === month) && + (this.selectedDay.getDate() === i)) { + dpDay.domNode.setAttribute('aria-selected', 'true'); } } -}; -DatePicker.prototype.open = function () { - this.dialogNode.style.display = 'block'; - this.comboboxNode.setAttribute('aria-expanded', 'true'); - if (this.lastFocused) { - this.lastFocused.focus(); - this.lastFocused.tabIndex = 0; + var remainingButtons = 42 - this.daysInCurrentMonth - dayOfWeek; + + if (remainingButtons >= 7) { + remainingButtons = remainingButtons - 7; + this.hideLastRow(); } else { - this.datesArray[this.today - 1].focus(); - this.datesArray[this.today - 1].tabIndex = 0; + this.showLastRow(); } + for (var i = 0; i < remainingButtons; i++) { + this.days[dayOfWeek+this.daysInCurrentMonth+i].updateDay(true, nextYear, nextMonth, i); + } }; -DatePicker.prototype.close = function (node) { - this.dialogNode.style.display = 'none'; - this.comboboxNode.setAttribute('aria-expanded','false'); - this.buttonNode.focus(); -}; +// If after updating the grid the current day is on a disabled button move it to an enabled button in the same column +DatePicker.prototype.adjustCurrentDay = function (dayButton, onFirstRow, onLastRow) { + var cd = this.currentDay; -DatePicker.prototype.handleDialogButton = function (event) { - var tgt = event.currentTarget; - var flag = false; - if (event.type === 'keydown') { - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); + console.log('[adjustCurrentDay]'); + + if (typeof dayButton !== 'object') { + console.log('[adjustCurrentDay][currentDay]'); + dayButton = cd; + } + + if (typeof onFirstRow !== 'boolean') { + onFirstRow = cd.row === 0; + onFirstRow = onFirstRow || ((cd.row === 1) && (this.days[this.index-7].isDisabled())); + var onLastRow = cd.row === 5; + onLastRow = onLastRow || (cd.row === 3) && (this.days[this.index+7].isDisabled()); + onLastRow = onLastRow || (cd.row === 4) && (this.days[this.index+7].isDisabled()); + } + + + if (dayButton.isDisabled()) { + if (dayButton.row === 0 ) { + this.day = this.days[dayButton.index+7].day; } - switch (event.keyCode) { - case this.keyCode.TAB: - if (tgt.value === 'ok') { - this.prevYearNode.focus(); - if (event.shiftKey) { - this.dialogButton[0].focus(); - } - } - else if (tgt.value === 'cancel') { - this.dialogButton[1].focus(); - if (event.shiftKey) { - if (this.lastFocused) { - this.lastFocused.focus(); - } - else { - this.datesArray[this.today - 1].focus(); - } - } - } - flag = true; - break; - case this.keyCode.RETURN: - case this.keyCode.SPACE: - if (tgt.value === 'ok') { - for (var i = 0; i < this.datesArrayDOM.length; i++) { - if (this.datesArrayDOM[i].domNode.classList.contains('lastFocused')) { - this.setSelectDate(this.datesArrayDOM[i]); - } - } - } - else if (tgt.value === 'cancel') { - this.close(); - } - case this.keyCode.ESC: - this.close(); - flag = true; - break; + else { + if (this.days[dayButton.index-7].isDisabled()) { + this.day = this.days[dayButton.index-14].day; + } + else { + this.day = this.days[dayButton.index-7].day; + } } } - else if (event.type === 'click') { - this.close(); - flag = true; - } - if (flag) { - event.stopPropagation(); - event.preventDefault(); + else { + if (onFirstRow && (dayButton.row === 1) && (!this.days[dayButton.index-7].isDisabled())) { + this.day = this.days[dayButton.index-7].day; + } + else { + if (onLastRow && ((dayButton.row === 3)|| (dayButton.row === 4)) && (!this.days[dayButton.index+7].isDisabled())) { + this.day = this.days[dayButton.index+7].day; + } + else { + this.day = dayButton.day; + } + } } }; -DatePicker.prototype.handleNextYearButton = function (event) { - var type = event.type; - if (type === 'keydown') { - var tgt = event.currentTarget, - char = event.key, - flag = false; - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); + +DatePicker.prototype.hideLastRow = function () { + this.lastRowNode.style.visibility = 'hidden'; +}; + +DatePicker.prototype.showLastRow = function () { + this.lastRowNode.style.visibility = 'visible'; +}; + + +DatePicker.prototype.setFocusDay = function (flag) { + + if (typeof flag !== 'boolean') { + flag = true; + } + + var day = this.day; + var month = this.month; + var dp = this; + + this.days.forEach(function(d) { + if ((d.day == day) && + (d.month == month)) { + d.domNode.setAttribute('tabindex', '0'); + dp.currentDay = d; + if (flag ) { + d.domNode.focus(); + } } - switch (event.keyCode) { - case this.keyCode.ESC: - this.close(); - flag = true; - break; - case this.keyCode.TAB: - if (event.shiftKey) { - this.nextMonthNode.focus(); - } - else { - this.lastFocused ? this.lastFocused.focus() : this.datesArray[this.today - 1].focus(); - } - flag = true; - - break; - case this.keyCode.RETURN: - case this.keyCode.SPACE: - this.moveToNextYear(); - flag = true; - break; + else { + d.domNode.setAttribute('tabindex', '-1'); } + }); + +}; + +DatePicker.prototype.updateDate = function (year, month, day) { + this.year = year; + this.month = month; + this.day = day; +}; + +DatePicker.prototype.daysInLastMonth = function (year, month) { + + if (typeof year !== 'number') year = this.year; + if (typeof month !== 'number') month = this.month; + + var lastMonth = month -1; + var lastYear = year; + if (lastMonth < 0) { + lastMonth = 11; + lastYear = year-1; } - else if (type === 'click') { - this.moveToNextYear(); - } - if (flag) { - event.stopPropagation(); - event.preventDefault(); + + return this.daysInMonth(lastYear, lastMonth); + +}; + +DatePicker.prototype.daysInMonth = function (year, month) { + + if (typeof year !== 'number') year = this.year; + if (typeof month !== 'number') month = this.month; + + switch(month) { + + case 0: + case 2: + case 4: + case 6: + case 7: + case 9: + case 11: + return 31; + break; + + case 1: + return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); + break; + + case 3: + case 5: + case 8: + case 10: + return 30; + break; + + default: + break; + } + + return -1; + }; -DatePicker.prototype.handlePrevYearButton = function (event) { - var type = event.type; - if (type === 'keydown') { - var tgt = event.currentTarget, - char = event.key, - flag = false; - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - switch (event.keyCode) { - case this.keyCode.ESC: - this.close(); - flag = true; - break; - case this.keyCode.TAB: - if (event.shiftKey) { - this.dialogButton[1].focus(); - } - else { - this.prevMonthNode.focus(); - } - flag = true; - break; - case this.keyCode.RETURN: - case this.keyCode.SPACE: - this.moveToPrevYear(); - flag = true; - break; - } + + +DatePicker.prototype.open = function () { + this.dialogNode.style.display = 'block'; + this.comboboxNode.setAttribute('aria-expanded', 'true'); + this.setFocusDay(); +}; + +DatePicker.prototype.close = function (node) { + this.dialogNode.style.display = 'none'; + this.comboboxNode.setAttribute('aria-expanded','false'); + this.buttonNode.focus(); +}; + + +DatePicker.prototype.handleBodyClick = function () { + if (this.inputNode.hasAttribute('aria-expanded')) { + } - else if (type === 'click') { - this.moveToPrevYear(); - flag = true; +}; + +DatePicker.prototype.handleOkButton = function (event) { + var flag = false; + + switch(event.type) { + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.RETURN: + case this.keyCode.SPACE: + +// TO DO + + this.close(); + flag = true; + break; + + case this.keyCode.TAB: + if (!event.shiftKey) { + this.prevYearNode.focus(); + flag = true; + } + break; + + case this.keyCode.ESC: + this.close(); + flag = true; + break; + + default: + break; + + } + break; + + case 'click': + this.close(); + flag = true; + break; + + default: + break; } if (flag) { @@ -268,337 +378,283 @@ DatePicker.prototype.handlePrevYearButton = function (event) { event.preventDefault(); } }; -DatePicker.prototype.handleNextMonthButton = function (event) { - var type = event.type; - if (type === 'keydown') { - var tgt = event.currentTarget, - char = event.key, - flag = false; - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - switch (event.keyCode) { - case this.keyCode.ESC: - this.close(); - flag = true; - break; - case this.keyCode.RETURN: - case this.keyCode.SPACE: - this.moveToNextMonth(); - flag = true; - break; - } - } - else if (type === 'click') { - this.moveToNextMonth(); - flag = true; + +DatePicker.prototype.handleCancelButton = function (event) { + var flag = false; + + switch(event.type) { + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.close(); + flag = true; + break; + + case this.keyCode.ESC: + this.close(); + flag = true; + break; + + default: + break; + + } + break; + + case 'click': + this.close(); + flag = true; + break; + + default: + break; } + if (flag) { event.stopPropagation(); event.preventDefault(); } }; -DatePicker.prototype.handlePrevMonthButton = function (event) { - var type = event.type; - if (type === 'keydown') { - var tgt = event.currentTarget, - char = event.key, - flag = false; - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - switch (event.keyCode) { - case this.keyCode.ESC: - this.close(); - flag = true; - break; - case this.keyCode.RETURN: - case this.keyCode.SPACE: - this.moveToPrevMonth(); - flag = true; - break; - } - } - else if (type === 'click') { - this.moveToPrevMonth(); - flag = true; + +DatePicker.prototype.handleNextYearButton = function (event) { + var flag = false; + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(); + flag = true; + break; + + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToNextYear(); + this.adjustCurrentDay(); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + this.moveToNextYear(); + this.adjustCurrentDay(); + this.setFocusDay(false); + break; + + default: + break; } + if (flag) { event.stopPropagation(); event.preventDefault(); } }; -DatePicker.prototype.moveToNextYear = function (dateCell) { - this.year++; - this.headerButtonClicked = true; - this.updateDates(); - if (this.selectDate === null) { - this.datesArray[0].focus(); - } - else { - this.datesArray[parseInt(this.selectDate) - 1].focus(); - } -}; +DatePicker.prototype.handlePreviousYearButton = function (event) { + var flag = false; + switch (event.type) { -DatePicker.prototype.moveToPrevYear = function (dateCell) { + case 'keydown': - this.year--; - this.headerButtonClicked = true; - this.updateDates(); - if (this.selectDate == null) { - this.datesArray[0].focus(); - } - else { - this.datesArray[parseInt(this.selectDate) - 1].focus(); - } + switch (event.keyCode) { -}; -DatePicker.prototype.moveToPrevMonth = function (dateCell) { + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToPreviousYear(); + this.adjustCurrentDay(); + this.setFocusDay(false); + flag = true; + break; - this.monthIndex--; - if (this.monthIndex < 0) { - this.monthIndex = 11; - this.year--; - } - this.headerButtonClicked = true; - this.updateDates(); - if (this.selectDate == null) { - this.datesArray[0].focus(); - } - else { - this.datesArray[parseInt(this.selectDate) - 1].focus(); - } + case this.keyCode.TAB: + if (event.shiftKey) { + this.okButtonNode.focus(); + flag = true; + } + break; -}; -DatePicker.prototype.moveToNextMonth = function (dateCell) { + case this.keyCode.ESC: + this.close(); + flag = true; + break; - this.monthIndex++; - if (this.monthIndex > 11) { - this.monthIndex = 0; - this.year++; - } - this.headerButtonClicked = true; - this.updateDates(); - if (this.selectDate == null) { - this.datesArray[0].focus(); - } - else { - this.datesArray[parseInt(this.selectDate) - 1].focus(); - } + default: + break; + } -}; + break; -DatePicker.prototype.setFocusToNewMonthYear = function (row, cellPos) { - if (row[cellPos[0]].children[cellPos[1]] === undefined || row[cellPos[0]].children[cellPos[1]].classList.contains('disabled') || row[cellPos[0]].innerHTML === '') { - if (cellPos[0] < 1) { - this.setFocusDate(row[cellPos[0] + 1].children[cellPos[1]].children[0]); - } - else { - cellPos[0]--; - console.log(cellPos); - this.setFocusDate(row[cellPos[0]].children[cellPos[1]].children[0]); - } + case 'click': + this.moveToPreviousYear(); + this.adjustCurrentDay(); + this.setFocusDay(false); + break; + + default: + break; } - else { - this.setFocusDate(row[cellPos[0]].children[cellPos[1]].children[0]); + + if (flag) { + event.stopPropagation(); + event.preventDefault(); } }; -DatePicker.prototype.setUpForNewMonthYear = function (row, dateCell) { - var weekNum, dayNum, cell; - for (var i = 0; i < row.length;i++) { - for (var j = 0; j < 7; j++) { - if (row[i].children[j] === undefined) { - break; - } - else { - if (row[i].children[j].children[0] === this.lastFocused) { - weekNum = i; - dayNum = j; + +DatePicker.prototype.handleNextMonthButton = function (event) { + var flag = false; + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(); + flag = true; + break; + + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToNextMonth(); + this.adjustCurrentDay(); + this.setFocusDay(false); + flag = true; break; - } } - } - } - cell = [weekNum, dayNum]; - return cell; -}; + break; -DatePicker.prototype.setSelectDate = function (dateCell) { - for (var i = 0;i < this.datesArrayDOM.length;i++) { - this.datesArrayDOM[i].domNode.removeAttribute('aria-selected'); - this.datesArrayDOM[i].domNode.tabIndex = '-1'; - if (this.datesArrayDOM[i] === dateCell) { - this.datesArrayDOM[i].domNode.tabIndex = '0'; - this.datesArrayDOM[i].domNode.focus(); - this.datesArrayDOM[i].domNode.setAttribute('aria-selected','true'); - } - } - this.selectDate = dateCell.domNode.innerHTML; + case 'click': + this.moveToNextMonth(); + this.adjustCurrentDay(); + this.setFocusDay(false); + break; - var numberOfMonth = null; - var numberOfDate = null; - if ((this.monthIndex + 1).toString().length === 1) { - numberOfMonth = '0' + (this.monthIndex + 1); - } - else { - numberOfMonth = this.monthIndex + 1; + default: + break; } - if (this.selectDate.length === 1) { - numberOfDate = '0' + this.selectDate; - } - else { - numberOfDate = this.selectDate; + if (flag) { + event.stopPropagation(); + event.preventDefault(); } - document.getElementById('id-date-1').value = numberOfMonth + '/' + numberOfDate + '/' + this.year; - this.buttonNode.setAttribute('aria-label', this.month + ' ' + numberOfDate + ' ' + this.year); - this.close(); }; +DatePicker.prototype.handlePreviousMonthButton = function (event) { + var flag = false; -DatePicker.prototype.setFocusDate = function (button) { - for (var i = 0; i < this.datesArray.length; i++) { - var dc = this.datesArray[i]; - if (dc === button) { - dc.tabIndex = 0; - dc.focus(); - this.lastFocused = dc; - } - else { - dc.tabIndex = -1; - } - } -}; + switch (event.type) { -DatePicker.prototype.setFocusToNextDay = function (dateCell) { - var nextDate = false; - var nextIndex = this.datesArray.indexOf(dateCell.domNode) + 1; - if (nextIndex > this.datesArray.length - 1) { - this.moveToNextMonth(); - nextIndex = 0; - } - nextDate = this.datesArray[nextIndex]; + case 'keydown': - this.setFocusDate(nextDate); -}; + switch (event.keyCode) { + case this.keyCode.ESC: + this.close(); + flag = true; + break; -DatePicker.prototype.setFocusToNextWeek = function (dateCell) { - var downDate = false; - var downIndex = this.datesArray.indexOf(dateCell.domNode) + 7; - if (downIndex > this.datesArray.length - 1) { - this.moveToNextMonth(); - downIndex = 0; + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.moveToPreviousMonth(); + this.adjustCurrentDay(); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + this.moveToPreviousMonth(); + this.adjustCurrentDay(); + this.setFocusDay(false); + flag = true; + break; + + default: + break; } - downDate = this.datesArray[downIndex]; - this.setFocusDate(downDate); -}; -DatePicker.prototype.setFocusToPrevWeek = function (dateCell) { - var upDate = false; - var upIndex = this.datesArray.indexOf(dateCell.domNode) - 7; - if (upIndex < 0) { - this.moveToPrevMonth(); - upIndex = this.datesArray.length - 1; - } - upDate = this.datesArray[upIndex]; - this.setFocusDate(upDate); -}; -DatePicker.prototype.setFocusToPrevDay = function (dateCell) { - var prevDate = false; - prevIndex = this.datesArray.indexOf(dateCell.domNode) - 1; - if (prevIndex < 0) { - this.moveToPrevMonth(); - prevIndex = this.datesArray.length - 1; + if (flag) { + event.stopPropagation(); + event.preventDefault(); } - prevDate = this.datesArray[prevIndex]; - this.setFocusDate(prevDate); }; -DatePicker.prototype.updateDates = function () { +DatePicker.prototype.moveToNextYear = function () { + this.year++; + this.updateGrid(); +}; - this.lastMonthDates = this.dates; - this.datesInMonth[1] = (((this.year % 4 === 0) && (this.year % 100 !== 0) && (this.year % 400 === 0)) ? 29 : 28); - this.month = months[this.monthIndex]; // show the string of the month - this.dates = this.datesInMonth[this.monthIndex]; // show the number of dates in that month - this.datesArray = []; - this.datesArrayDOM = []; - this.updateCalendar(this.month, this.year); +DatePicker.prototype.moveToPreviousYear = function () { + this.year--; + this.updateGrid(); }; -DatePicker.prototype.updateCalendar = function (month, year) { - this.MonthYearNode.innerHTML = month + ' ' + year; - var firstDateOfMonth = new Date(this.year, this.monthIndex, 1); - var startDay = firstDateOfMonth.getDay(); - - this.tbodyNode.innerHTML = ''; - for (var i = 0; i < 6;i++) { - var row = this.tbodyNode.insertRow(i); - row.classList.add('dateRow'); +DatePicker.prototype.moveToPreviousMonth = function () { + this.month--; + if (this.month < 0) { + this.month = 11; + this.year--; } + this.updateGrid(); +}; - var tableRow = document.getElementsByClassName('dateRow'); - for (var i = 0; i < tableRow.length;i++) { - for (var j = 0;j < 7; j++) { - var cell = document.createElement('td'); - var cellButton = document.createElement('button'); - cell.appendChild(cellButton); - tableRow[i].appendChild(cell); - cellButton.classList.add('dateCell'); - cell.classList.add('date'); - } +DatePicker.prototype.moveToNextMonth = function () { + this.month++; + if (this.month > 11) { + this.month = 0; + this.year++; } - var dateCells = document.querySelectorAll('.dateCell'); - var cells = document.querySelectorAll('.date'); + this.updateGrid(); +}; +DatePicker.prototype.moveFocusToNextDay = function () { - for (var i = startDay - 1; i >= 0; i--) { - dateCells[i].innerHTML = this.lastMonthDates; - dateCells[i].setAttribute('value', '0'); - this.lastMonthDates--; + this.day++; + if (this.daysInCurrentMonth <= this.day) { + this.day = 0; + this.moveToNextMonth(); } + this.setFocusDay(); +}; - for (var i = 1;i <= this.dates;i++) { - dateCells[startDay].innerHTML = i; - dateCells[startDay].setAttribute('value', i); - startDay++; - } +DatePicker.prototype.moveFocusToNextWeek = function () { - if (this.tbodyNode.rows[tableRow.length - 1].cells[0].querySelector('button').innerHTML === '') { - this.tbodyNode.rows[tableRow.length - 1].innerHTML = ''; - this.tbodyNode.rows[tableRow.length - 1].style.height = '40px'; - } - var j = 1; - for (var i = startDay; i < dateCells.length; i++) { - dateCells[i].innerHTML = j; - dateCells[i].setAttribute('value', '0'); - j++; + this.day += 7; + if (this.daysInCurrentMonth <= this.day) { + this.day = this.day - this.daysInCurrentMonth; + this.moveToNextMonth(); } + this.setFocusDay(); +}; - - for (var i = 0;i < dateCells.length;i++) { - if (dateCells[i].getAttribute('value') === '0') { - dateCells[i].disabled = true; - dateCells[i].setAttribute('tabIndex', '1'); - cells[i].classList.add('disabled'); - } - else { - this.datesArray.push(dateCells[i]); - } +DatePicker.prototype.moveFocusToPreviousDay = function () { + this.day--; + if (this.day < 0) { + this.moveToPreviousMonth(); + this.day = this.daysInCurrentMonth-1; } + this.setFocusDay(); +}; - if (this.headerButtonClicked) { // if the calendar toggled to previous month/year - for (var i = 0;i < this.datesArray.length;i++) { - var dc = new DatePickerDay(this.datesArray[i], this); - dc.init(); - this.datesArrayDOM.push(dc); - } +DatePicker.prototype.moveFocusToPreviousWeek = function () { + this.day -= 7; + if (this.day < 0) { + this.day = this.daysInLastMonth + this.day; + this.moveToPreviousMonth(); } - this.headerButtonClicked = false; - return true; + this.setFocusDay(); }; // Initialize date picker diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js index 715f855fbc..c79b768b37 100644 --- a/examples/datepicker/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -1,4 +1,12 @@ -var DatePickerDay = function (domNode,datepicker) { +var DatePickerDay = function (domNode, datepicker, index, row, column) { + + this.index = index; + this.row = row; + this.column = column; + + this.year = -1; + this.month = -1; + this.day = -1; this.domNode = domNode; this.datepicker = datepicker; @@ -20,20 +28,40 @@ var DatePickerDay = function (domNode,datepicker) { }; DatePickerDay.prototype.init = function () { - this.domNode.tabIndex = -1; + this.domNode.setAttribute('tabindex', '-1'); this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.domNode.innerHTML = '-1'; + +}; + +DatePickerDay.prototype.isDisabled = function () { + return this.domNode.disabled; +}; + +DatePickerDay.prototype.updateDay = function (disable, year, month, day) { + + this.domNode.disabled = disable; + + this.year = year; + this.month = month; + this.day = day; + + this.domNode.innerHTML = day + 1; + this.domNode.setAttribute('tabindex', '-1'); + this.domNode.removeAttribute('aria-selected'); + }; DatePickerDay.prototype.handleKeyDown = function (event) { - var tgt = event.currentTarget, - char = event.key, - flag = false, - clickEvent; - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } + var flag = false + var onFirstRow = this.row === 0; + onFirstRow = onFirstRow || ((this.row === 1) && (this.datepicker.days[this.index-7].isDisabled())); + var onLastRow = this.row === 5; + onLastRow = onLastRow || (this.row === 3) && (this.datepicker.days[this.index+7].isDisabled()); + onLastRow = onLastRow || (this.row === 4) && (this.datepicker.days[this.index+7].isDisabled()); + switch (event.keyCode) { case this.keyCode.ESC: @@ -41,59 +69,50 @@ DatePickerDay.prototype.handleKeyDown = function (event) { break; case this.keyCode.TAB: - this.datepicker.dialogButton[0].focus(); + this.datepicker.cancelButtonNode.focus(); if (event.shiftKey) { this.datepicker.nextYearNode.focus(); } flag = true; break; + case this.keyCode.RETURN: case this.keyCode.SPACE: this.datepicker.setSelectDate(this); flag = true; break; + case this.keyCode.RIGHT: - this.datepicker.setFocusToNextDay(this); + this.datepicker.moveFocusToNextDay(); flag = true; break; + case this.keyCode.LEFT: - this.datepicker.setFocusToPrevDay(this); + this.datepicker.moveFocusToPreviousDay(); flag = true; break; - case this.keyCode.UP: - this.datepicker.setFocusToPrevWeek(this); + + case this.keyCode.DOWN: + this.datepicker.moveFocusToNextWeek(); flag = true; break; - case this.keyCode.DOWN: - this.datepicker.setFocusToNextWeek(this); + + case this.keyCode.UP: + this.datepicker.moveFocusToPreviousWeek(); flag = true; break; - case this.keyCode.PAGEUP: - var row = document.getElementsByClassName('dateRow'); - var cell = this.datepicker.setUpForNewMonthYear(row, this); - if (event.shiftKey) { - this.datepicker.moveToPrevYear(this); - } - else { - this.datepicker.moveToPrevMonth(this); - } - console.log(cell); - var newRow = document.getElementsByClassName('dateRow'); - this.datepicker.setFocusToNewMonthYear(newRow, cell); - console.log(cell); + case this.keyCode.PAGEUP: + this.datepicker.moveToPreviousMonth(); + this.datepicker.adjustCurrentDay(this, onFirstRow, onLastRow); + this.datepicker.setFocusDay(); flag = true; break; + case this.keyCode.PAGEDOWN: - var row = document.getElementsByClassName('dateRow'); - var cell = this.datepicker.setUpForNewMonthYear(row, this); - if (event.shiftKey) { - this.datepicker.moveToNextYear(this); - } - else { - this.datepicker.moveToNextMonth(this); - } - this.datepicker.setFocusToNewMonthYear(row, cell); + this.datepicker.moveToNextMonth(); + this.datepicker.adjustCurrentDay(this, onFirstRow, onLastRow); + this.datepicker.setFocusDay(); flag = true; break; } @@ -104,8 +123,12 @@ DatePickerDay.prototype.handleKeyDown = function (event) { } }; + DatePickerDay.prototype.handleClick = function (event) { - this.datepicker.setSelectDate(this); + this.datepicker.day = this.day; + + this.datepicker.setFocusDay(); + event.stopPropagation(); event.preventDefault(); From 7b076d6320f6bf97b207b338acbb183d5d9a2462 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 14 Dec 2018 10:29:38 -0600 Subject: [PATCH 010/137] updated code --- examples/datepicker/css/datepicker.css | 4 +- examples/datepicker/datepicker.html | 32 +++--- examples/datepicker/js/datepicker.js | 129 +++++++++++++++++------- examples/datepicker/js/datepickerDay.js | 31 +++--- 4 files changed, 128 insertions(+), 68 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index e6c7fa0da3..38b5eff802 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -8,8 +8,8 @@ background-color:white; } -label{ - display:block; +.datepicker .date label { + display:inline; } .datepicker input { diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index dd4e6d0afe..7a50e52c91 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -52,21 +52,23 @@

    Example

    aria-owns="id-datepicker-1" aria-label="Date"> - - - - - +
    + + + + + +
    Date: Fri, 14 Dec 2018 10:47:00 -0600 Subject: [PATCH 011/137] fixed problem with document clicks when date picker is open --- examples/datepicker/js/datepicker.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index fe0660cd86..19bab51480 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -3,7 +3,6 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - this.body = document; this.comboboxNode = comboboxNode; this.inputNode = inputNode; this.buttonNode = buttonNode; @@ -59,7 +58,6 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { DatePicker.prototype.init = function () { - this.body.addEventListener('click', this.handleBodyClick.bind(this)); var di = new DateInput(this.inputNode, this.buttonNode, this); di.init(); @@ -321,6 +319,8 @@ DatePicker.prototype.getDaysInMonth = function (year, month) { DatePicker.prototype.open = function () { + document.addEventListener('click', this.handleDocumentClick.bind(this), true); + this.dialogNode.style.display = 'block'; this.comboboxNode.setAttribute('aria-expanded', 'true'); this.getTextboxDate(); @@ -328,15 +328,19 @@ DatePicker.prototype.open = function () { }; DatePicker.prototype.close = function (node) { + document.removeEventListener('click', this.handleDocumentClick.bind(this), true); + this.dialogNode.style.display = 'none'; this.comboboxNode.setAttribute('aria-expanded','false'); this.buttonNode.focus(); }; -DatePicker.prototype.handleBodyClick = function () { - if (this.inputNode.hasAttribute('aria-expanded')) { - +DatePicker.prototype.handleDocumentClick = function (event) { + if (!this.dialogNode.contains(event.target)) { + this.setFocusDay(); + event.stopPropagation(); + event.preventDefault(); } }; From a15d38f76ecdd06fa8b5c19809d404513ee1288c Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 14 Dec 2018 10:58:31 -0600 Subject: [PATCH 012/137] fixed problem with document clicks when date picker is open --- examples/datepicker/js/datepicker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 19bab51480..aa0ebf28de 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -37,6 +37,8 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { this.currentDay = null; + this.handleDocumentClick; + this.keyCode = Object.freeze({ 'TAB': 9, @@ -319,7 +321,8 @@ DatePicker.prototype.getDaysInMonth = function (year, month) { DatePicker.prototype.open = function () { - document.addEventListener('click', this.handleDocumentClick.bind(this), true); + this.handleDocumentClick = this.handleDocumentClick.bind(this); + document.addEventListener('click', this.handleDocumentClick, true); this.dialogNode.style.display = 'block'; this.comboboxNode.setAttribute('aria-expanded', 'true'); @@ -328,7 +331,7 @@ DatePicker.prototype.open = function () { }; DatePicker.prototype.close = function (node) { - document.removeEventListener('click', this.handleDocumentClick.bind(this), true); + document.removeEventListener('click', this.handleDocumentClick, true); this.dialogNode.style.display = 'none'; this.comboboxNode.setAttribute('aria-expanded','false'); @@ -379,6 +382,7 @@ DatePicker.prototype.handleOkButton = function (event) { break; case 'click': + this.setTextboxDate(); this.close(); flag = true; break; From cc61f5855de6ed33c643ab7594a5a31dd836eafd Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 14 Dec 2018 11:04:41 -0600 Subject: [PATCH 013/137] updated month year title --- examples/datepicker/css/datepicker.css | 5 +++++ examples/datepicker/js/datepicker.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index 38b5eff802..a70ffca7a3 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -100,6 +100,11 @@ color:hsl(216, 89%, 72%); } +.datepicker .monthYear { + display: inline-block; + width: 12em; + text-align: center; +} table.dates { width:100%; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index aa0ebf28de..39fcee0a1c 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -1,7 +1,7 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { - var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; this.comboboxNode = comboboxNode; this.inputNode = inputNode; @@ -113,7 +113,7 @@ DatePicker.prototype.updateGrid = function (year, month) { if (typeof year !== 'number') year = this.year; if (typeof month !== 'number') month = this.month; - this.MonthYearNode.innerHTML = month + ' ' + year; + this.MonthYearNode.innerHTML = this.months[month] + ' ' + year; this.daysInCurrentMonth = this.getDaysInMonth(year, month); From 78f4cd8822adf00676933dd05b3241fe83f4620d Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 14 Dec 2018 12:02:25 -0600 Subject: [PATCH 014/137] improved source code documentation --- examples/datepicker/datepicker.html | 56 ++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 7a50e52c91..e5792fc07f 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -92,7 +92,7 @@

    Example

    - + December 2018 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e48ffb62d164343185a042a30102a68147de7578 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 09:32:51 -0600 Subject: [PATCH 015/137] focus returns to textbox and added Home and End key commands for first and last day of the week --- examples/datepicker/datepicker.html | 14 ++++++++++--- examples/datepicker/js/datepicker.js | 27 ++++++++++++++++++++++++- examples/datepicker/js/datepickerDay.js | 11 ++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index e5792fc07f..f7b1724f6d 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -255,7 +255,7 @@

    Dialog (Date Picker)

    ESC - Close the dialog and move focus back to the calendar button. + Close the dialog and move focus back to the date textbox. TAB @@ -313,7 +313,7 @@

    Grid (Days in Month)

    Space,
    Return
      -
    • Select the date, close the dialog box and move focus to the calendar button.
    • +
    • Select the date, close the dialog box and move focus to the date textbox.
    • Focus is moved to calendar button for compatibility with mobile browsers.
    @@ -334,6 +334,14 @@

    Grid (Days in Month)

    Left Arrow Move the focus to the previous day. + + Home + Move the focus to the first day (e.g Sunday) of the current week. + + + End + Move the focus to the last day (e.g. Saturday) of the current week. + PageUp @@ -505,7 +513,7 @@

    Button (Open Date Picker)

    button
      -
    • The button provides a means to return focus from the dialog box for mobile browsers.
    • +
    • The button provides a means to open the calendar dialog box using the mouse or touch.
    • Accessible name comes from aria-label attribute.
    • Button is removed from tab order of the page, since the dialog can be opened from the textbox with the keyboard.
    diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 39fcee0a1c..096544b077 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -335,7 +335,7 @@ DatePicker.prototype.close = function (node) { this.dialogNode.style.display = 'none'; this.comboboxNode.setAttribute('aria-expanded','false'); - this.buttonNode.focus(); + this.inputNode.focus(); }; @@ -666,6 +666,7 @@ DatePicker.prototype.moveFocusToNextWeek = function () { }; DatePicker.prototype.moveFocusToPreviousDay = function () { + this.day--; if (this.day < 0) { this.moveToPreviousMonth(); @@ -683,6 +684,30 @@ DatePicker.prototype.moveFocusToPreviousWeek = function () { this.setFocusDay(); }; +DatePicker.prototype.moveFocusToFirstDayOfWeek = function () { + + this.day = this.day - this.currentDay.column; + + if (this.day < 0) { + this.day = this.daysInLastMonth + this.day; + this.moveToPreviousMonth(); + } + this.setFocusDay(); + +}; + +DatePicker.prototype.moveFocusToLastDayOfWeek = function () { + + this.day = this.day + ( 6 - this.currentDay.column); + + if (this.daysInCurrentMonth <= this.day) { + this.day = this.day - this.daysInCurrentMonth; + this.moveToNextMonth(); + } + this.setFocusDay(); + +}; + DatePicker.prototype.setTextboxDate = function () { this.inputNode.value = (this.month + 1) + '/' + (this.day + 1) + '/' + this.year; }; diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js index 47fb07a550..236c3fffae 100644 --- a/examples/datepicker/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -123,6 +123,17 @@ DatePickerDay.prototype.handleKeyDown = function (event) { this.datepicker.setFocusDay(); flag = true; break; + + case this.keyCode.HOME: + this.datepicker.moveFocusToFirstDayOfWeek(); + flag = true; + break; + + case this.keyCode.END: + this.datepicker.moveFocusToLastDayOfWeek(); + flag = true; + break; + } if (flag) { From f0b9f044613259c810c7e1d38e0c61c406903fff Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 09:37:28 -0600 Subject: [PATCH 016/137] edited description of example --- examples/datepicker/datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index f7b1724f6d..7e4744eaa0 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -37,7 +37,7 @@

    Date Picker Example Using ARIA 1.1 Combobox

    The date picker is an example of the combobox design pattern that opens a dialog box. The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the calendar button. - The date picker dialog uses a grid pattern to show and select a date, and buttons for changing the month and year shown in the grid. For compatibility with mobile browsers, keyboard focus is moved to the calender button when the date picker dialog box closes, rather than the text input control. In some mobile browsers moving focus to the input control causes the date picker dialog to open automatically, which is avoided by moving focus to the calendar button. The accessible name for the calender button and the value of the textbox value are updated when the date picker dialog closes. + The date picker dialog uses a grid pattern to show and select a date using the cursor keys. Additional buttons in the dialog box can be used for changing the month and year shown in the grid.

    Example

    From 6445f29ff340886ccd73491f9ea7246529692fb2 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 16:32:06 -0600 Subject: [PATCH 017/137] updated documentation --- examples/datepicker/datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 7e4744eaa0..52198630a1 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -235,7 +235,7 @@

    Button (Open Date Picker)

    • Toggles calendar dialog.
    • -
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, for compatibility with mobile browsers.
    • +
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the textbox (e.g. down arrow key).
    From 88de7de4d115967d2e01a66a2b5df775d0f96d1f Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 16:41:04 -0600 Subject: [PATCH 018/137] updated documentation --- examples/datepicker/datepicker.html | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 52198630a1..e6194b4908 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -235,7 +235,7 @@

    Button (Open Date Picker)

    • Toggles calendar dialog.
    • -
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the textbox (e.g. down arrow key).
    • +
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    @@ -255,7 +255,7 @@

    Dialog (Date Picker)

    ESC - Close the dialog and move focus back to the date textbox. + Close the dialog and move focus back to the date input. TAB @@ -310,13 +310,13 @@

    Grid (Days in Month)

    - Space,
    Return - -
      -
    • Select the date, close the dialog box and move focus to the date textbox.
    • -
    • Focus is moved to calendar button for compatibility with mobile browsers.
    • -
    - + Space,
    Return + +
      +
    • Select the date, close the dialog box and move focus to the date input.
    • +
    • The value of the date input is updated with the selected date.
    • +
    + Up Arrow @@ -390,9 +390,10 @@

    Grid (Days in Month)

    ESC
      -
    • Close the dialog and move focus back to calendar button.
    • -
    • Focus is move to calendar button for compatibility with mobile browsers.
    • -
    +
  • Close the dialog and move focus back to date input.
  • +
  • The value of the date texbox is not updated.
  • + + @@ -412,8 +413,8 @@

    Buttons (OK and Cancel)

    Space,
    Return
      -
    • The Cancel button closes the calendar dialog, moves focus to calendar button, does not update date in date input.
    • -
    • The OK button closes the calendar dialog, moves focus to calendar button, does update date in date input.
    • +
    • The Cancel button closes the calendar dialog, moves focus to date input, does not update date in date input.
    • +
    • The OK button closes the calendar dialog, moves focus to date input, does update date in date input.
    @@ -515,7 +516,7 @@

    Button (Open Date Picker)

    • The button provides a means to open the calendar dialog box using the mouse or touch.
    • Accessible name comes from aria-label attribute.
    • -
    • Button is removed from tab order of the page, since the dialog can be opened from the textbox with the keyboard.
    • +
    • Button is removed from tab order of the page, since the dialog can be opened from the date input with the keyboard.
    @@ -771,8 +772,8 @@

    Grid (Days in Month)

    td > button
      -
    • Identifies the currently selected date in the textbox.
    • -
    • The aria-selected attribute is only set on the button representing the date in the textbox, all other buttons do not have an aria-selected attribute.
    • +
    • Identifies the currently selected date in the date input.
    • +
    • The aria-selected attribute is only set on the button representing the date in the date input, all other buttons do not have an aria-selected attribute.
    From df65352b46dbc921234cc2870cdf1c0f6b8d0046 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 16:42:10 -0600 Subject: [PATCH 019/137] updated documentation --- examples/datepicker/datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index e6194b4908..370986f7f5 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -314,7 +314,7 @@

    Grid (Days in Month)

    • Select the date, close the dialog box and move focus to the date input.
    • -
    • The value of the date input is updated with the selected date.
    • +
    • The value of the date input is updated with the selected date in the calendar grid.
    From c72fed077944ec333e07db4863bc306bce5d8335 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 17:15:00 -0600 Subject: [PATCH 020/137] fixed some bugs --- examples/datepicker/js/dateInput.js | 9 ++++++++- examples/datepicker/js/datepicker.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/js/dateInput.js b/examples/datepicker/js/dateInput.js index e605deb7dc..24599469de 100644 --- a/examples/datepicker/js/dateInput.js +++ b/examples/datepicker/js/dateInput.js @@ -1,4 +1,4 @@ -var DateInput = function (domNode,buttonNode, datepicker) { +var DateInput = function (domNode, buttonNode, datepicker) { this.domNode = domNode; this.buttonNode = buttonNode; this.datepicker = datepicker; @@ -33,14 +33,21 @@ DateInput.prototype.handleKeyDown = function (event) { function isPrintableCharacter (str) { return str.length === 1 && str.match(/\S/); } + switch (event.keyCode) { + case this.keyCode.DOWN: this.datepicker.open(); flag = true; break; + case this.keyCode.ESC: this.datepicker.close(); + + default: + break; } + if (flag) { event.stopPropagation(); event.preventDefault(); diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 096544b077..a124027020 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -26,7 +26,7 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { this.year = date.getFullYear(); this.month = date.getMonth(); - this.day = date.getDate() - 1; + this.day = date.getDate() - 1; this.daysInCurrentMonth = this.getDaysInMonth(); this.daysInLastMonth = this.getDaysInLastMonth(); From 1c291caedca480e543d0a8a8458788662c6906f0 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 17:26:26 -0600 Subject: [PATCH 021/137] fixed some bugs --- examples/datepicker/js/datepicker.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index a124027020..1046e94266 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -326,7 +326,11 @@ DatePicker.prototype.open = function () { this.dialogNode.style.display = 'block'; this.comboboxNode.setAttribute('aria-expanded', 'true'); - this.getTextboxDate(); + this.getDateInput(); + console.log(" Year: " + this.year); + console.log("Month: " + this.month); + console.log(" Day: " + this.day); + this.updateGrid(); this.setFocusDay(); }; @@ -712,14 +716,14 @@ DatePicker.prototype.setTextboxDate = function () { this.inputNode.value = (this.month + 1) + '/' + (this.day + 1) + '/' + this.year; }; -DatePicker.prototype.getTextboxDate = function () { +DatePicker.prototype.getDateInput = function () { var parts = this.inputNode.value.split('/'); if ((parts.length === 3) && - !isNaN(parts[0]) && - !isNaN(parts[1]) && - !isNaN(parts[2])) { + Number.isInteger(parseInt(parts[0])) && + Number.isInteger(parseInt(parts[1])) && + Number.isInteger(parseInt(parts[2]))) { this.month = parseInt(parts[0])-1; this.day = parseInt(parts[1])-1; this.year = parseInt(parts[2]); @@ -733,7 +737,6 @@ DatePicker.prototype.getTextboxDate = function () { this.day = date.getDate()-1; } - this.daysInCurrentMonth = this.getDaysInMonth(); this.daysInLastMonth = this.getDaysInLastMonth(); From eef22a499353406cfab932870b640430f5145a14 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 5 Feb 2019 17:27:18 -0600 Subject: [PATCH 022/137] removed some debugging code --- examples/datepicker/js/datepicker.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 1046e94266..e651492523 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -327,9 +327,6 @@ DatePicker.prototype.open = function () { this.dialogNode.style.display = 'block'; this.comboboxNode.setAttribute('aria-expanded', 'true'); this.getDateInput(); - console.log(" Year: " + this.year); - console.log("Month: " + this.month); - console.log(" Day: " + this.day); this.updateGrid(); this.setFocusDay(); }; From 4c083c2dad81b2ae602c1e3abf49ad05c066225e Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 7 Feb 2019 13:06:34 -0600 Subject: [PATCH 023/137] validated js and css code --- examples/datepicker/css/datepicker.css | 135 ++++++++++++------------ examples/datepicker/js/dateInput.js | 3 +- examples/datepicker/js/datepicker.js | 138 +++++++++++++------------ package-lock.json | 41 ++------ 4 files changed, 151 insertions(+), 166 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index a70ffca7a3..a20de8cd74 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -1,64 +1,63 @@ -.datepicker{ - margin-top:1em; +.datepicker { + margin-top: 1em; } .datepicker button.icon { - border-style:none; - text-align:left; - background-color:white; + border-style: none; + text-align: left; + background-color: white; } .datepicker .date label { - display:inline; + display: inline; } .datepicker input { - margin-top:1em; - width:20%; + margin-top: 1em; + width: 20%; } .datepickerDialog { - width:45%; - clear:both; - display:none; - border:3px solid hsl(216, 80%, 55%); - margin-top:1em; - border-radius:5px; - padding:0; + width: 45%; + clear: both; + display: none; + border: 3px solid hsl(216, 80%, 55%); + margin-top: 1em; + border-radius: 5px; + padding: 0; } -.datepickerDialog button::-moz-focus-inner { - border:0; -} - - .header { - cursor:default; - background-color:hsl(216, 80%, 55%); - padding:7px; - font-weight:bold; - text-transform:uppercase; + cursor: default; + background-color: hsl(216, 80%, 55%); + padding: 7px; + font-weight: bold; + text-transform: uppercase; color: white; display: flex; justify-content: space-around; } +.header span { + display: inline-block; +} + .header button { - border-style:none; - background:transparent; + border-style: none; + background: transparent; } -.header span { - display:inline-block; +.datepickerDialog button::-moz-focus-inner { + border: 0; } .prevYear, .prevMonth, .nextMonth, .nextYear { - padding:4px; - width:24px; - height:24px; + padding: 4px; + width: 24px; + height: 24px; color: white; } @@ -73,31 +72,31 @@ } .dialogButtonGroup { - text-align:right; - margin-top:1em; - margin-bottom:1em; - margin-right:1em; + text-align: right; + margin-top: 1em; + margin-bottom: 1em; + margin-right: 1em; } .dialogButton { - padding:5px; - margin-left:1em; - width:5em; + padding: 5px; + margin-left: 1em; + width: 5em; background-color: hsl(216, 80%, 92%); - font-size:0.85em; - color:black; + font-size: 0.85em; + color: black; outline: none; border: 1px solid hsl(216, 80%, 92%); - border-radius:5px; + border-radius: 5px; } -.dialogButton:focus { +.dialogButton:focus { padding: 4px; border: 2px solid black; } .fa-calendar-alt { - color:hsl(216, 89%, 72%); + color: hsl(216, 89%, 72%); } .datepicker .monthYear { @@ -107,52 +106,52 @@ } table.dates { - width:100%; - padding-left:1em; - padding-right:1em; - padding-top:1em; + width: 100%; + padding-left: 1em; + padding-right: 1em; + padding-top: 1em; } table.dates th, -table.dates td{ - text-align:center; +table.dates td { + text-align: center; } .dateRow { - border:1px solid black; + border: 1px solid black; } .dateCell { - outline:0; - border:0; - padding:0; - margin:0; + outline: 0; + border: 0; + padding: 0; + margin: 0; height: 40px; - width:40px; + width: 40px; } .dateButton { - padding:0; - margin:0; + padding: 0; + margin: 0; line-height: inherit; - height:100%; + height: 100%; width: 100%; - border: 1px solid #EEE; - border-radius:5px; + border: 1px solid #eee; + border-radius: 5px; font-size: 15px; - background: #EEE; + background: #eee; } .dateButton:focus, .dateButton:hover { - padding:0; - background-color:hsl(216, 80%, 92%); + padding: 0; + background-color: hsl(216, 80%, 92%); } .dateButton:focus { border-width: 2px; border-color: rgb(100, 100, 100); - outline:0; + outline: 0; } .dateButton[aria-selected] { @@ -160,7 +159,7 @@ table.dates td{ } .dateButton[tabindex="0"] { - background-color:hsl(216, 80%, 92%); + background-color: hsl(216, 80%, 92%); } .disabled { @@ -170,9 +169,7 @@ table.dates td{ .dateButton:disabled { color: #777; - background-color: #FFF; + background-color: #fff; border: none; cursor: not-allowed; } - - diff --git a/examples/datepicker/js/dateInput.js b/examples/datepicker/js/dateInput.js index 24599469de..89b5ca72a1 100644 --- a/examples/datepicker/js/dateInput.js +++ b/examples/datepicker/js/dateInput.js @@ -25,7 +25,6 @@ DateInput.prototype.init = function () { this.buttonNode.addEventListener('keydown', this.handleButtonKeyDown.bind(this)); }; - DateInput.prototype.handleKeyDown = function (event) { var tgt = event.currentTarget, char = event.key, @@ -45,7 +44,7 @@ DateInput.prototype.handleKeyDown = function (event) { this.datepicker.close(); default: - break; + break; } if (flag) { diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index e651492523..409ed9638d 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -1,7 +1,7 @@ -var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { - this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; +var DatePicker = function (comboboxNode, inputNode, buttonNode, dialogNode) { + this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; this.comboboxNode = comboboxNode; this.inputNode = inputNode; @@ -24,9 +24,9 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { var date = new Date(); - this.year = date.getFullYear(); + this.year = date.getFullYear(); this.month = date.getMonth(); - this.day = date.getDate() - 1; + this.day = date.getDate() - 1; this.daysInCurrentMonth = this.getDaysInMonth(); this.daysInLastMonth = this.getDaysInLastMonth(); @@ -39,7 +39,6 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { this.handleDocumentClick; - this.keyCode = Object.freeze({ 'TAB': 9, 'RETURN': 13, @@ -57,10 +56,8 @@ var DatePicker = function (comboboxNode, inputNode,buttonNode,dialogNode) { }; - DatePicker.prototype.init = function () { - var di = new DateInput(this.inputNode, this.buttonNode, this); di.init(); @@ -70,13 +67,13 @@ DatePicker.prototype.init = function () { this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); - this.prevMonthNode.addEventListener('click',this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('click',this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('click',this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('click',this.handleNextYearButton.bind(this)); + this.prevMonthNode.addEventListener('click', this.handlePreviousMonthButton.bind(this)); + this.nextMonthNode.addEventListener('click', this.handleNextMonthButton.bind(this)); + this.prevYearNode.addEventListener('click', this.handlePreviousYearButton.bind(this)); + this.nextYearNode.addEventListener('click', this.handleNextYearButton.bind(this)); - this.prevMonthNode.addEventListener('keydown',this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('keydown',this.handleNextMonthButton.bind(this)); + this.prevMonthNode.addEventListener('keydown', this.handlePreviousMonthButton.bind(this)); + this.nextMonthNode.addEventListener('keydown', this.handleNextMonthButton.bind(this)); this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); @@ -110,18 +107,23 @@ DatePicker.prototype.updateGrid = function (year, month) { var i; - if (typeof year !== 'number') year = this.year; - if (typeof month !== 'number') month = this.month; + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } this.MonthYearNode.innerHTML = this.months[month] + ' ' + year; this.daysInCurrentMonth = this.getDaysInMonth(year, month); - var lastMonth = month -1; + var lastMonth = month - 1; var lastYear = year; if (lastMonth < 0) { lastMonth = 11; - lastYear = year-1; + lastYear = year - 1; } var daysInLastMonth = this.getDaysInMonth(lastYear, lastMonth); @@ -131,7 +133,7 @@ DatePicker.prototype.updateGrid = function (year, month) { var nextYear = year; if (nextMonth > 11) { nextMonth = 1; - nextYear = year+1; + nextYear = year + 1; } var firstDayOfMonth = new Date(year, month, 1); @@ -143,7 +145,7 @@ DatePicker.prototype.updateGrid = function (year, month) { } for (var i = 0; i < this.daysInCurrentMonth; i++) { - var dpDay = this.days[dayOfWeek+i]; + var dpDay = this.days[dayOfWeek + i]; dpDay.updateDay(false, year, month, i); if ((this.selectedDay.getFullYear() === year) && (this.selectedDay.getMonth() === month) && @@ -163,7 +165,7 @@ DatePicker.prototype.updateGrid = function (year, month) { } for (var i = 0; i < remainingButtons; i++) { - this.days[dayOfWeek+this.daysInCurrentMonth+i].updateDay(true, nextYear, nextMonth, i); + this.days[dayOfWeek + this.daysInCurrentMonth + i].updateDay(true, nextYear, nextMonth, i); } }; @@ -171,16 +173,15 @@ DatePicker.prototype.updateGrid = function (year, month) { DatePicker.prototype.onFirstRow = function () { var cd = this.currentDay; var flag = cd.row === 0; - flag = flag || ((cd.row === 1) && this.days[cd.index-7].isDisabled()); + flag = flag || ((cd.row === 1) && this.days[cd.index - 7].isDisabled()); return flag; }; - DatePicker.prototype.onLastRow = function () { var cd = this.currentDay; var flag = cd.row === 5; - flag = flag || ((cd.row === 3) && this.days[cd.index+7].isDisabled()); - flag = flag || ((cd.row === 4) && this.days[cd.index+7].isDisabled()); + flag = flag || ((cd.row === 3) && this.days[cd.index + 7].isDisabled()); + flag = flag || ((cd.row === 4) && this.days[cd.index + 7].isDisabled()); return flag; }; @@ -198,25 +199,27 @@ DatePicker.prototype.adjustCurrentDay = function (onFirstRow, onLastRow) { } if (cd.isDisabled()) { - if (cd.row === 0 ) { - this.day = this.days[cd.index+7].day; + if (cd.row === 0) { + this.day = this.days[cd.index + 7].day; } else { - if (this.days[cd.index-7].isDisabled()) { - this.day = this.days[cd.index-14].day; + if (this.days[cd.index - 7].isDisabled()) { + this.day = this.days[cd.index - 14].day; } else { - this.day = this.days[cd.index-7].day; + this.day = this.days[cd.index - 7].day; } } } else { - if (onFirstRow && (cd.row === 1) && (!this.days[cd.index-7].isDisabled())) { - this.day = this.days[cd.index-7].day; + if (onFirstRow && (cd.row === 1) && (!this.days[cd.index - 7].isDisabled())) { + this.day = this.days[cd.index - 7].day; } else { - if (onLastRow && ((cd.row === 3)|| (cd.row === 4)) && (!this.days[cd.index+7].isDisabled())) { - this.day = this.days[cd.index+7].day; + if (onLastRow && + ((cd.row === 3) || (cd.row === 4)) && + (!this.days[cd.index + 7].isDisabled())) { + this.day = this.days[cd.index + 7].day; } else { this.day = cd.day; @@ -233,7 +236,6 @@ DatePicker.prototype.showLastRow = function () { this.lastRowNode.style.visibility = 'visible'; }; - DatePicker.prototype.setFocusDay = function (flag) { if (typeof flag !== 'boolean') { @@ -242,14 +244,14 @@ DatePicker.prototype.setFocusDay = function (flag) { var day = this.day; var month = this.month; - var dp = this; + var currentDay = this.currentDay; - this.days.forEach(function(d) { + this.days.forEach(function (d) { if ((d.day == day) && (d.month == month)) { d.domNode.setAttribute('tabindex', '0'); - dp.currentDay = d; - if (flag ) { + currentDay = d; + if (flag) { d.domNode.focus(); } } @@ -268,14 +270,19 @@ DatePicker.prototype.updateDate = function (year, month, day) { DatePicker.prototype.getDaysInLastMonth = function (year, month) { - if (typeof year !== 'number') year = this.year; - if (typeof month !== 'number') month = this.month; + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } - var lastMonth = month -1; + var lastMonth = month - 1; var lastYear = year; if (lastMonth < 0) { lastMonth = 11; - lastYear = year-1; + lastYear = year - 1; } return this.getDaysInMonth(lastYear, lastMonth); @@ -284,10 +291,15 @@ DatePicker.prototype.getDaysInLastMonth = function (year, month) { DatePicker.prototype.getDaysInMonth = function (year, month) { - if (typeof year !== 'number') year = this.year; - if (typeof month !== 'number') month = this.month; + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } - switch(month) { + switch (month) { case 0: case 2: @@ -319,7 +331,6 @@ DatePicker.prototype.getDaysInMonth = function (year, month) { }; - DatePicker.prototype.open = function () { this.handleDocumentClick = this.handleDocumentClick.bind(this); document.addEventListener('click', this.handleDocumentClick, true); @@ -335,11 +346,10 @@ DatePicker.prototype.close = function (node) { document.removeEventListener('click', this.handleDocumentClick, true); this.dialogNode.style.display = 'none'; - this.comboboxNode.setAttribute('aria-expanded','false'); + this.comboboxNode.setAttribute('aria-expanded', 'false'); this.inputNode.focus(); }; - DatePicker.prototype.handleDocumentClick = function (event) { if (!this.dialogNode.contains(event.target)) { this.setFocusDay(); @@ -351,7 +361,7 @@ DatePicker.prototype.handleDocumentClick = function (event) { DatePicker.prototype.handleOkButton = function (event) { var flag = false; - switch(event.type) { + switch (event.type) { case 'keydown': switch (event.keyCode) { @@ -401,7 +411,7 @@ DatePicker.prototype.handleOkButton = function (event) { DatePicker.prototype.handleCancelButton = function (event) { var flag = false; - switch(event.type) { + switch (event.type) { case 'keydown': switch (event.keyCode) { @@ -622,7 +632,6 @@ DatePicker.prototype.moveToNextYear = function () { this.updateGrid(); }; - DatePicker.prototype.moveToPreviousYear = function () { this.year--; this.updateGrid(); @@ -671,7 +680,7 @@ DatePicker.prototype.moveFocusToPreviousDay = function () { this.day--; if (this.day < 0) { this.moveToPreviousMonth(); - this.day = this.daysInCurrentMonth-1; + this.day = this.daysInCurrentMonth - 1; } this.setFocusDay(); }; @@ -699,7 +708,7 @@ DatePicker.prototype.moveFocusToFirstDayOfWeek = function () { DatePicker.prototype.moveFocusToLastDayOfWeek = function () { - this.day = this.day + ( 6 - this.currentDay.column); + this.day = this.day + (6 - this.currentDay.column); if (this.daysInCurrentMonth <= this.day) { this.day = this.day - this.daysInCurrentMonth; @@ -721,17 +730,17 @@ DatePicker.prototype.getDateInput = function () { Number.isInteger(parseInt(parts[0])) && Number.isInteger(parseInt(parts[1])) && Number.isInteger(parseInt(parts[2]))) { - this.month = parseInt(parts[0])-1; - this.day = parseInt(parts[1])-1; + this.month = parseInt(parts[0]) - 1; + this.day = parseInt(parts[1]) - 1; this.year = parseInt(parts[2]); } else { // If not a valid date (MM/DD/YY) initialize with todays date var date = new Date(); - this.year = date.getFullYear(); + this.year = date.getFullYear(); this.month = date.getMonth(); - this.day = date.getDate()-1; + this.day = date.getDate() - 1; } this.daysInCurrentMonth = this.getDaysInMonth(); @@ -747,14 +756,13 @@ window.addEventListener('load' , function () { var datePickers = document.querySelectorAll('.datepicker'); - datePickers.forEach( function (dp) { - var dpInput = dp.querySelector('input'); - var dpButton = dp.querySelector('button'); - var dpDialog = document.getElementById(dp.getAttribute('aria-owns')); - var datePicker = new DatePicker(dp, dpInput, dpButton, dpDialog); - datePicker.init(); - } - ); + datePickers.forEach(function (dp) { + var dpInput = dp.querySelector('input'); + var dpButton = dp.querySelector('button'); + var dpDialog = document.getElementById(dp.getAttribute('aria-owns')); + var datePicker = new DatePicker(dp, dpInput, dpButton, dpDialog); + datePicker.init(); + }); }); diff --git a/package-lock.json b/package-lock.json index 709a001870..c142c7673e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3210,8 +3210,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3232,14 +3231,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3254,20 +3251,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3384,8 +3378,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3397,7 +3390,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3412,7 +3404,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3420,14 +3411,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3446,7 +3435,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3527,8 +3515,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3540,7 +3527,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3626,8 +3612,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3663,7 +3648,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3683,7 +3667,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3727,14 +3710,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, From 0097ce7fd5e284766c5748a32f1925334824f512 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 7 Feb 2019 13:13:13 -0600 Subject: [PATCH 024/137] fixed bug --- examples/datepicker/js/datepicker.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 409ed9638d..4b383082b1 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -244,13 +244,13 @@ DatePicker.prototype.setFocusDay = function (flag) { var day = this.day; var month = this.month; - var currentDay = this.currentDay; + var cd = this.currentDay; this.days.forEach(function (d) { if ((d.day == day) && (d.month == month)) { d.domNode.setAttribute('tabindex', '0'); - currentDay = d; + cd = d; if (flag) { d.domNode.focus(); } @@ -260,6 +260,8 @@ DatePicker.prototype.setFocusDay = function (flag) { } }); + this.currentDay = cd; + }; DatePicker.prototype.updateDate = function (year, month, day) { From 5b43f501b7e95f8f4dd9dbb6fd251855e86a0f15 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 7 Feb 2019 16:57:46 -0600 Subject: [PATCH 025/137] added two regression tests --- examples/datepicker/datepicker.html | 15 ++++++++----- test/tests/daetpicker-1.js | 35 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 test/tests/daetpicker-1.js diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 370986f7f5..1f681cd658 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -45,11 +45,12 @@

    Example

    -
    -

    Button (Open Date Picker)

    From 8ce71fc0817e199af585bf0b3e8e0ed042c8b6c3 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Sun, 3 Mar 2019 15:15:45 -0600 Subject: [PATCH 029/137] updated date picker example to support keyboard announcements --- examples/datepicker/css/datepicker.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index f30ff3da3d..4d5ad007d4 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -176,6 +176,6 @@ table.dates td { .datepicker .message { position: absolute; - top: 1em; - left: 1em; + top: -30em; + left: -300em; } From 9317fd1292450e1fef939c80e57d87c05a5ab595 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Sun, 3 Mar 2019 15:45:53 -0600 Subject: [PATCH 030/137] added down arrow key --- examples/datepicker/css/datepicker.css | 2 +- examples/datepicker/datepicker.html | 4 +++- examples/datepicker/images/down-arrow-gray.png | Bin 0 -> 1325 bytes 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 examples/datepicker/images/down-arrow-gray.png diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index 4d5ad007d4..c14d27cbff 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -9,7 +9,7 @@ } .datepicker .date label { - display: inline; + display: block; } .datepicker input { diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/datepicker.html index 875f366973..36a64f98d2 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/datepicker.html @@ -59,12 +59,14 @@

    Example

    - + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SuMoTuWeThFrSa
    + +
    + + +
    + +
    + + + + +
    + +
    +

    Accessibility Features

    +

    There are three main featues of the combobox date picker example:

    +
      +
    • Textbox: Holds the value of the selected date and can open the date picker dialog box.
    • +
    • Calendar Button: Opens the datepicker dialog box.
    • +
    • Date Picker Dialog Box: Displays a grid of dates for the user to select from and provide additional buttons to change the month and year of dates shown in the grid.
    • +
    + +

    The combobox, textbox and button all have the same accessible name.

    + + +

    Textbox

    + +
      +
    • Contains the date value.
    • +
    • Opens and closes the date picker dialog box through keyboard, click and touch events.
    • +
    • A live region provides information on how to open the date picker dialog when textbox receives focus (e.g. using down arrow key).
    • +
    • To support people who are sighted in understanding the down arrow key opens the date picker dialog box, a down arrow icon appears in the textbox when the textbox receives keyboard focus and the down arrow icon is removed when the textbox does not have keyboard focus.
    • +
    + +

    Calendar Button

    + +
      +
    • Opens and closes the date picker dialog box through click events.
    • +
    • Removed from the tab order of the page (eg. tabindex=-1), since the user can use the down arrow key to open the date picker dialog box from the textbox.
    • +
    + +

    Date Picker Dialog

    + +
      +
    • The date picker dialog is a modal dialog box for selecting a date from a grid of dates for a particular month and year.
    • +
    • Additional buttons and keyboard commands are used to change month and year.
    • +
    • A live region announces changes in the month and year.
    • +
    • The dialog is opened through keyboard commands in the textbox or clicking on the calendar button.
    • +
    + +

    Mobile Support

    + +
      +
    • One issue with mobile browsers is that the onscreen keyboard is visible when the textbox has focus.
    • +
    • Adding touchstart event allows the user to close the onscreen keyboard when the textbox has focus.
    • +
    + +
    + + +
    +

    Keyboard Support

    + +
    +

    Textbox

    + + + + + + + + + + + + + +
    KeyFunction
    Down Arrow +
      +
    • Open the calendar dialog.
    • +
    • Move focus to current date.
    • +
    +
    +
    + +
    +

    Calendar Button

    + + + + + + + + + + + + + +
    KeyFunction
    Space,
    Return
    +
      +
    • Toggles calendar dialog.
    • +
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    • +
    +
    +
    + +
    +

    Date Picker Dialog

    + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    ESCClose the dialog and move focus back to the date input.
    TAB +
      +
    • Move focus to next button or grid inside the dialog.
    • +
    • If on the last button (i.e. OK Button), moves button to the first button (i.e. Previous Year Button).
    • +
    +
    Shift +
    TAB
    +
      +
    • Move focus to previous button or grid inside the dialog.
    • +
    • If on the first button (i.e. Previous Year Button), moves button to the first button (i.e. OK Button).
    • +
    +
    +
    + +
    +

    Date Picker Dialog: Calendar Buttons

    + + + + + + + + + + + + + +
    KeyFunction
    Space,
    Return
    + Change the month and/or year for selecting a date from the calendar gird. +
    +
    + +
    +

    Date Picker Dialog: Date Grid

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Space,
    Return
    +
      +
    • Select the date, close the dialog box and move focus to the date input.
    • +
    • The value of the date input is updated with the selected date in the from the selected date in the date grid.
    • +
    +
    Up ArrowMove the focus to the same day of the previous week.
    Down ArrowMove the focus to the same day of the next week.
    Right ArrowMove the focus to the next day.
    Left ArrowMove the focus to the previous day.
    HomeMove the focus to the first day (e.g Sunday) of the current week.
    EndMove the focus to the last day (e.g. Saturday) of the current week.
    PageUp +
      +
    • Change the calendar to the previous month.
    • +
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • +
    +
    Shift+
    PageUp
    +
      +
    • Change the calendar to the previous Year.
    • +
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • +
    +
    PageDown +
      +
    • Change the calendar to the next month.
    • +
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • +
    +
    Shift+
    PageDown
    +
      +
    • Change the calendar to the next Year.
    • +
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • +
    +
    TABMove focus to next button inside the dialog.
    Shift +
    TAB
    Move focus to previous button inside the dialog.
    ESC +
      +
    • Close the dialog and move focus back to date input.
    • +
    • The value of the date texbox is not updated.
    • +
    +
    +
    + +
    +

    Date Picker Buttons (OK and Cancel)

    + + + + + + + + + + + + + +
    KeyFunction
    Space,
    Return
    +
      +
    • The Cancel button closes the calendar dialog, moves focus to date input, does not update date in date input.
    • +
    • The OK button closes the calendar dialog, moves focus to date input, does update date in date input.
    • +
    +
    +
    +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + +
    +

    Combobox

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    role=comboboxdiv +
      +
    • Identifies the texbox as having a combobox behavior.
    • +
    • Accessible name is defined using the aria-labelledby attribute.
    • +
    +
    aria-labelledby=IDREFdiv + The accessible name is Date and has the same name as the names for the combobox and textbox. +
    aria-haspopup=dialogdiv + Identifies the combobox opens a dialog box. +
    aria-expanded=falsediv + aria-expanded=false when the date picker dialog box is closed. +
    aria-expanded=truediv + aria-expanded=true when the date picker dialog box is open. +
    aria-owns=IDREFdiv +
      +
    • Refers to the element that serves as the dialog box.
    • +
    • Tells browsers to arrange the screen reader reading order so the dialog box, when it is visible, immediately follows the other elements inside the combobox, regardless of where the dialog element is in the DOM.
    • +
    +
    +
    + + +
    +

    Textbox

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    textboxinput +
      +
    • The input element with no type attribute has the default role of textbox.
    • +
    • Accessible name comes from a label element using the for attribute and has the same name as the combobox and calendar button.
    • +
    +
    aria-autocomplete=noneinput + aria-autocomplete=none the textbox does not support autocompletion when text is entered. +
    aria-controls=IDREFinput + Provides a reference to the screen reader for direct navigation to the dialog box. +
    statusdiv +
      +
    • This div is rendered off screen for providing keyboard help information to screen reader users on how to open the date picker dialog.
    • +
    • When the textbox receives keyboard focus the content of the div element is updated to provide information on using the down arrow key to open the date picker dialog.
    • +
    • When the textbox looses keyboard focus the content of the div is cleared, so that the message will be repeated to screen reader users each time the textbox received focus.
    • +
    +
    +
    + +
    +

    Calendar Button

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    buttonbutton +
      +
    • The button has the default role of button.
    • +
    • Accessible name comes from aria-labelledby attribute.
    • +
    • Button is removed from tab order of the page, since the dialog can be opened from the date input with the keyboard.
    • +
    +
    tabindex=-1button + Removes the button from the tab order of the page. +
    aria-labelledby=IDREFbutton +
      +
    • Initial value of accessible name is Date, same as combobox and textbox.
    • +
    • When user selected date, the accessible name will update to current date.
    • +
    +
    aria-expanded=truebutton +
      +
    • Is defined when the date picker dialog is open.
    • +
    • When the date picker dialog is closed, the aria-expanded attribute is removed from element.
    • +
    +
    +
    + +
    +

    Date Picker Dialog

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    dialog + div + +
      +
    • Identifies div element as a dialog box container.
    • +
    • The accessible name comes from the aria-labelledby attribute.
    • +
    +
    aria-modal=truediv + Identifies that dialog box as a modal dialog box. +
    aria-labelledby=IDREFSdiv + Defines the accessible name for the dialog box. +
    +
    + +
    +

    Date Picker Dialog: Calendar Navigation Buttons

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    button + button + +
      +
    • The button element has the default role of button.
    • +
    • The accessible name comes from the aria-label attribute.
    • +
    +
    aria-controls=IDREFbutton + Provide a reference for screen reader to navigate to the date grid. +
    aria-label=Stringbutton + Defines the accessible name of the button (e.g. "Next Year"). +
    status + span + +
      +
    • When the month and/or year changes the content of the span element is updated.
    • +
    • Identifies the span element that contains the current month and year as a polite live region.
    • +
    +
    +
    + +
    +

    Date Picker Dialog: Date Grid

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    grid + table + +
      +
    • Identifies the table element serving as the grid widget container.
    • +
    • The accessible name comes from the aria-labelledby attribute.
    • +
    • Since the grid role is applied to a table element, the row, colheader, and gridcell roles do not need to be specified because they are implied by tr, th, and td tags.
    • +
    +
    aira-labelledby=IDREFtable +
      +
    • Define the accessible name for the grid using current month and year.
    • +
    +
    row + tr + + The tr element has a default role of row. +
    columnheader + th + + The default role for a th[scope="col"] element is columnheader. +
    aira-label=Stringth +
      +
    • Define the accessible name as a day of week for column headers.
    • +
    +
    gridcell + td + + Since the table element has a grid role, all descendant td elements will have the role of gridcell. +
    button + td > button + +
      +
    • The button element is used to identify the dates in calendar grid and are a child of the gridcell.
    • +
    • Accessible name for the button is the date which is defined by the text content of the button element.
    • +
    +
    + tabindex=0 + + td > button + + Identify the currently selected date and make it part of tab sequence of the dialog box. +
    + tabindex=-1 + + td > button + + Exclude the button from tab sequence to support grid cell navigation. +
    aria-selected=truetd > button +
      +
    • Identifies the currently selected date in the date input.
    • +
    • The aria-selected attribute is only set on the button representing the date in the date input, all other buttons do not have an aria-selected attribute.
    • +
    +
    +
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    +
    + + + diff --git a/examples/datepicker/combobox/js/combobox.js b/examples/datepicker/combobox/js/combobox.js new file mode 100644 index 0000000000..61e788c2c4 --- /dev/null +++ b/examples/datepicker/combobox/js/combobox.js @@ -0,0 +1,242 @@ +var ComboboxInput = function (comboboxNode, inputNode, buttonNode, messageNode, datepicker) { + this.comboboxNode = comboboxNode; + this.inputNode = inputNode; + this.buttonNode = buttonNode; + this.messageNode = messageNode; + this.imageNode = false; + + this.datepicker = datepicker; + + this.ignoreFocusEvent = false; + this.ignoreBlurEvent = false; + + this.hasFocusFlag = false; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +ComboboxInput.prototype.init = function () { + this.inputNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.inputNode.addEventListener('focus', this.handleFocus.bind(this)); + this.inputNode.addEventListener('blur', this.handleBlur.bind(this)); + this.inputNode.addEventListener('click', this.handleClick.bind(this)); + + this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); + this.buttonNode.addEventListener('touchstart', this.handleTouchStart.bind(this)); + this.buttonNode.addEventListener('keydown', this.handleButtonKeyDown.bind(this)); + + if (this.inputNode.nextElementSibling && + this.inputNode.nextElementSibling.tagName.toLowerCase() == 'img') { + this.imageNode = this.inputNode.nextElementSibling; + } + + if (this.imageNode) { + this.imageNode.addEventListener('click', this.handleClick.bind(this)); + } + + this.setMessage(''); +}; + +ComboboxInput.prototype.handleKeyDown = function (event) { + var flag = false; + + switch (event.keyCode) { + + case this.keyCode.DOWN: + this.datepicker.show(); + this.ignoreBlurEvent = true; + this.datepicker.setFocusDay(); + flag = true; + break; + + case this.keyCode.ESC: + this.datepicker.hide(false); + flag = true; + break; + + case this.keyCode.TAB: + this.ignoreBlurEvent = true; + this.datepicker.hide(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +ComboboxInput.prototype.handleTouchStart = function (event) { + console.log('[handleTouchStart][length]: ' + event.targetTouches.length); + + if (event.targetTouches.length === 1) { + if (this.comboboxNode.contains(event.targetTouches[0].target)) { + if (this.isCollapsed()) { + this.showDownArrow(); + this.datepicker.show(); + event.stopPropagation(); + event.preventDefault(); + return false; + } + } + } +}; + +ComboboxInput.prototype.handleFocus = function () { + console.log('[ComboboxInput][handleFocus][hasFocus]: ' + this.hasFocusFlag); + if (!this.ignoreFocusEvent && this.isCollapsed()) { + setTimeout(this.datepicker.show.bind(this.datepicker), 200); + this.setMessage('Use the down arrow key to move focus to the datepicker grid.'); + } + this.showDownArrow(); + + this.hasFocusFlag = true; + this.ignoreFocusEvent = false; + +}; + +ComboboxInput.prototype.handleBlur = function () { + console.log('[ComboboxInput][handleBlur]'); + if (!this.ignoreBlurEvent) { + this.datepicker.hide(false); + this.setMessage(''); + } + this.hideDownArrow(); + + this.hasFocusFlag = false; + this.ignoreBlurEvent = false; +}; + +ComboboxInput.prototype.handleClick = function (event) { + console.log('[ComboboxInput][handleClick]: ' + event.target.tagName); + console.log('[ComboboxInput][handleClick][hasFocus]: ' + this.hasFocus()); + console.log('[ComboboxInput][handleClick][isCollapsed]: ' + this.isCollapsed()); + + if (this.isCollapsed()) { + this.ignoreBlurEvent = true; + this.datepicker.show(); + } + else { + this.ignoreFocusEvent = true; + this.datepicker.hide(); + } + + event.stopPropagation(); + event.preventDefault(); + +}; + +ComboboxInput.prototype.handleButtonClick = function (event) { + this.ignoreBlurEvent = true; + this.datepicker.show(); + this.datepicker.setFocusDay(); + + if (event) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +ComboboxInput.prototype.handleButtonKeyDown = function (event) { + + switch (event.keyCode) { + case this.keyCode.RETURN: + case this.keyCode.SPACE: + this.handleButtonClick(); + this.ignoreBlurEvent = true; + this.setFocusDay(); + flag = true; + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +ComboboxInput.prototype.focus = function () { + this.inputNode.focus(); +}; + +ComboboxInput.prototype.setAriaExpanded = function (flag) { + + if (flag) { + this.comboboxNode.setAttribute('aria-expanded', 'true'); + this.buttonNode.setAttribute('aria-expanded', 'true'); + } + else { + this.comboboxNode.setAttribute('aria-expanded', 'false'); + this.buttonNode.setAttribute('aria-expanded', 'false'); + } + +}; + +ComboboxInput.prototype.getAriaExpanded = function () { + return this.comboboxNode.getAttribute('aria-expanded') === 'true'; +}; + +ComboboxInput.prototype.isCollapsed = function () { + return this.comboboxNode.getAttribute('aria-expanded') !== 'true'; +}; + +ComboboxInput.prototype.setDate = function (month, day, year) { + this.inputNode.value = (month + 1) + '/' + (day + 1) + '/' + year; +}; + +ComboboxInput.prototype.getDate = function () { + return this.inputNode.value; +}; + +ComboboxInput.prototype.setMessage = function (str) { + return this.messageNode.textContent = str; +}; + +ComboboxInput.prototype.hasFocus = function () { + return this.hasFocusFlag; +}; + +ComboboxInput.prototype.showDownArrow = function () { + if (this.imageNode) { + this.imageNode.style.visibility = 'visible'; + } +}; + +ComboboxInput.prototype.hideDownArrow = function () { + if (this.imageNode) { + this.imageNode.style.visibility = 'hidden'; + } +}; + +// Initialize combobox date picker + +window.addEventListener('load' , function () { + + var datePickers = document.querySelectorAll('[role=combobox].datepicker'); + + datePickers.forEach(function (dp) { + var dpInput = dp.querySelector('input'); + var dpButton = dp.querySelector('button'); + var dpMessage = dp.querySelector('.message'); + var dpDialog = dp.querySelector('[role=dialog]'); + var datePicker = new DatePicker(dp, dpInput, dpButton, dpDialog, dpMessage); + datePicker.init(); + }); + +}); diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/common/css/datepicker.css similarity index 100% rename from examples/datepicker/css/datepicker.css rename to examples/datepicker/common/css/datepicker.css diff --git a/examples/datepicker/images/down-arrow-gray.png b/examples/datepicker/common/images/down-arrow-gray.png similarity index 100% rename from examples/datepicker/images/down-arrow-gray.png rename to examples/datepicker/common/images/down-arrow-gray.png diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/common/js/datepicker.js similarity index 96% rename from examples/datepicker/js/datepicker.js rename to examples/datepicker/common/js/datepicker.js index 17782f61a3..1a816496d3 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/common/js/datepicker.js @@ -3,8 +3,12 @@ var DatePicker = function (comboboxNode, inputNode, buttonNode, dialogNode, messageNode) { this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - this.dateInput = new DateInput(comboboxNode, inputNode, buttonNode, messageNode, this); - this.comboboxNode = comboboxNode; + if (comboboxNode) { + this.dateInput = new ComboboxInput(comboboxNode, inputNode, buttonNode, messageNode, this); + } + else { + this.dateInput = new MenuButtonInput(inputNode, buttonNode, messageNode, this); + } this.inputNode = inputNode; this.buttonNode = buttonNode; this.dialogNode = dialogNode; @@ -241,6 +245,7 @@ DatePicker.prototype.showLastRow = function () { }; DatePicker.prototype.setFocusDay = function (flag) { + console.log('[DatePicker][setFocusDay]'); if (typeof flag !== 'boolean') { flag = true; @@ -259,6 +264,7 @@ DatePicker.prototype.setFocusDay = function (flag) { } this.hasFocusFlag = true; + console.log('[DatePicker][setFocusDay][focus]'); d.domNode.focus(); d.domNode.setAttribute('tabindex', '0'); } @@ -366,7 +372,8 @@ DatePicker.prototype.hide = function (ignore) { }; DatePicker.prototype.handleDocumentClick = function (event) { - console.log('[DateInput][handleDocumentClick]: ' + event.target.tagName); + console.log('[DateInput][handleDocumentClick][target]: ' + (event.target !== this.dateInput.inputNode)); + console.log('[DateInput][handleDocumentClick][contains]: ' + this.dialogNode.contains(event.target)); console.log('[DateInput][handleDocumentClick][isOpen]: ' + this.isOpen()); if (this.isOpen() && @@ -771,20 +778,5 @@ DatePicker.prototype.getDateInput = function () { }; -// Initialize date picker - -window.addEventListener('load' , function () { - - var datePickers = document.querySelectorAll('.datepicker'); - - datePickers.forEach(function (dp) { - var dpInput = dp.querySelector('input'); - var dpButton = dp.querySelector('button'); - var dpMessage = dp.querySelector('.message'); - var dpDialog = document.getElementById(dp.getAttribute('aria-owns')); - var datePicker = new DatePicker(dp, dpInput, dpButton, dpDialog, dpMessage); - datePicker.init(); - }); -}); diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/common/js/datepickerDay.js similarity index 100% rename from examples/datepicker/js/datepickerDay.js rename to examples/datepicker/common/js/datepickerDay.js diff --git a/examples/datepicker/menubutton/css/datepicker.css b/examples/datepicker/menubutton/css/datepicker.css new file mode 100644 index 0000000000..f117b63d02 --- /dev/null +++ b/examples/datepicker/menubutton/css/datepicker.css @@ -0,0 +1,190 @@ +.datepicker { + margin-top: 1em; +} + +.datepicker .downarrow { + content: url('../images/down-arrow-gray.png'); + position: relative; + left: -20px; +} + +.datepicker button.icon { + border-style: none; + text-align: left; + background-color: white; + position: relative; + left: -20px; + top: 4px; +} + +.datepicker .date label { + display: block; +} + +.datepicker input { + margin-top: 1em; + width: 20%; +} + +.datepickerDialog { + width: 45%; + clear: both; + display: none; + border: 3px solid hsl(216, 80%, 55%); + margin-top: 1em; + border-radius: 5px; + padding: 0; +} + +.header { + cursor: default; + background-color: hsl(216, 80%, 55%); + padding: 7px; + font-weight: bold; + text-transform: uppercase; + color: white; + display: flex; + justify-content: space-around; +} + +.header span { + display: inline-block; +} + +.header button { + border-style: none; + background: transparent; +} + +.datepickerDialog button::-moz-focus-inner { + border: 0; +} + +.prevYear, +.prevMonth, +.nextMonth, +.nextYear { + padding: 4px; + width: 24px; + height: 24px; + color: white; +} + +.prevYear:focus, +.prevMonth:focus, +.nextMonth:focus, +.nextYear:focus { + padding: 2px; + border: 2px solid white; + border-radius: 4px; + outline: 0; +} + +.dialogButtonGroup { + text-align: right; + margin-top: 1em; + margin-bottom: 1em; + margin-right: 1em; +} + +.dialogButton { + padding: 5px; + margin-left: 1em; + width: 5em; + background-color: hsl(216, 80%, 92%); + font-size: 0.85em; + color: black; + outline: none; + border: 1px solid hsl(216, 80%, 92%); + border-radius: 5px; +} + +.dialogButton:focus { + padding: 4px; + border: 2px solid black; +} + +.fa-calendar-alt { + color: hsl(216, 89%, 72%); +} + +.datepicker .monthYear { + display: inline-block; + width: 12em; + text-align: center; +} + +table.dates { + width: 100%; + padding-left: 1em; + padding-right: 1em; + padding-top: 1em; +} + +table.dates th, +table.dates td { + text-align: center; +} + +.dateRow { + border: 1px solid black; +} + +.dateCell { + outline: 0; + border: 0; + padding: 0; + margin: 0; + height: 40px; + width: 40px; +} + +.dateButton { + padding: 0; + margin: 0; + line-height: inherit; + height: 100%; + width: 100%; + border: 1px solid #eee; + border-radius: 5px; + font-size: 15px; + background: #eee; +} + +.dateButton:focus, +.dateButton:hover { + padding: 0; + background-color: hsl(216, 80%, 92%); +} + +.dateButton:focus { + border-width: 2px; + border-color: rgb(100, 100, 100); + outline: 0; +} + +.dateButton[aria-selected] { + border-color: rgb(100, 100, 100); +} + +.dateButton[tabindex="0"] { + background-color: hsl(216, 80%, 92%); +} + +.disabled { + cursor: not-allowed; + color: #afafaf; +} + +.dateButton:disabled { + color: #777; + background-color: #fff; + border: none; + cursor: not-allowed; +} + +.datepicker .message { + position: absolute; + top: -30em; + left: -300em; +} diff --git a/examples/datepicker/menubutton/images/down-arrow-gray.png b/examples/datepicker/menubutton/images/down-arrow-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d34f2e07d8f85cfb228ca7a587a94204f5790a GIT binary patch literal 1325 zcmV+|1=9M7P)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z+et)0R45f=U>J0Ov7w=XOHxwuH!&`I{`@&B3mY5T%J=WzYyJQK{|`P*jEsyRwbxkw z{ri{2#Kg3lot<6%_wU~fKnWCmK!X@qSy>r={rYtiq!ujO*4Adu%*?zKsFEKjh@t^# z7>NJz@87@fRaI3d!8{NHC{PGA{XdFoAO|D{k^`v$$uYwe?A^Qff`fyD7|@-jFk4ty zSQ!5N`7;HmxE-brYy*q~^g}ri9|kFeDn1N!dlgI`M5Fo;C;;@28qnQ`K$1Y4?0}}< j000000NkvXXu0mjf+hB%b literal 0 HcmV?d00001 diff --git a/examples/datepicker/datepicker.html b/examples/datepicker/menubutton/index.html similarity index 77% rename from examples/datepicker/datepicker.html rename to examples/datepicker/menubutton/index.html index 9a12dbefd4..8b54a7a11a 100644 --- a/examples/datepicker/datepicker.html +++ b/examples/datepicker/menubutton/index.html @@ -2,21 +2,21 @@ -Date Picker Example Using ARIA 1.1 Combobox | WAI-ARIA Authoring Practices 1.1 +Date Picker Example Using Menu Button| WAI-ARIA Authoring Practices 1.1 - + - - - + + + - - - - + + + +
    -

    Date Picker Example Using ARIA 1.1 Combobox

    +

    Date Picker Example Using Menu Button

    NOTE: This example page is work in progress. Please provide feedback in issue 34.

    - The date picker is an example of the combobox design pattern that opens a dialog box. - The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the calendar button. + The date picker is an example of the Menu Button design pattern that opens a dialog box. + The date picker dialog box in this example is opened when keyboard focus is moved to the calendar button and a space, return, down arrow key pressed or clicking on the calendar button. The date picker dialog uses a grid pattern to show and select a date using the cursor keys. Additional buttons in the dialog box can be used for changing the month and year shown in the grid.

    @@ -46,27 +46,24 @@

    Example

    +
    +

    Accessibility Features

    +

    There are three main featues of the combobox date picker example:

    +
      +
    • Textbox: Holds the value of the selected date and can open the date picker dialog box.
    • +
    • Calendar Button: Opens the datepicker dialog box.
    • +
    • Date Picker Dialog Box: Displays a grid of dates for the user to select from and provide additional buttons to change the month and year of grid of dates.
    • +
    + +

    Textbox

    + +
      +
    • Contains the date value.
    • +
    • Opens the date picker dialog box through keyboard.
    • +
    + +

    Calendar Button

    + +
      +
    • Opens the date picker dialog box through click events.
    • +
    • Removed from the tab order of the page.
    • +
    + +

    Date Picker Dialog

    + +
      +
    • Provides a grid of dates from the user to select from.
    • +
    • Removed from the tab order of the page.
    • +
    + +
    + +

    Keyboard Support

    -

    Combobox

    +

    Textbox

    @@ -228,7 +258,7 @@

    Combobox

    -

    Button (Open Date Picker)

    +

    Calendar Button

    @@ -251,7 +281,7 @@

    Button (Open Date Picker)

    -

    Dialog (Date Picker)

    +

    Date Picker Dialog

    @@ -287,7 +317,7 @@

    Dialog (Date Picker)

    -

    Button (Next/Previous Month and Year)

    +

    Date Picker Dialog: Calendar Buttons

    @@ -307,7 +337,7 @@

    Button (Next/Previous Month and Year)

    -

    Grid (Days in Month)

    +

    Date Picker Dialog: Date Grid

    @@ -321,7 +351,7 @@

    Grid (Days in Month)

    @@ -407,7 +437,7 @@

    Grid (Days in Month)

    -

    Buttons (OK and Cancel)

    +

    Date Picker Buttons (OK and Cancel)

    • Select the date, close the dialog box and move focus to the date input.
    • -
    • The value of the date input is updated with the selected date in the calendar grid.
    • +
    • The value of the date input is updated with the selected date in the from the selected date in the date grid.
    @@ -432,9 +462,10 @@

    Buttons (OK and Cancel)

    Role, Property, State, and Tabindex Attributes

    +
    -

    Combobox

    -
    +

    Combobox

    +
    @@ -458,12 +489,12 @@

    Combobox

    - + - + @@ -471,7 +502,7 @@

    Combobox

    Identifies the combobox opens a dialog box. - + @@ -487,7 +518,7 @@

    Combobox

    aria-expanded=true when the date picker dialog box is open. - + @@ -502,8 +533,52 @@

    Combobox

    Role
    aria-label=Datebuttondiv Defines the accessible name of the combobox (e.g. "Date").
    aria-haspopup=dialog div
    aria-expanded=false div
    aria-owns=IDREF div
    + +
    +

    Textbox

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    textboxinput +
      +
    • The input element with no type attribute has the default role of textbox.
    • +
    • Accessible name comes from a label element using the for attribute.
    • +
    +
    aria-autocomplete=noneinput + aria-autocomplete=none the textbox does not support autocompletion when text is entered. +
    aria-controls=IDREFinput + Provides a reference to the screen reader for direct navigation to the dialog box. +
    +
    +
    -

    Button (Open Date Picker)

    +

    Calendar Button

    @@ -520,13 +595,13 @@

    Button (Open Date Picker)

    - + @@ -534,7 +609,7 @@

    Button (Open Date Picker)

    Removes the button from the tab order of the page. - + @@ -545,7 +620,7 @@

    Button (Open Date Picker)

    - + @@ -561,7 +636,7 @@

    Button (Open Date Picker)

    -

    Dialog (Date Picker)

    +

    Date Picker Dialog

    button
      -
    • The button provides a means to open the calendar dialog box using the mouse or touch.
    • +
    • The button has the default role of button.
    • Accessible name comes from aria-label attribute.
    • Button is removed from tab order of the page, since the dialog can be opened from the date input with the keyboard.
    tabindex=-1 button
    aria-label=String button
    aria-expanded=true button
    @@ -572,17 +647,20 @@

    Dialog (Date Picker)

    - + - + @@ -590,7 +668,7 @@

    Dialog (Date Picker)

    Identifies that dialog box as a modal dialog box. - + @@ -603,7 +681,7 @@

    Dialog (Date Picker)

    -

    Buttons (Next/Previous Month and Year)

    +

    Date Picker Dialog: Calendar Navigation Buttons

    dialog div - Identifies div element as a dialog box container. +
      +
    • Identifies div element as a dialog box container.
    • +
    • The accessible name comes from the aria-labelledby attribute.
    • +
    aria-modal=true div
    aria-labelledby=IDREFS div
    @@ -615,35 +693,35 @@

    Buttons (Next/Previous Month and Year)

    - + + + + + + + + + + + + + - - - - - - - - - - - - - + - + @@ -713,7 +659,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

    Provide a reference for screen reader to navigate to the date grid. - + @@ -721,7 +667,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

    Defines the accessible name of the button (e.g. "Next Year"). - + - - - - - - - + + + + + + + From 47964619c63079cd0e2caa07ac82b91e6e9f0ae3 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 18 Jun 2019 14:43:33 -0500 Subject: [PATCH 048/137] fixed bugs with buttons for changing the next/previous year and month buttons --- examples/datepicker/css/datepicker.css | 4 +++ examples/datepicker/datepicker-combobox.html | 2 +- .../datepicker/datepicker-menubutton.html | 21 ++++++------ examples/datepicker/js/datepicker-combobox.js | 17 ++++++++++ .../datepicker/js/datepicker-menubutton.js | 31 +++++------------ examples/datepicker/js/datepicker.js | 33 +++++-------------- 6 files changed, 48 insertions(+), 60 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index e4b42bb60f..0e21ebd87e 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -12,6 +12,10 @@ top: 3px; } +.datepicker button.menubutton.icon { + left: -4px; +} + .datepicker span.arrow { margin: 0; padding: 0; diff --git a/examples/datepicker/datepicker-combobox.html b/examples/datepicker/datepicker-combobox.html index 9692a2d7f2..7522e3938b 100644 --- a/examples/datepicker/datepicker-combobox.html +++ b/examples/datepicker/datepicker-combobox.html @@ -277,7 +277,7 @@

    Textbox

    - + - + - +
      +
    • Increases the spin button value by 1 item.
    • +
    • When on the spin button for day is on the last day of the month, the value is set to the first day.
    • +
    • When on the spin button for month is on December, the value is set to January.
    • +
    From 0c63e6acdbd1cbdd713d2d1117aad6a252f58cfc Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 20 Jun 2019 13:38:41 -0500 Subject: [PATCH 054/137] added documenation about values wrapping for day and month --- examples/datepicker/datepicker-spinbuttons.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html index 8e51f4404d..e3c7d99216 100644 --- a/examples/datepicker/datepicker-spinbuttons.html +++ b/examples/datepicker/datepicker-spinbuttons.html @@ -155,11 +155,13 @@

    Spin Button (Day, Month and Year)

    + From 893b41939dabac7de9c1e275fd01dd14bfa98949 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 21 Jun 2019 11:00:14 -0500 Subject: [PATCH 055/137] button label is updated with current date information --- .../datepicker/css/datepicker-spinbuttons.css | 29 ++++++++-- examples/datepicker/js/datepicker-combobox.js | 15 ----- .../datepicker/js/datepicker-menubutton.js | 56 ++++--------------- examples/datepicker/js/datepicker.js | 28 +++++++--- examples/datepicker/js/datepickerDay.js | 5 +- 5 files changed, 58 insertions(+), 75 deletions(-) diff --git a/examples/datepicker/css/datepicker-spinbuttons.css b/examples/datepicker/css/datepicker-spinbuttons.css index 2983e9cedb..e361a7c1c1 100644 --- a/examples/datepicker/css/datepicker-spinbuttons.css +++ b/examples/datepicker/css/datepicker-spinbuttons.css @@ -2,7 +2,6 @@ margin-top: 1em; } - .datepicker-spinbuttons .day { width: 2em; } @@ -15,15 +14,38 @@ width: 3em; } - .datepicker-spinbuttons .spinbutton { float: left; text-align: center; } +.datepicker-spinbuttons .spinbutton:first-child { + border-left: 4px; +} + +.datepicker-spinbuttons .spinbutton:last-child { + border-right: 4px; +} + .datepicker-spinbuttons .spinbutton .previous, .datepicker-spinbuttons .spinbutton .next { - color: gray; + color: #666; +} + +.datepicker-spinbuttons .spinbutton:focus { + outline: 2px solid #005A9C; +} + +.datepicker-spinbuttons .spinbutton:focus, +.datepicker-spinbuttons .spinbutton:hover { + color: #444; + background-color: #EEE; +} + +.datepicker-spinbuttons .spinbutton:focus .value, +.datepicker-spinbuttons .spinbutton:hover .value { + background-color: #FFF; + color: black; } .datepicker-spinbuttons .spinbutton .previous { @@ -34,7 +56,6 @@ border-top: 1px solid black; } - .datepicker-spinbuttons .spinbutton .decrease svg polygon, .datepicker-spinbuttons .spinbutton .increase svg polygon { fill: #333; diff --git a/examples/datepicker/js/datepicker-combobox.js b/examples/datepicker/js/datepicker-combobox.js index 5a36ad02a2..87f9261fbf 100644 --- a/examples/datepicker/js/datepicker-combobox.js +++ b/examples/datepicker/js/datepicker-combobox.js @@ -93,8 +93,6 @@ ComboboxInput.prototype.handleKeyDown = function (event) { }; ComboboxInput.prototype.handleTouchStart = function (event) { - console.log('[handleTouchStart][length]: ' + event.targetTouches.length); - if (event.targetTouches.length === 1) { if (this.comboboxNode.contains(event.targetTouches[0].target)) { if (this.isCollapsed()) { @@ -108,9 +106,6 @@ ComboboxInput.prototype.handleTouchStart = function (event) { }; ComboboxInput.prototype.handleFocus = function () { - console.log('[ComboboxInput][handleFocus][ignoreFocusEvent]: ' + this.ignoreFocusEvent); - console.log('[ComboboxInput][handleFocus][isMouseDownOnBackground]: ' + this.datepicker.isMouseDownOnBackground); - if (!this.datepicker.isMouseDownOnBackground && !this.isMouseDownOnButton && !this.ignoreFocusEvent && @@ -125,9 +120,6 @@ ComboboxInput.prototype.handleFocus = function () { }; ComboboxInput.prototype.handleBlur = function () { - console.log('[ComboboxInput][handleBlur][ignoreBlurEvent]: ' + this.ignoreBlurEvent); - console.log('[ComboboxInput][handleBlur][isMouseDownOnBackground]: ' + this.datepicker.isMouseDownOnBackground); - if (!this.datepicker.isMouseDownOnBackground && !this.isMouseDownOnButton && !this.ignoreBlurEvent) { @@ -148,10 +140,6 @@ ComboboxInput.prototype.hasFocus = function () { }; ComboboxInput.prototype.handleMouseDown = function (event) { - console.log('[ComboboxInput][handleMouseDown]: ' + event.target.tagName + '.' + event.target.className); - console.log('[ComboboxInput][handleMouseDown][hasFocus]: ' + this.hasFocus()); - console.log('[ComboboxInput][handleMouseDown][isCollapsed]: ' + this.isCollapsed()); - if (this.isCollapsed()) { this.datepicker.show(); } @@ -166,9 +154,6 @@ ComboboxInput.prototype.handleMouseDown = function (event) { }; ComboboxInput.prototype.handleButtonMouseDown = function (event) { - console.log('[ComboboxInput][handleButtonMouseDown]'); - console.log('[ComboboxInput][handleButtonMouseDown][hasFocusFlag]: ' + this.hasFocusFlag); - this.isMouseDownOnButton = true; if (this.isCollapsed()) { diff --git a/examples/datepicker/js/datepicker-menubutton.js b/examples/datepicker/js/datepicker-menubutton.js index a2bc7ccd1f..4f6eac3659 100644 --- a/examples/datepicker/js/datepicker-menubutton.js +++ b/examples/datepicker/js/datepicker-menubutton.js @@ -20,7 +20,7 @@ var MenuButtonInput = function (inputNode, buttonNode, messageNode, datepicker) this.keyCode = Object.freeze({ 'TAB': 9, - 'RETURN': 13, + 'ENTER': 13, 'ESC': 27, 'SPACE': 32, 'PAGEUP': 33, @@ -36,10 +36,13 @@ var MenuButtonInput = function (inputNode, buttonNode, messageNode, datepicker) MenuButtonInput.prototype.init = function () { - this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); - this.buttonNode.addEventListener('touchstart', this.handleTouchStart.bind(this)); + this.buttonNode.addEventListener('click', this.handleClick.bind(this)); this.buttonNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.buttonNode.addEventListener('focus', this.handleFocus.bind(this)); + this.buttonNode.addEventListener('blur', this.handleBlur.bind(this)); + + this.setMessage(''); }; @@ -49,9 +52,8 @@ MenuButtonInput.prototype.handleKeyDown = function (event) { switch (event.keyCode) { case this.keyCode.DOWN: - case this.keyCode.RETURN: + case this.keyCode.ENTER: this.datepicker.show(); - this.ignoreBlurEvent = true; this.datepicker.setFocusDay(); flag = true; break; @@ -71,67 +73,33 @@ MenuButtonInput.prototype.handleKeyDown = function (event) { } }; -MenuButtonInput.prototype.handleTouchStart = function (event) { - - if (event.targetTouches.length === 1) { - console.log('[handleTouchStart][tagName]: ' + event.targetTouches[0].target.tagName); - if (this.comboboxNode.contains(event.targetTouches[0].target)) { - if (this.isCollapsed()) { - this.datepicker.show(); - event.stopPropagation(); - event.preventDefault(); - return false; - } - } - } -}; - MenuButtonInput.prototype.handleFocus = function () { - console.log('[MenuButtonInput][handleFocus]') - if (!this.ignoreFocusEvent && this.isCollapsed()) { + if (this.isCollapsed()) { this.setMessage('Use the down arrow key or the following change date button to move focus to the datepicker grid.'); } - - this.hasFocusFlag = true; - this.ignoreFocusEvent = false; - }; - MenuButtonInput.prototype.handleBlur = function () { - if (!this.ignoreBlurEvent) { - this.setMessage(''); - } - this.hasFocusFlag = false; - this.ignoreBlurEvent = false; + this.setMessage(''); }; MenuButtonInput.prototype.handleClick = function (event) { - console.log('[MenuButtonInput][handleClick]: ' + event.target.tagName); if (this.isCollapsed()) { this.datepicker.show(); } else { - this.ignoreFocusEvent = true; this.datepicker.hide(); } -}; - -MenuButtonInput.prototype.handleButtonClick = function (event) { - this.ignoreBlurEvent = true; - this.datepicker.show(); - this.datepicker.setFocusDay(); + event.stopPropagation(); + event.preventDefault(); - if (event) { - event.stopPropagation(); - event.preventDefault(); - } }; MenuButtonInput.prototype.setFocus = function () { + this.buttonNode.setAttribute('aria-label', this.datepicker.getButtonLabel()) this.buttonNode.focus(); }; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 1b3dae8920..68b7c0aff5 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -6,7 +6,8 @@ */ var DatePicker = function (comboboxNode, inputNode, buttonNode, messageNode, dialogNode) { - this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + this.dayLabels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; this.inputNode = inputNode; this.buttonNode = buttonNode; @@ -54,7 +55,7 @@ var DatePicker = function (comboboxNode, inputNode, buttonNode, messageNode, dia this.keyCode = Object.freeze({ 'TAB': 9, - 'RETURN': 13, + 'ENTER': 13, 'ESC': 27, 'SPACE': 32, 'PAGEUP': 33, @@ -131,7 +132,7 @@ DatePicker.prototype.updateGrid = function (year, month) { month = this.month; } - this.MonthYearNode.innerHTML = this.months[month] + ' ' + year; + this.MonthYearNode.innerHTML = this.monthLabels[month] + ' ' + year; this.daysInCurrentMonth = this.getDaysInMonth(year, month); @@ -406,7 +407,7 @@ DatePicker.prototype.handleOkButton = function (event) { case 'keydown': switch (event.keyCode) { - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.setTextboxDate(); @@ -456,7 +457,7 @@ DatePicker.prototype.handleCancelButton = function (event) { case 'keydown': switch (event.keyCode) { - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.hide(); flag = true; @@ -503,7 +504,7 @@ DatePicker.prototype.handleNextYearButton = function (event) { flag = true; break; - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToNextYear(); this.adjustCurrentDay(onFirstRow, onLastRow); @@ -544,7 +545,7 @@ DatePicker.prototype.handlePreviousYearButton = function (event) { switch (event.keyCode) { - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToPreviousYear(); this.adjustCurrentDay(onFirstRow, onLastRow); @@ -604,7 +605,7 @@ DatePicker.prototype.handleNextMonthButton = function (event) { flag = true; break; - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToNextMonth(); this.adjustCurrentDay(onFirstRow, onLastRow); @@ -649,7 +650,7 @@ DatePicker.prototype.handlePreviousMonthButton = function (event) { flag = true; break; - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToPreviousMonth(); this.adjustCurrentDay(onFirstRow, onLastRow); @@ -811,4 +812,13 @@ DatePicker.prototype.getDateInput = function () { }; +DatePicker.prototype.getButtonLabel = function () { + this.selectedDay = new Date(this.year, this.month, this.day + 1); + var label = 'Calendar, current date is '; + label += this.dayLabels[this.selectedDay.getDay()]; + label += ' ' + this.monthLabels[this.selectedDay.getMonth()]; + label += ' ' + (this.selectedDay.getDate()); + label += ', ' + this.selectedDay.getFullYear(); + return label; +}; diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js index 3f03498bd5..aaf6c5d7a9 100644 --- a/examples/datepicker/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -20,7 +20,7 @@ var DatePickerDay = function (domNode, datepicker, index, row, column) { this.keyCode = Object.freeze({ 'TAB': 9, - 'RETURN': 13, + 'ENTER': 13, 'ESC': 27, 'SPACE': 32, 'PAGEUP': 33, @@ -85,7 +85,7 @@ DatePickerDay.prototype.handleKeyDown = function (event) { flag = true; break; - case this.keyCode.RETURN: + case this.keyCode.ENTER: case this.keyCode.SPACE: this.datepicker.setTextboxDate(); this.datepicker.hide(); @@ -156,7 +156,6 @@ DatePickerDay.prototype.handleKeyDown = function (event) { }; DatePickerDay.prototype.handleMouseDown = function (event) { - console.log('[DatePickerDay][handleMouseDown]'); this.datepicker.day = this.day; if (this.isDisabled()) { From 4f0cf6681b1c8c3b095326ac180d534f4e3e7b66 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 21 Jun 2019 11:03:47 -0500 Subject: [PATCH 056/137] button label is updated with current date information for combobox too now --- examples/datepicker/js/datepicker-combobox.js | 1 + examples/datepicker/js/datepicker-menubutton.js | 2 +- examples/datepicker/js/datepicker.js | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/datepicker/js/datepicker-combobox.js b/examples/datepicker/js/datepicker-combobox.js index 87f9261fbf..e2101cc506 100644 --- a/examples/datepicker/js/datepicker-combobox.js +++ b/examples/datepicker/js/datepicker-combobox.js @@ -132,6 +132,7 @@ ComboboxInput.prototype.handleBlur = function () { }; ComboboxInput.prototype.setFocus = function () { + this.buttonNode.setAttribute('aria-label', 'Date, current date is ' + this.datepicker.getDateForButtonLabel()) this.inputNode.focus(); }; diff --git a/examples/datepicker/js/datepicker-menubutton.js b/examples/datepicker/js/datepicker-menubutton.js index 4f6eac3659..2987d4b72b 100644 --- a/examples/datepicker/js/datepicker-menubutton.js +++ b/examples/datepicker/js/datepicker-menubutton.js @@ -99,7 +99,7 @@ MenuButtonInput.prototype.handleClick = function (event) { MenuButtonInput.prototype.setFocus = function () { - this.buttonNode.setAttribute('aria-label', this.datepicker.getButtonLabel()) + this.buttonNode.setAttribute('aria-label', 'Calendar, current date is ' + this.datepicker.getDateForButtonLabel()) this.buttonNode.focus(); }; diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 68b7c0aff5..318f14d1d9 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -812,10 +812,9 @@ DatePicker.prototype.getDateInput = function () { }; -DatePicker.prototype.getButtonLabel = function () { +DatePicker.prototype.getDateForButtonLabel = function () { this.selectedDay = new Date(this.year, this.month, this.day + 1); - var label = 'Calendar, current date is '; - label += this.dayLabels[this.selectedDay.getDay()]; + var label = this.dayLabels[this.selectedDay.getDay()]; label += ' ' + this.monthLabels[this.selectedDay.getMonth()]; label += ' ' + (this.selectedDay.getDate()); label += ', ' + this.selectedDay.getFullYear(); From b16c6736577381e94f6ca6bd948a418a52454626 Mon Sep 17 00:00:00 2001 From: Matt King Date: Mon, 24 Jun 2019 13:15:17 -0700 Subject: [PATCH 057/137] Move spinbutton and combobox examples Moved the combobox datepicker to the aria 1.1 combobox directory. Move the spinbutton date picker to new branch issue125-spinbutton-datepicker-example. --- .../aria1.1pattern/css/datepicker.css | 214 +++++ .../aria1.1pattern}/datepicker-combobox.html | 0 .../aria1.1pattern}/js/datepicker-combobox.js | 2 +- .../combobox/aria1.1pattern/js/datepicker.js | 823 ++++++++++++++++++ .../aria1.1pattern/js/datepickerDay.js | 175 ++++ .../datepicker/css/datepicker-spinbuttons.css | 85 -- .../datepicker/datepicker-spinbuttons.html | 315 ------- .../datepicker/js/datepicker-spinbuttons.js | 154 ---- examples/datepicker/js/spinbutton-date.js | 222 ----- 9 files changed, 1213 insertions(+), 777 deletions(-) create mode 100644 examples/combobox/aria1.1pattern/css/datepicker.css rename examples/{datepicker => combobox/aria1.1pattern}/datepicker-combobox.html (100%) rename examples/{datepicker => combobox/aria1.1pattern}/js/datepicker-combobox.js (99%) create mode 100644 examples/combobox/aria1.1pattern/js/datepicker.js create mode 100644 examples/combobox/aria1.1pattern/js/datepickerDay.js delete mode 100644 examples/datepicker/css/datepicker-spinbuttons.css delete mode 100644 examples/datepicker/datepicker-spinbuttons.html delete mode 100644 examples/datepicker/js/datepicker-spinbuttons.js delete mode 100644 examples/datepicker/js/spinbutton-date.js diff --git a/examples/combobox/aria1.1pattern/css/datepicker.css b/examples/combobox/aria1.1pattern/css/datepicker.css new file mode 100644 index 0000000000..078b08b29d --- /dev/null +++ b/examples/combobox/aria1.1pattern/css/datepicker.css @@ -0,0 +1,214 @@ +.datepicker { + margin-top: 1em; +} + + +.datepicker button.icon { + border-style: none; + text-align: left; + background-color: white; + position: relative; + left: -25px; + top: 3px; +} + +.datepicker button.menubutton.icon { + left: -4px; +} + +.datepicker span.arrow { + margin: 0; + padding: 0; + display: none; + background: transparent; +} + +.datepicker span.arrow svg polygon { + stroke: gray; + stroke-width: 1px; + fill: gray; +} + +.datepicker[aria-expanded=false] span.arrow.up { + display: inline-block; + position: relative; + left: -23px; +} + +.datepicker[aria-expanded=true] span.arrow.down { + display: inline-block; + position: relative; + left: -23px; +} + +.datepicker input { + margin: 0; + width: 20%; +} + +.datepickerDialog { + width: 45%; + clear: both; + display: none; + border: 3px solid hsl(216, 80%, 55%); + margin-top: 1em; + border-radius: 5px; + padding: 0; + background-color: #fff; +} + +.header { + cursor: default; + background-color: hsl(216, 80%, 55%); + padding: 7px; + font-weight: bold; + text-transform: uppercase; + color: white; + display: flex; + justify-content: space-around; +} + +.header span { + display: inline-block; +} + +.header button { + border-style: none; + background: transparent; +} + +.datepickerDialog button::-moz-focus-inner { + border: 0; +} + +.prevYear, +.prevMonth, +.nextMonth, +.nextYear { + padding: 4px; + width: 24px; + height: 24px; + color: white; +} + +.prevYear:focus, +.prevMonth:focus, +.nextMonth:focus, +.nextYear:focus { + padding: 2px; + border: 2px solid white; + border-radius: 4px; + outline: 0; +} + +.dialogButtonGroup { + text-align: right; + margin-top: 1em; + margin-bottom: 1em; + margin-right: 1em; +} + +.dialogButton { + padding: 5px; + margin-left: 1em; + width: 5em; + background-color: hsl(216, 80%, 92%); + font-size: 0.85em; + color: black; + outline: none; + border: 1px solid hsl(216, 80%, 92%); + border-radius: 5px; +} + +.dialogButton:focus { + padding: 4px; + border: 2px solid black; +} + +.fa-calendar-alt { + color: hsl(216, 89%, 72%); +} + +.datepicker .monthYear { + display: inline-block; + width: 12em; + text-align: center; +} + +table.dates { + width: 100%; + padding-left: 1em; + padding-right: 1em; + padding-top: 1em; +} + +table.dates th, +table.dates td { + text-align: center; +} + +.dateRow { + border: 1px solid black; +} + +.dateCell { + outline: 0; + border: 0; + padding: 0; + margin: 0; + height: 40px; + width: 40px; +} + +.dateButton { + padding: 0; + margin: 0; + line-height: inherit; + height: 100%; + width: 100%; + border: 1px solid #eee; + border-radius: 5px; + font-size: 15px; + background: #eee; +} + +.dateButton:focus, +.dateButton:hover { + padding: 0; + background-color: hsl(216, 80%, 92%); +} + +.dateButton:focus { + border-width: 2px; + border-color: rgb(100, 100, 100); + outline: 0; +} + +.dateButton[aria-selected] { + border-color: rgb(100, 100, 100); +} + +.dateButton[tabindex="0"] { + background-color: hsl(216, 80%, 92%); +} + +.disabled { + color: #afafaf; +} + +.disabled:hover { + color: black; +} + +.dateButton:disabled { + color: #777; + background-color: #fff; + border: none; + cursor: not-allowed; +} + +.datepicker .message { + position: absolute; + top: -30em; + left: -300em; +} diff --git a/examples/datepicker/datepicker-combobox.html b/examples/combobox/aria1.1pattern/datepicker-combobox.html similarity index 100% rename from examples/datepicker/datepicker-combobox.html rename to examples/combobox/aria1.1pattern/datepicker-combobox.html diff --git a/examples/datepicker/js/datepicker-combobox.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox.js similarity index 99% rename from examples/datepicker/js/datepicker-combobox.js rename to examples/combobox/aria1.1pattern/js/datepicker-combobox.js index e2101cc506..e33af916dc 100644 --- a/examples/datepicker/js/datepicker-combobox.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox.js @@ -132,7 +132,7 @@ ComboboxInput.prototype.handleBlur = function () { }; ComboboxInput.prototype.setFocus = function () { - this.buttonNode.setAttribute('aria-label', 'Date, current date is ' + this.datepicker.getDateForButtonLabel()) + this.buttonNode.setAttribute('aria-label', 'Date, current date is ' + this.datepicker.getDateForButtonLabel()); this.inputNode.focus(); }; diff --git a/examples/combobox/aria1.1pattern/js/datepicker.js b/examples/combobox/aria1.1pattern/js/datepicker.js new file mode 100644 index 0000000000..318f14d1d9 --- /dev/null +++ b/examples/combobox/aria1.1pattern/js/datepicker.js @@ -0,0 +1,823 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: datepicker.js +*/ + +var DatePicker = function (comboboxNode, inputNode, buttonNode, messageNode, dialogNode) { + this.dayLabels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + this.inputNode = inputNode; + this.buttonNode = buttonNode; + this.messageNode = messageNode; + this.dialogNode = dialogNode; + + if (comboboxNode) { + this.dateInput = new ComboboxInput(comboboxNode, this.inputNode, this.buttonNode, this.messageNode, this); + } + else { + this.dateInput = new MenuButtonInput(this.inputNode, this.buttonNode, this.messageNode, this); + } + + this.MonthYearNode = this.dialogNode.querySelector('.monthYear'); + + this.prevYearNode = this.dialogNode.querySelector('.prevYear'); + this.prevMonthNode = this.dialogNode.querySelector('.prevMonth'); + this.nextMonthNode = this.dialogNode.querySelector('.nextMonth'); + this.nextYearNode = this.dialogNode.querySelector('.nextYear'); + + this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); + this.cancelButtonNode = this.dialogNode.querySelector('button[value="cancel"]'); + + this.tbodyNode = this.dialogNode.querySelector('table.dates tbody'); + + this.lastRowNode = null; + + var date = new Date(); + + this.year = date.getFullYear(); + this.month = date.getMonth(); + this.day = date.getDate() - 1; + + this.daysInCurrentMonth = this.getDaysInMonth(); + this.daysInLastMonth = this.getDaysInLastMonth(); + + this.days = []; + + this.selectedDay = new Date(this.year, this.month, this.day); + + this.currentDay = null; + + this.hasFocusFlag = false; + this.isMouseDownOnBackground = false; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'ENTER': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); + +}; + +DatePicker.prototype.init = function () { + + this.dateInput.init(); + + this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); + this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); + + this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); + this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); + + this.prevMonthNode.addEventListener('click', this.handlePreviousMonthButton.bind(this)); + this.nextMonthNode.addEventListener('click', this.handleNextMonthButton.bind(this)); + this.prevYearNode.addEventListener('click', this.handlePreviousYearButton.bind(this)); + this.nextYearNode.addEventListener('click', this.handleNextYearButton.bind(this)); + + this.prevMonthNode.addEventListener('keydown', this.handlePreviousMonthButton.bind(this)); + this.nextMonthNode.addEventListener('keydown', this.handleNextMonthButton.bind(this)); + this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); + + this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); + + document.body.addEventListener('mousedown', this.handleBackgroundMouseDown.bind(this), true); + document.body.addEventListener('mouseup', this.handleBackgroundMouseUp.bind(this), true); + + // Create Grid of Dates + + this.tbodyNode.innerHTML = ''; + var index = 0; + for (var i = 0; i < 6;i++) { + var row = this.tbodyNode.insertRow(i); + this.lastRowNode = row; + row.classList.add('dateRow'); + for (var j = 0;j < 7; j++) { + var cell = document.createElement('td'); + cell.classList.add('dateCell'); + var cellButton = document.createElement('button'); + cellButton.classList.add('dateButton'); + cell.appendChild(cellButton); + row.appendChild(cell); + var dpDay = new DatePickerDay(cellButton, this, index, i, j); + dpDay.init(); + this.days.push(dpDay); + index++; + } + } + + this.updateGrid(); + this.setFocusDay(); +}; + +DatePicker.prototype.updateGrid = function (year, month) { + + var i; + + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } + + this.MonthYearNode.innerHTML = this.monthLabels[month] + ' ' + year; + + this.daysInCurrentMonth = this.getDaysInMonth(year, month); + + var lastMonth = month - 1; + var lastYear = year; + if (lastMonth < 0) { + lastMonth = 11; + lastYear = year - 1; + } + + var daysInLastMonth = this.getDaysInMonth(lastYear, lastMonth); + this.daysInLastMonth = daysInLastMonth; + + var nextMonth = month + 1; + var nextYear = year; + if (nextMonth > 11) { + nextMonth = 1; + nextYear = year + 1; + } + + var firstDayOfMonth = new Date(year, month, 1); + var dayOfWeek = firstDayOfMonth.getDay(); + + for (i = dayOfWeek - 1; i >= 0; i--) { + daysInLastMonth--; + this.days[i].updateDay(true, lastYear, lastMonth, daysInLastMonth); + } + + for (i = 0; i < this.daysInCurrentMonth; i++) { + var dpDay = this.days[dayOfWeek + i]; + dpDay.updateDay(false, year, month, i); + if ((this.selectedDay.getFullYear() === year) && + (this.selectedDay.getMonth() === month) && + (this.selectedDay.getDate() === i)) { + dpDay.domNode.setAttribute('aria-selected', 'true'); + } + } + + var remainingButtons = 42 - this.daysInCurrentMonth - dayOfWeek; + + if (remainingButtons >= 7) { + remainingButtons = remainingButtons - 7; + this.hideLastRow(); + } + else { + this.showLastRow(); + } + + for (i = 0; i < remainingButtons; i++) { + this.days[dayOfWeek + this.daysInCurrentMonth + i].updateDay(true, nextYear, nextMonth, i); + } + +}; + +DatePicker.prototype.onFirstRow = function () { + var cd = this.currentDay; + var flag = cd.row === 0; + flag = flag || ((cd.row === 1) && this.days[cd.index - 7].isDisabled()); + return flag; +}; + +DatePicker.prototype.onLastRow = function () { + var cd = this.currentDay; + var flag = cd.row === 5; + flag = flag || ((cd.row === 3) && this.days[cd.index + 7].isDisabled()); + flag = flag || ((cd.row === 4) && this.days[cd.index + 7].isDisabled()); + return flag; +}; + + +// If after updating the grid the current day is on a disabled button move it to an enabled button in the same column +DatePicker.prototype.adjustCurrentDay = function (onFirstRow, onLastRow) { + var cd = this.currentDay; + + if (typeof onFirstRow !== 'boolean') { + onFirstRow = false; + } + + if (typeof onLastRow !== 'boolean') { + onLastRow = false; + } + + if (cd.isDisabled()) { + if (cd.row === 0) { + this.day = this.days[cd.index + 7].day; + } + else { + if (this.days[cd.index - 7].isDisabled()) { + this.day = this.days[cd.index - 14].day; + } + else { + this.day = this.days[cd.index - 7].day; + } + } + } + else { + if (onFirstRow && (cd.row === 1) && (!this.days[cd.index - 7].isDisabled())) { + this.day = this.days[cd.index - 7].day; + } + else { + if (onLastRow && + ((cd.row === 3) || (cd.row === 4)) && + (!this.days[cd.index + 7].isDisabled())) { + this.day = this.days[cd.index + 7].day; + } + else { + this.day = cd.day; + } + } + } +}; + +DatePicker.prototype.hideLastRow = function () { + this.lastRowNode.style.visibility = 'hidden'; +}; + +DatePicker.prototype.showLastRow = function () { + this.lastRowNode.style.visibility = 'visible'; +}; + +DatePicker.prototype.setFocusDay = function (flag) { + + if (typeof flag !== 'boolean') { + flag = true; + } + + this.dateInput.setMessage(''); + + function checkDay (d) { + d.domNode.setAttribute('tabindex', '-1'); + if ((d.day == this.day) && + (d.month == this.month)) { + this.currentDay = d; + d.domNode.setAttribute('tabindex', '0'); + + if (flag) { + if (!this.hasFocusFlag) { + this.dateInput.setMessage('Use the cursor keys to navigate the date picker grid.'); + } + + this.hasFocusFlag = true; + d.domNode.focus(); + } + } + } + + this.days.forEach(checkDay.bind(this)); +}; + +DatePicker.prototype.updateDate = function (year, month, day) { + this.year = year; + this.month = month; + this.day = day; +}; + +DatePicker.prototype.getDaysInLastMonth = function (year, month) { + + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } + + var lastMonth = month - 1; + var lastYear = year; + if (lastMonth < 0) { + lastMonth = 11; + lastYear = year - 1; + } + + return this.getDaysInMonth(lastYear, lastMonth); + +}; + +DatePicker.prototype.getDaysInMonth = function (year, month) { + + if (typeof year !== 'number') { + year = this.year; + } + + if (typeof month !== 'number') { + month = this.month; + } + + switch (month) { + + case 0: + case 2: + case 4: + case 6: + case 7: + case 9: + case 11: + return 31; + + case 1: + return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); + + case 3: + case 5: + case 8: + case 10: + return 30; + + default: + break; + + } + + return -1; + +}; + +DatePicker.prototype.show = function () { + + this.dialogNode.style.display = 'block'; + this.dialogNode.style.zIndex = 2; + + this.dateInput.setAriaExpanded(true); + this.getDateInput(); + this.updateGrid(); + + return this.hasFocusFlag; +}; + +DatePicker.prototype.isOpen = function () { + return this.dateInput.getAriaExpanded(); +}; + +DatePicker.prototype.hide = function (ignore) { + + if (typeof ignore !== 'boolean') { + ignore = true; + } + + this.dialogNode.style.display = 'none'; + this.dateInput.setAriaExpanded(false); + + this.hasFocusFlag = false; + this.dateInput.ignoreFocusEvent = ignore; + this.dateInput.setFocus(); +}; + +DatePicker.prototype.handleBackgroundMouseDown = function (event) { + console.log('[DatePicker][handleBackgroundMouseDown]'); + if (!this.inputNode.parentNode.contains(event.target) && + !this.dialogNode.contains(event.target)) { + + this.isMouseDownOnBackground = true; + console.log('[DatePicker][handleBackgroundMouseDown][isMouseDownOnBackground]: ' + this.isMouseDownOnBackground); + + if (this.isOpen()) { + this.hide(); + event.stopPropagation(); + event.preventDefault(); + } + } +}; + +DatePicker.prototype.handleBackgroundMouseUp = function (event) { + console.log('[DatePicker][handleBackgroundMouseUp]'); + this.isMouseDownOnBackground = false; +}; + + +DatePicker.prototype.handleOkButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ENTER: + case this.keyCode.SPACE: + + this.setTextboxDate(); + + this.hide(); + flag = true; + break; + + case this.keyCode.TAB: + if (!event.shiftKey) { + this.prevYearNode.focus(); + flag = true; + } + break; + + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + default: + break; + + } + break; + + case 'click': + this.setTextboxDate(); + this.hide(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.handleCancelButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.hide(); + flag = true; + break; + + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + default: + break; + + } + break; + + case 'click': + this.hide(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.handleNextYearButton = function (event) { + var flag = false; + var onFirstRow = this.onFirstRow(); + var onLastRow = this.onLastRow(); + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.moveToNextYear(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } + this.moveToNextYear(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.handlePreviousYearButton = function (event) { + var flag = false; + var onFirstRow = this.onFirstRow(); + var onLastRow = this.onLastRow(); + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.moveToPreviousYear(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + flag = true; + break; + + case this.keyCode.TAB: + if (event.shiftKey) { + this.okButtonNode.focus(); + flag = true; + } + break; + + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + default: + break; + } + + break; + + case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } + this.moveToPreviousYear(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.handleNextMonthButton = function (event) { + var flag = false; + var onFirstRow = this.onFirstRow(); + var onLastRow = this.onLastRow(); + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.moveToNextMonth(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } + this.moveToNextMonth(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.handlePreviousMonthButton = function (event) { + var flag = false; + var onFirstRow = this.onFirstRow(); + var onLastRow = this.onLastRow(); + + switch (event.type) { + + case 'keydown': + + switch (event.keyCode) { + case this.keyCode.ESC: + this.hide(); + flag = true; + break; + + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.moveToPreviousMonth(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } + this.moveToPreviousMonth(); + this.adjustCurrentDay(onFirstRow, onLastRow); + this.setFocusDay(false); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePicker.prototype.moveToNextYear = function () { + this.year++; + this.updateGrid(); +}; + +DatePicker.prototype.moveToPreviousYear = function () { + this.year--; + this.updateGrid(); +}; + +DatePicker.prototype.moveToPreviousMonth = function () { + this.month--; + if (this.month < 0) { + this.month = 11; + this.year--; + } + this.updateGrid(); +}; + +DatePicker.prototype.moveToNextMonth = function () { + this.month++; + if (this.month > 11) { + this.month = 0; + this.year++; + } + this.updateGrid(); +}; + +DatePicker.prototype.moveToDay = function (day, month, year) { + this.day = day; + this.month = month; + this.year = year; + this.updateGrid(); + this.setFocusDay(); +}; + +DatePicker.prototype.moveFocusToNextDay = function () { + + this.day++; + if (this.daysInCurrentMonth <= this.day) { + this.day = 0; + this.moveToNextMonth(); + } + this.setFocusDay(); +}; + +DatePicker.prototype.moveFocusToNextWeek = function () { + + this.day += 7; + if (this.daysInCurrentMonth <= this.day) { + this.day = this.day - this.daysInCurrentMonth; + this.moveToNextMonth(); + } + this.setFocusDay(); +}; + +DatePicker.prototype.moveFocusToPreviousDay = function () { + + this.day--; + if (this.day < 0) { + this.moveToPreviousMonth(); + this.day = this.daysInCurrentMonth - 1; + } + this.setFocusDay(); +}; + +DatePicker.prototype.moveFocusToPreviousWeek = function () { + this.day -= 7; + if (this.day < 0) { + this.day = this.daysInLastMonth + this.day; + this.moveToPreviousMonth(); + } + this.setFocusDay(); +}; + +DatePicker.prototype.moveFocusToFirstDayOfWeek = function () { + + this.day = this.day - this.currentDay.column; + + if (this.day < 0) { + this.day = this.daysInLastMonth + this.day; + this.moveToPreviousMonth(); + } + this.setFocusDay(); + +}; + +DatePicker.prototype.moveFocusToLastDayOfWeek = function () { + + this.day = this.day + (6 - this.currentDay.column); + + if (this.daysInCurrentMonth <= this.day) { + this.day = this.day - this.daysInCurrentMonth; + this.moveToNextMonth(); + } + this.setFocusDay(); + +}; + +DatePicker.prototype.setTextboxDate = function () { + this.dateInput.setDate(this.month, this.day, this.year); +}; + +DatePicker.prototype.getDateInput = function () { + + var parts = this.dateInput.getDate().split('/'); + + if ((parts.length === 3) && + Number.isInteger(parseInt(parts[0])) && + Number.isInteger(parseInt(parts[1])) && + Number.isInteger(parseInt(parts[2]))) { + this.month = parseInt(parts[0]) - 1; + this.day = parseInt(parts[1]) - 1; + this.year = parseInt(parts[2]); + } + else { + // If not a valid date (MM/DD/YY) initialize with todays date + var date = new Date(); + + this.year = date.getFullYear(); + this.month = date.getMonth(); + this.day = date.getDate() - 1; + } + + this.daysInCurrentMonth = this.getDaysInMonth(); + this.daysInLastMonth = this.getDaysInLastMonth(); + + this.selectedDay = new Date(this.year, this.month, this.day); + +}; + +DatePicker.prototype.getDateForButtonLabel = function () { + this.selectedDay = new Date(this.year, this.month, this.day + 1); + var label = this.dayLabels[this.selectedDay.getDay()]; + label += ' ' + this.monthLabels[this.selectedDay.getMonth()]; + label += ' ' + (this.selectedDay.getDate()); + label += ', ' + this.selectedDay.getFullYear(); + return label; +}; + diff --git a/examples/combobox/aria1.1pattern/js/datepickerDay.js b/examples/combobox/aria1.1pattern/js/datepickerDay.js new file mode 100644 index 0000000000..aaf6c5d7a9 --- /dev/null +++ b/examples/combobox/aria1.1pattern/js/datepickerDay.js @@ -0,0 +1,175 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: datepickerDay.js +*/ + +var DatePickerDay = function (domNode, datepicker, index, row, column) { + + this.index = index; + this.row = row; + this.column = column; + + this.year = -1; + this.month = -1; + this.day = -1; + + this.domNode = domNode; + this.datepicker = datepicker; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'ENTER': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +DatePickerDay.prototype.init = function () { + this.domNode.setAttribute('tabindex', '-1'); + this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + + this.domNode.innerHTML = '-1'; + +}; + +DatePickerDay.prototype.isDisabled = function () { + return this.domNode.classList.contains('disabled'); +}; + +DatePickerDay.prototype.updateDay = function (disable, year, month, day) { + + if (disable) { + this.domNode.classList.add('disabled'); + } + else { + this.domNode.classList.remove('disabled'); + } + + this.year = year; + this.month = month; + this.day = day; + + this.domNode.innerHTML = day + 1; + this.domNode.setAttribute('tabindex', '-1'); + this.domNode.removeAttribute('aria-selected'); + +}; + +DatePickerDay.prototype.handleKeyDown = function (event) { + var flag = false; + var onFirstRow = this.datepicker.onFirstRow(); + var onLastRow = this.datepicker.onLastRow(); + + switch (event.keyCode) { + + case this.keyCode.ESC: + this.datepicker.hide(); + break; + + case this.keyCode.TAB: + this.datepicker.cancelButtonNode.focus(); + if (event.shiftKey) { + this.datepicker.nextYearNode.focus(); + } + flag = true; + break; + + case this.keyCode.ENTER: + case this.keyCode.SPACE: + this.datepicker.setTextboxDate(); + this.datepicker.hide(); + flag = true; + break; + + case this.keyCode.RIGHT: + this.datepicker.moveFocusToNextDay(); + flag = true; + break; + + case this.keyCode.LEFT: + this.datepicker.moveFocusToPreviousDay(); + flag = true; + break; + + case this.keyCode.DOWN: + this.datepicker.moveFocusToNextWeek(); + flag = true; + break; + + case this.keyCode.UP: + this.datepicker.moveFocusToPreviousWeek(); + flag = true; + break; + + case this.keyCode.PAGEUP: + if (event.shiftKey) { + this.datepicker.moveToPreviousYear(); + } + else { + this.datepicker.moveToPreviousMonth(); + } + this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); + this.datepicker.setFocusDay(); + flag = true; + break; + + case this.keyCode.PAGEDOWN: + if (event.shiftKey) { + this.datepicker.moveToNextYear(); + } + else { + this.datepicker.moveToNextMonth(); + } + this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); + this.datepicker.setFocusDay(); + flag = true; + break; + + case this.keyCode.HOME: + this.datepicker.moveFocusToFirstDayOfWeek(); + flag = true; + break; + + case this.keyCode.END: + this.datepicker.moveFocusToLastDayOfWeek(); + flag = true; + break; + + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } + +}; + +DatePickerDay.prototype.handleMouseDown = function (event) { + this.datepicker.day = this.day; + + if (this.isDisabled()) { + this.datepicker.moveToDay(this.day, this.month, this.year); + } + else { + if (!this.datepicker.dateInput.hasFocus()) { + this.datepicker.dateInput.ignoreBlurEvent = true; + } + this.datepicker.setTextboxDate(); + this.datepicker.hide(); + } + + event.stopPropagation(); + event.preventDefault(); + +}; diff --git a/examples/datepicker/css/datepicker-spinbuttons.css b/examples/datepicker/css/datepicker-spinbuttons.css deleted file mode 100644 index e361a7c1c1..0000000000 --- a/examples/datepicker/css/datepicker-spinbuttons.css +++ /dev/null @@ -1,85 +0,0 @@ -.datepicker-spinbuttons { - margin-top: 1em; -} - -.datepicker-spinbuttons .day { - width: 2em; -} - -.datepicker-spinbuttons .month { - width: 6em; -} - -.datepicker-spinbuttons .year { - width: 3em; -} - -.datepicker-spinbuttons .spinbutton { - float: left; - text-align: center; -} - -.datepicker-spinbuttons .spinbutton:first-child { - border-left: 4px; -} - -.datepicker-spinbuttons .spinbutton:last-child { - border-right: 4px; -} - -.datepicker-spinbuttons .spinbutton .previous, -.datepicker-spinbuttons .spinbutton .next { - color: #666; -} - -.datepicker-spinbuttons .spinbutton:focus { - outline: 2px solid #005A9C; -} - -.datepicker-spinbuttons .spinbutton:focus, -.datepicker-spinbuttons .spinbutton:hover { - color: #444; - background-color: #EEE; -} - -.datepicker-spinbuttons .spinbutton:focus .value, -.datepicker-spinbuttons .spinbutton:hover .value { - background-color: #FFF; - color: black; -} - -.datepicker-spinbuttons .spinbutton .previous { - border-bottom: 1px solid black; -} - -.datepicker-spinbuttons .spinbutton .next { - border-top: 1px solid black; -} - -.datepicker-spinbuttons .spinbutton .decrease svg polygon, -.datepicker-spinbuttons .spinbutton .increase svg polygon { - fill: #333; - stroke-width: 3px; - stroke: transparent; -} - -.datepicker-spinbuttons .spinbutton .decrease { - position: relative; - top: 4px; -} - -.datepicker-spinbuttons .spinbutton .decrease:hover svg polygon, -.datepicker-spinbuttons .spinbutton .increase:hover svg polygon { - fill: #005A9C; - stroke: #005A9C; -} - -.datepicker-spinbuttons .spinbutton:focus svg polygon { - fill: #005A9C; - stroke: #005A9C; -} - -div[role="separator"] { - clear: both; -} - diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html deleted file mode 100644 index e3c7d99216..0000000000 --- a/examples/datepicker/datepicker-spinbuttons.html +++ /dev/null @@ -1,315 +0,0 @@ - - - - -Date Picker Example Using Spin Buttons | WAI-ARIA Authoring Practices 1.1 - - - - - - - - - - - - - - - - -
    -

    Date Picker Example Using Spin Buttons

    -

    - NOTE: This example page is work in progress. - Please provide feedback in - issue 34. -

    -

    - The date picker is an example of the Spin Button design pattern to define a date. - In this example the year, month and date have separate spin buttons. -

    -
    -

    Example

    - - -
    - -
    - -

    Date

    - -
    -
    - - - -
    - -
    1
    - -
    - - - -
    -
    - -
    -
    - - - -
    - -
    June
    - -
    - - - -
    -
    - -
    -
    - - - -
    - -
    2019
    - -
    - - - -
    -
    -
    -
    - -
    - -
    -

    Accessibility Features

    -

    There are three main featues of the spin button date picker example:

    -
      -
    • Day, month and year are separate spin buttons
    • -
    • The increase and decrease events are the div element containing the impages of the up and odwn arrows, making the effective target area larger for changing the value of a spin button.
    • -
    • Focusing a spin button increases the size of the increase and decrease bttons to make the element with focus easier to find and to track focus changes.
    • -
    -
    -
    -

    Keyboard Support

    - -
    -

    Spin Button (Day, Month and Year)

    -

    The spin button for changing the day, month and year provides the following keyboard support described in the spin button design pattern.

    -
    buttonbutton + button + +
      +
    • The button element has the default role of button.
    • +
    • The accessible name comes from the aria-label attribute.
    • +
    +
    aria-controls=IDREFbutton - button + Provide a reference for screen reader to navigate to the date grid.
    aria-label=Stringbutton -
      -
    • The default role of a button element is button.
    • -
    • The accessible name comes from the aria-label attribute.
    • -
    + Defines the accessible name of the button (e.g. "Next Year").
    aria-controls=IDREFbutton - Provide a reference for screen reader to navigate to the reference content. -
    aria-label=Stringbutton - Defines the accessible name of the button (e.g. "Next Year"). -
    status @@ -661,7 +739,7 @@

    Buttons (Next/Previous Month and Year)

    -

    Grid (Days in Month)

    +

    Date Picker Dialog: Date Grid

    @@ -672,7 +750,7 @@

    Grid (Days in Month)

    - + - - - - + + + - - - - - - + @@ -722,9 +791,19 @@

    Grid (Days in Month)

    th + + + + + + @@ -732,7 +811,7 @@

    Grid (Days in Month)

    td @@ -743,8 +822,8 @@

    Grid (Days in Month)

    @@ -793,11 +872,15 @@

    Javascript and CSS Source Code

    diff --git a/examples/datepicker/js/dateInput.js b/examples/datepicker/menubutton/js/menubutton.js similarity index 64% rename from examples/datepicker/js/dateInput.js rename to examples/datepicker/menubutton/js/menubutton.js index a46e25bf9d..9cc8d75910 100644 --- a/examples/datepicker/js/dateInput.js +++ b/examples/datepicker/menubutton/js/menubutton.js @@ -1,5 +1,4 @@ -var DateInput = function (comboboxNode, inputNode, buttonNode, messageNode, datepicker) { - this.comboboxNode = comboboxNode; +var MenuButtonInput = function (inputNode, buttonNode, messageNode, datepicker) { this.inputNode = inputNode; this.buttonNode = buttonNode; this.messageNode = messageNode; @@ -29,11 +28,10 @@ var DateInput = function (comboboxNode, inputNode, buttonNode, messageNode, date }); }; -DateInput.prototype.init = function () { +MenuButtonInput.prototype.init = function () { this.inputNode.addEventListener('keydown', this.handleKeyDown.bind(this)); this.inputNode.addEventListener('focus', this.handleFocus.bind(this)); this.inputNode.addEventListener('blur', this.handleBlur.bind(this)); - this.inputNode.addEventListener('click', this.handleClick.bind(this)); this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); this.buttonNode.addEventListener('touchstart', this.handleTouchStart.bind(this)); @@ -51,7 +49,7 @@ DateInput.prototype.init = function () { this.setMessage(''); }; -DateInput.prototype.handleKeyDown = function (event) { +MenuButtonInput.prototype.handleKeyDown = function (event) { var flag = false; switch (event.keyCode) { @@ -83,9 +81,7 @@ DateInput.prototype.handleKeyDown = function (event) { } }; -DateInput.prototype.handleTouchStart = function (event) { - - console.log('[handleTouchStart][length]: ' + event.targetTouches.length); +MenuButtonInput.prototype.handleTouchStart = function (event) { if (event.targetTouches.length === 1) { console.log('[handleTouchStart][tagName]: ' + event.targetTouches[0].target.tagName); @@ -101,10 +97,9 @@ DateInput.prototype.handleTouchStart = function (event) { } }; -DateInput.prototype.handleFocus = function () { - console.log('[DateInput][handleFocus]') +MenuButtonInput.prototype.handleFocus = function () { + console.log('[MenuButtonInput][handleFocus]') if (!this.ignoreFocusEvent && this.isCollapsed()) { - this.datepicker.show(); this.setMessage('Use the down arrow key to move focus to the datepicker grid.'); } this.showDownArrow(); @@ -116,9 +111,8 @@ DateInput.prototype.handleFocus = function () { }; -DateInput.prototype.handleBlur = function () { +MenuButtonInput.prototype.handleBlur = function () { if (!this.ignoreBlurEvent) { - this.datepicker.hide(false); this.setMessage(''); } this.hideDownArrow(); @@ -128,8 +122,8 @@ DateInput.prototype.handleBlur = function () { this.ignoreBlurEvent = false; }; -DateInput.prototype.handleClick = function (event) { - console.log('[DateInput][handleClick]: ' + event.target.tagName); +MenuButtonInput.prototype.handleClick = function (event) { + console.log('[MenuButtonInput][handleClick]: ' + event.target.tagName); if (this.lastEventFocus) { this.lastEventFocus = false; return; @@ -147,15 +141,18 @@ DateInput.prototype.handleClick = function (event) { }; -DateInput.prototype.handleButtonClick = function (event) { +MenuButtonInput.prototype.handleButtonClick = function (event) { this.ignoreBlurEvent = true; this.datepicker.show(); this.datepicker.setFocusDay(); - event.stopPropagation(); - event.preventDefault(); + + if (event) { + event.stopPropagation(); + event.preventDefault(); + } }; -DateInput.prototype.handleButtonKeyDown = function (event) { +MenuButtonInput.prototype.handleButtonKeyDown = function (event) { var flag = false; @@ -175,55 +172,79 @@ DateInput.prototype.handleButtonKeyDown = function (event) { } }; -DateInput.prototype.focus = function () { +MenuButtonInput.prototype.focus = function () { this.inputNode.focus(); }; -DateInput.prototype.setAriaExpanded = function (flag) { +MenuButtonInput.prototype.setAriaExpanded = function (flag) { if (flag) { - this.comboboxNode.setAttribute('aria-expanded', 'true'); + if (this.comboboxNode) { + this.comboboxNode.setAttribute('aria-expanded', 'true'); + } this.buttonNode.setAttribute('aria-expanded', 'true'); } else { - this.comboboxNode.setAttribute('aria-expanded', 'false'); + if (this.comboboxNode) { + this.comboboxNode.setAttribute('aria-expanded', 'false'); + } this.buttonNode.setAttribute('aria-expanded', 'false'); } }; -DateInput.prototype.getAriaExpanded = function () { - return this.comboboxNode.getAttribute('aria-expanded') === 'true'; +MenuButtonInput.prototype.getAriaExpanded = function () { + if (this.comboboxNode) { + return this.comboboxNode.getAttribute('aria-expanded') === 'true'; + } + return this.buttonNode.getAttribute('aria-expanded') === 'true'; }; -DateInput.prototype.isCollapsed = function () { +MenuButtonInput.prototype.isCollapsed = function () { return this.inputNode.getAttribute('aria-expanded') !== 'true'; }; -DateInput.prototype.setDate = function (month, day, year) { +MenuButtonInput.prototype.setDate = function (month, day, year) { this.inputNode.value = (month + 1) + '/' + (day + 1) + '/' + year; }; -DateInput.prototype.getDate = function () { +MenuButtonInput.prototype.getDate = function () { return this.inputNode.value; }; -DateInput.prototype.setMessage = function (str) { +MenuButtonInput.prototype.setMessage = function (str) { return this.messageNode.textContent = str; }; -DateInput.prototype.hasFocus = function () { +MenuButtonInput.prototype.hasFocus = function () { return this.hasFocusflag; }; -DateInput.prototype.showDownArrow = function () { +MenuButtonInput.prototype.showDownArrow = function () { if (this.imageNode) { this.imageNode.style.visibility = 'visible'; } }; -DateInput.prototype.hideDownArrow = function () { +MenuButtonInput.prototype.hideDownArrow = function () { if (this.imageNode) { this.imageNode.style.visibility = 'hidden'; } }; + +// Initialize menu button date picker + +window.addEventListener('load' , function () { + + var datePickers = document.querySelectorAll('.datepicker'); + + datePickers.forEach(function (dp) { + var dpInput = dp.querySelector('input'); + var dpButton = dp.querySelector('button'); + var dpMessage = dp.querySelector('.message'); + var dpDialog = dp.querySelector('[role=dialog]'); + var datePicker = new DatePicker(null, dpInput, dpButton, dpDialog, dpMessage); + datePicker.init(); + }); + +}); From 482d0e6011cfb61d1b4b4ce7994df08978efb4f6 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 2 May 2019 17:34:45 -0500 Subject: [PATCH 042/137] updated documentation, fied bugs and added a menu button date picker --- .../{common => }/css/datepicker.css | 13 +- .../index.html => datepicker-combobox.html} | 67 ++--- .../index.html => datepicker-menubutton.html} | 212 ++++++--------- .../datepicker/datepicker-spinbuttons.html | 257 ++++++++++++++++++ .../{common => }/images/down-arrow-gray.png | Bin examples/datepicker/images/up-arrow-gray.png | Bin 0 -> 304 bytes .../combobox.js => js/datepicker-combobox.js} | 29 +- .../datepicker-menubutton.js} | 59 +--- .../datepicker/js/datepicker-spinbuttons.js | 142 ++++++++++ .../datepicker/{common => }/js/datepicker.js | 10 +- .../{common => }/js/datepickerDay.js | 7 + .../datepicker/menubutton/css/datepicker.css | 190 ------------- .../menubutton/images/down-arrow-gray.png | Bin 1325 -> 0 bytes ...daetpicker-1.js => daetpicker-combobox.js} | 9 +- 14 files changed, 564 insertions(+), 431 deletions(-) rename examples/datepicker/{common => }/css/datepicker.css (92%) rename examples/datepicker/{combobox/index.html => datepicker-combobox.html} (94%) rename examples/datepicker/{menubutton/index.html => datepicker-menubutton.html} (83%) create mode 100644 examples/datepicker/datepicker-spinbuttons.html rename examples/datepicker/{common => }/images/down-arrow-gray.png (100%) create mode 100644 examples/datepicker/images/up-arrow-gray.png rename examples/datepicker/{combobox/js/combobox.js => js/datepicker-combobox.js} (91%) rename examples/datepicker/{menubutton/js/menubutton.js => js/datepicker-menubutton.js} (80%) create mode 100644 examples/datepicker/js/datepicker-spinbuttons.js rename examples/datepicker/{common => }/js/datepicker.js (98%) rename examples/datepicker/{common => }/js/datepickerDay.js (94%) delete mode 100644 examples/datepicker/menubutton/css/datepicker.css delete mode 100644 examples/datepicker/menubutton/images/down-arrow-gray.png rename test/tests/{daetpicker-1.js => daetpicker-combobox.js} (72%) diff --git a/examples/datepicker/common/css/datepicker.css b/examples/datepicker/css/datepicker.css similarity index 92% rename from examples/datepicker/common/css/datepicker.css rename to examples/datepicker/css/datepicker.css index f117b63d02..d6e954fa2e 100644 --- a/examples/datepicker/common/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -2,10 +2,17 @@ margin-top: 1em; } -.datepicker .downarrow { +.datepicker button[aria-expanded] + span.arrow { content: url('../images/down-arrow-gray.png'); position: relative; - left: -20px; + left: -60px; +} + +.datepicker button[aria-expanded=true] + span.arrow { + content: url('../images/up-arrow-gray.png'); + position: relative; + top: 3px; + left: -60px; } .datepicker button.icon { @@ -13,7 +20,7 @@ text-align: left; background-color: white; position: relative; - left: -20px; + left: -2px; top: 4px; } diff --git a/examples/datepicker/combobox/index.html b/examples/datepicker/datepicker-combobox.html similarity index 94% rename from examples/datepicker/combobox/index.html rename to examples/datepicker/datepicker-combobox.html index 9734b26788..0db28ebaed 100644 --- a/examples/datepicker/combobox/index.html +++ b/examples/datepicker/datepicker-combobox.html @@ -6,17 +6,17 @@ - + - - - + + + - - - - + + + + @@ -279,7 +280,7 @@

    Textbox

    -

    Calendar Button

    +

    Change Date Button

    grid @@ -680,30 +758,21 @@

    Grid (Days in Month)

      -
    • Identifies the element that serves as the grid widget container.
    • +
    • Identifies the table element serving as the grid widget container.
    • +
    • The accessible name comes from the aria-labelledby attribute.
    • Since the grid role is applied to a table element, the row, colheader, and gridcell roles do not need to be specified because they are implied by tr, th, and td tags.
    aira-labelledby=IDREFtable + aira-labelledby=IDREFtable
    • Define the accessible name for the grid using current month and year.
    -
    aira-label=Stringth -
      -
    • Define the accessible name as a day of week for column headers.
    • -
    -
    row - The default role for a th[scope="col"] element is columnheader. + The default role for a th[scope="col"] element is columnheader.
    aira-label=Stringth +
      +
    • Define the accessible name as a day of week for column headers.
    • +
    +
    gridcell - Since the table element has a grid role, all child td elements will have the role of gridcell. + Since the table element has a grid role, all descendant td elements will have the role of gridcell.
      -
    • The button element is used to identify the dates in calendar grid and are a child of the gridcell.
    • -
    • Accessible name for the button is the date which is defined by the text content of the button element.
    • +
    • The button element is used to identify the dates in calendar grid and are a child of the gridcell.
    • +
    • Accessible name for the button is the date which is defined by the text content of the button element.
    Down Arrow
      -
    • Open the calendar dialog.
    • +
    • Open the date picker dialog.
    • Move focus to current date.
    @@ -292,8 +293,8 @@

    Calendar Button

    @@ -350,7 +351,7 @@

    Date Picker Dialog: Calendar Buttons

    @@ -404,7 +405,7 @@

    Date Picker Dialog: Date Grid

    @@ -413,7 +414,7 @@

    Date Picker Dialog: Date Grid

    @@ -422,7 +423,7 @@

    Date Picker Dialog: Date Grid

    @@ -431,7 +432,7 @@

    Date Picker Dialog: Date Grid

    @@ -471,8 +472,8 @@

    Date Picker Buttons (OK and Cancel)

    @@ -574,7 +575,7 @@

    Textbox

    @@ -611,7 +612,7 @@

    Textbox

    -

    Calendar Button

    +

    Change Date Button

    Space,
    Return
      -
    • Toggles calendar dialog.
    • -
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    • +
    • Toggles date picker dialog.
    • +
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the date picker dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    Space,
    Return
    - Change the month and/or year for selecting a date from the calendar gird. + Change the month and/or year for selecting a date from the grid of dates.
    PageUp
      -
    • Change the calendar to the previous month.
    • +
    • Change the grid of dates to the previous month.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Shift+
    PageUp
      -
    • Change the calendar to the previous Year.
    • +
    • Change the grid of dates to the previous Year.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    PageDown
      -
    • Change the calendar to the next month.
    • +
    • Change the grid of dates to the next month.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Shift+
    PageDown
      -
    • Change the calendar to the next Year.
    • +
    • Change the grid of dates to the next Year.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Space,
    Return
      -
    • The Cancel button closes the calendar dialog, moves focus to date input, does not update date in date input.
    • -
    • The OK button closes the calendar dialog, moves focus to date input, does update date in date input.
    • +
    • The Cancel button closes the date picker dialog, moves focus to date input, does not update date in date input.
    • +
    • The OK button closes the date picker dialog, moves focus to date input, does update date in date input.
    • The input element with no type attribute has the default role of textbox.
    • -
    • Accessible name comes from a label element using the for attribute and has the same name as the combobox and calendar button.
    • +
    • Accessible name comes from a label element using the for attribute and has the same name as the combobox and change date button.
    @@ -855,7 +856,7 @@

    Date Picker Dialog: Date Grid

    @@ -905,13 +906,13 @@

    Javascript and CSS Source Code

    diff --git a/examples/datepicker/menubutton/index.html b/examples/datepicker/datepicker-menubutton.html similarity index 83% rename from examples/datepicker/menubutton/index.html rename to examples/datepicker/datepicker-menubutton.html index 8b54a7a11a..1c52aa1f08 100644 --- a/examples/datepicker/menubutton/index.html +++ b/examples/datepicker/datepicker-menubutton.html @@ -6,17 +6,17 @@ - + - - - + + + - - - - + + + + @@ -258,7 +267,7 @@

    Textbox

    -

    Calendar Button

    +

    Change Date Button

      -
    • The button element is used to identify the dates in calendar grid and are a child of the gridcell.
    • +
    • The button element is used to represent each date in the grid and are a child of the gridcell.
    • Accessible name for the button is the date which is defined by the text content of the button element.
    Down Arrow
      -
    • Open the calendar dialog.
    • +
    • Open the date picker dialog.
    • Move focus to current date.
    @@ -268,11 +277,11 @@

    Calendar Button

    - - + @@ -329,7 +338,7 @@

    Date Picker Dialog: Calendar Buttons

    @@ -383,7 +392,7 @@

    Date Picker Dialog: Date Grid

    @@ -392,7 +401,7 @@

    Date Picker Dialog: Date Grid

    @@ -401,7 +410,7 @@

    Date Picker Dialog: Date Grid

    @@ -410,7 +419,7 @@

    Date Picker Dialog: Date Grid

    @@ -450,8 +459,8 @@

    Date Picker Buttons (OK and Cancel)

    @@ -463,77 +472,6 @@

    Date Picker Buttons (OK and Cancel)

    Role, Property, State, and Tabindex Attributes

    -
    -

    Combobox

    -
    Space,
    Return
    -
      -
    • Toggles calendar dialog.
    • -
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the calendar dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    • +
    space, return and Down Arrow +
      +
    • Open the date picker dialog.
    • +
    • Move focus to current date.
    Space,
    Return
    - Change the month and/or year for selecting a date from the calendar gird. + Change the month and/or year for selecting a date from the grid or dates.
    PageUp
      -
    • Change the calendar to the previous month.
    • +
    • Change the gird of dates to the previous month.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Shift+
    PageUp
      -
    • Change the calendar to the previous Year.
    • +
    • Change the grid of dates to the previous Year.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    PageDown
      -
    • Change the calendar to the next month.
    • +
    • Change the grid of dates to the next month.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Shift+
    PageDown
      -
    • Change the calendar to the next Year.
    • +
    • Change the grid of dates to the next Year.
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    Space,
    Return
      -
    • The Cancel button closes the calendar dialog, moves focus to date input, does not update date in date input.
    • -
    • The OK button closes the calendar dialog, moves focus to date input, does update date in date input.
    • +
    • The Cancel button closes the date picker dialog, moves focus to date input, does not update date in date input.
    • +
    • The OK button closes the date picker dialog, moves focus to date input, does update date in date input.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    role=comboboxdiv -
      -
    • Identifies the texbox as having a combobox behavior.
    • -
    • Accessible name is defined using the aria-label attribute.
    • -
    -
    aria-label=Datediv - Defines the accessible name of the combobox (e.g. "Date"). -
    aria-haspopup=dialogdiv - Identifies the combobox opens a dialog box. -
    aria-expanded=falsediv - aria-expanded=false when the date picker dialog box is closed. -
    aria-expanded=truediv - aria-expanded=true when the date picker dialog box is open. -
    aria-owns=IDREFdiv -
      -
    • Refers to the element that serves as the dialog box.
    • -
    • Tells browsers to arrange the screen reader reading order so the dialog box, when it is visible, immediately follows the other elements inside the combobox, regardless of where the dialog element is in the DOM.
    • -
    -
    -
    - -

    Textbox

    @@ -565,20 +503,12 @@

    Textbox

    aria-autocomplete=none the textbox does not support autocompletion when text is entered. - - - - - -
    aria-controls=IDREFinput - Provides a reference to the screen reader for direct navigation to the dialog box. -
    -

    Calendar Button

    +

    Change Date Button

    @@ -601,26 +531,26 @@

    Calendar Button

    - + - + - + - + - + @@ -631,6 +561,22 @@

    Calendar Button

    + + + + + + + + + + + +
    tabindex=-1aria-label=String button - Removes the button from the tab order of the page. +
      +
    • The initial value of accessible name is Change Date.
    • +
    • When user selected date, the accessible name will update to current date.
    • +
    aria-label=Stringaria-haspopup=dialog button -
      -
    • Initial value of accessible name is Pick a Date.
    • -
    • When user selected date, the accessible name will update to current date.
    • -
    + Identifies the button as a menu button and follws the meu button design pattern.
    aria-expanded=true button
    aria-controls=IDREFbutton + Provides a reference to a screen reader for direct navigation to the dialog box. +
    title=stringbutton + Provides a description of the relationship textbox and menu button, basically the button allows you to choose a new date using a date picker. +
    @@ -705,7 +651,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

    aria-controls=IDREF button
    aria-label=String button
    status @@ -877,9 +823,9 @@

    Javascript and CSS Source Code

  • Javascript:
  • diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html new file mode 100644 index 0000000000..bdcf2bf594 --- /dev/null +++ b/examples/datepicker/datepicker-spinbuttons.html @@ -0,0 +1,257 @@ + + + + +Date Picker Example Using Spin Buttons | WAI-ARIA Authoring Practices 1.1 + + + + + + + + + + + + + + + +
    +

    Date Picker Example Using Menu Button

    +

    + NOTE: This example page is work in progress. + Please provide feedback in + issue 34. +

    +

    + The date picker is an example of the Spin Button design pattern to define a date. + In this example the year, month and date have separate spin buttons. +

    +
    +

    Example

    + + +
    + +
    + +
    + January + + + + + + + + + + +
    + +
    + 1st + + + + + + + + + + +
    + +
    + 2019 + + + + + + + + + + +
    +
    +
    + +
    + +
    +

    Accessibility Features

    +

    There are three main featues of the spin button date picker example:

    +
      +
    • +
    • +
    • +
    +
    +
    +

    Keyboard Support

    + +
    +

    Spin Button (Font Size)

    +

    The spin button for changing font size provides the following keyboard support described in the spin button design pattern.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Down ArrowDecreases the font size of the text in the textarea by 1 point.
    Up ArrowIncreases the font size of the text in the textarea by 1 point.
    Page DownDecreases the font size of the text in the textarea by 5 points.
    Page UpIncreases the font size of the text in the textarea by 5 points.
    +
    + +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + +
    +

    Spin Button (Font Size)

    +

    The spin button for changing font size implements the following ARIA attributes described in the spin button design pattern.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    spinbuttondivIdentifies the div element as a spinbutton.
    aria-label="Font size in points"divDefines the accessible name for the spin button as "Font size in points".
    aria-valuenow="NUMBER_POINTS"div +
      +
    • Indicates the current value of the spin button.
    • +
    • Updated by JavaScript as users adjust the value of the spin button.
    • +
    +
    aria-valuetext="NUMBER_POINTS Points"div +
      +
    • Provides a more user friendly announcement of the current value for screen reader users.
    • +
    • NUMBER_POINTS is updated by JavaScript as the user adjusts the value of the spin button.
    • +
    • As users adjust the value, instead of hearing only a number, they will hear the current value followed by the word Points, e.g., 10 Points. +
    +
    aria-valuemin="8"divIndicates the minimum allowed value for the spin button, i.e., smallest supported font size.
    aria-valuemax="40"divIndicates the maximum allowed value for the spin button, i.e., largest supported font size.
    +
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    +
    + + + diff --git a/examples/datepicker/common/images/down-arrow-gray.png b/examples/datepicker/images/down-arrow-gray.png similarity index 100% rename from examples/datepicker/common/images/down-arrow-gray.png rename to examples/datepicker/images/down-arrow-gray.png diff --git a/examples/datepicker/images/up-arrow-gray.png b/examples/datepicker/images/up-arrow-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..1e893e1ad1b98df743aba7e6081bd7fcad2498f8 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=FbuAj}xMNVpLwc+}IyF+?MH>O@~Y7YC7+)BZ<9b9OeKzIkrx zk)S&cZ*n(o+92@J`W3HiPtTd@A#u<5u6nm@YbvYwC!6W7D(78M{uL%#8mru;BzQgC zuW{e|qc=+Dc4U9+o5SQeX@QP+&tr{MtGEhf@-OpiO-PlzWFq?boB_|J this.valueMax) { + value = this.valueMax; + } + + if (value < this.valueMin) { + value = this.valueMin; + } + + this.valueNow = value; + this.valueText = value + ' Point'; + + this.domNode.setAttribute('aria-valuenow', this.valueNow); + this.domNode.setAttribute('aria-valuetext', this.valueText); + + if (this.valueDomNode) { + this.valueDomNode.innerHTML = this.valueText; + } + + this.toolbar.changeFontSize(value); + +}; + +DateSpinButton.prototype.handleKeyDown = function (event) { + + var flag = false; + + switch (event.keyCode) { + case this.keyCode.DOWN: + this.setValue(this.valueNow - 1); + flag = true; + break; + + case this.keyCode.UP: + this.setValue(this.valueNow + 1); + flag = true; + break; + + case this.keyCode.PAGEDOWN: + this.setValue(this.valueNow - 5); + flag = true; + break; + + case this.keyCode.PAGEUP: + this.setValue(this.valueNow + 5); + flag = true; + break; + + case this.keyCode.HOME: + this.setValue(this.valueMin); + flag = true; + break; + + case this.keyCode.END: + this.setValue(this.valueMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + +}; + +DateSpinButton.prototype.handleIncreaseClick = function (event) { + + this.setValue(this.valueNow + 1); + + event.preventDefault(); + event.stopPropagation(); + +}; + +DateSpinButton.prototype.handleDecreaseClick = function (event) { + + this.setValue(this.valueNow - 1); + + event.preventDefault(); + event.stopPropagation(); + +}; diff --git a/examples/datepicker/common/js/datepicker.js b/examples/datepicker/js/datepicker.js similarity index 98% rename from examples/datepicker/common/js/datepicker.js rename to examples/datepicker/js/datepicker.js index 1a816496d3..1fe1498479 100644 --- a/examples/datepicker/common/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -1,4 +1,9 @@ - +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: datepicker.js +*/ var DatePicker = function (comboboxNode, inputNode, buttonNode, dialogNode, messageNode) { this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; @@ -373,12 +378,11 @@ DatePicker.prototype.hide = function (ignore) { DatePicker.prototype.handleDocumentClick = function (event) { console.log('[DateInput][handleDocumentClick][target]: ' + (event.target !== this.dateInput.inputNode)); - console.log('[DateInput][handleDocumentClick][contains]: ' + this.dialogNode.contains(event.target)); - console.log('[DateInput][handleDocumentClick][isOpen]: ' + this.isOpen()); if (this.isOpen() && !this.dialogNode.contains(event.target) && !this.buttonNode.contains(event.target) && + (this.imageNode !== event.target) && (this.dateInput.inputNode !== event.target)) { this.hide(); event.stopPropagation(); diff --git a/examples/datepicker/common/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js similarity index 94% rename from examples/datepicker/common/js/datepickerDay.js rename to examples/datepicker/js/datepickerDay.js index 4b06548f1e..b27ac7e6b4 100644 --- a/examples/datepicker/common/js/datepickerDay.js +++ b/examples/datepicker/js/datepickerDay.js @@ -1,3 +1,10 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: datepickerDay.js +*/ + var DatePickerDay = function (domNode, datepicker, index, row, column) { this.index = index; diff --git a/examples/datepicker/menubutton/css/datepicker.css b/examples/datepicker/menubutton/css/datepicker.css deleted file mode 100644 index f117b63d02..0000000000 --- a/examples/datepicker/menubutton/css/datepicker.css +++ /dev/null @@ -1,190 +0,0 @@ -.datepicker { - margin-top: 1em; -} - -.datepicker .downarrow { - content: url('../images/down-arrow-gray.png'); - position: relative; - left: -20px; -} - -.datepicker button.icon { - border-style: none; - text-align: left; - background-color: white; - position: relative; - left: -20px; - top: 4px; -} - -.datepicker .date label { - display: block; -} - -.datepicker input { - margin-top: 1em; - width: 20%; -} - -.datepickerDialog { - width: 45%; - clear: both; - display: none; - border: 3px solid hsl(216, 80%, 55%); - margin-top: 1em; - border-radius: 5px; - padding: 0; -} - -.header { - cursor: default; - background-color: hsl(216, 80%, 55%); - padding: 7px; - font-weight: bold; - text-transform: uppercase; - color: white; - display: flex; - justify-content: space-around; -} - -.header span { - display: inline-block; -} - -.header button { - border-style: none; - background: transparent; -} - -.datepickerDialog button::-moz-focus-inner { - border: 0; -} - -.prevYear, -.prevMonth, -.nextMonth, -.nextYear { - padding: 4px; - width: 24px; - height: 24px; - color: white; -} - -.prevYear:focus, -.prevMonth:focus, -.nextMonth:focus, -.nextYear:focus { - padding: 2px; - border: 2px solid white; - border-radius: 4px; - outline: 0; -} - -.dialogButtonGroup { - text-align: right; - margin-top: 1em; - margin-bottom: 1em; - margin-right: 1em; -} - -.dialogButton { - padding: 5px; - margin-left: 1em; - width: 5em; - background-color: hsl(216, 80%, 92%); - font-size: 0.85em; - color: black; - outline: none; - border: 1px solid hsl(216, 80%, 92%); - border-radius: 5px; -} - -.dialogButton:focus { - padding: 4px; - border: 2px solid black; -} - -.fa-calendar-alt { - color: hsl(216, 89%, 72%); -} - -.datepicker .monthYear { - display: inline-block; - width: 12em; - text-align: center; -} - -table.dates { - width: 100%; - padding-left: 1em; - padding-right: 1em; - padding-top: 1em; -} - -table.dates th, -table.dates td { - text-align: center; -} - -.dateRow { - border: 1px solid black; -} - -.dateCell { - outline: 0; - border: 0; - padding: 0; - margin: 0; - height: 40px; - width: 40px; -} - -.dateButton { - padding: 0; - margin: 0; - line-height: inherit; - height: 100%; - width: 100%; - border: 1px solid #eee; - border-radius: 5px; - font-size: 15px; - background: #eee; -} - -.dateButton:focus, -.dateButton:hover { - padding: 0; - background-color: hsl(216, 80%, 92%); -} - -.dateButton:focus { - border-width: 2px; - border-color: rgb(100, 100, 100); - outline: 0; -} - -.dateButton[aria-selected] { - border-color: rgb(100, 100, 100); -} - -.dateButton[tabindex="0"] { - background-color: hsl(216, 80%, 92%); -} - -.disabled { - cursor: not-allowed; - color: #afafaf; -} - -.dateButton:disabled { - color: #777; - background-color: #fff; - border: none; - cursor: not-allowed; -} - -.datepicker .message { - position: absolute; - top: -30em; - left: -300em; -} diff --git a/examples/datepicker/menubutton/images/down-arrow-gray.png b/examples/datepicker/menubutton/images/down-arrow-gray.png deleted file mode 100644 index f5d34f2e07d8f85cfb228ca7a587a94204f5790a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9M7P)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z+et)0R45f=U>J0Ov7w=XOHxwuH!&`I{`@&B3mY5T%J=WzYyJQK{|`P*jEsyRwbxkw z{ri{2#Kg3lot<6%_wU~fKnWCmK!X@qSy>r={rYtiq!ujO*4Adu%*?zKsFEKjh@t^# z7>NJz@87@fRaI3d!8{NHC{PGA{XdFoAO|D{k^`v$$uYwe?A^Qff`fyD7|@-jFk4ty zSQ!5N`7;HmxE-brYy*q~^g}ri9|kFeDn1N!dlgI`M5Fo;C;;@28qnQ`K$1Y4?0}}< j000000NkvXXu0mjf+hB%b diff --git a/test/tests/daetpicker-1.js b/test/tests/daetpicker-combobox.js similarity index 72% rename from test/tests/daetpicker-1.js rename to test/tests/daetpicker-combobox.js index eba63ce5a1..b4d1b9a02e 100644 --- a/test/tests/daetpicker-1.js +++ b/test/tests/daetpicker-combobox.js @@ -27,9 +27,14 @@ ariaTest('Combobox: has role', exampleFile, 'combobox-role', async (t) => { await assertAriaRoles(t, 'example', 'combobox', 1, 'div'); }); -ariaTest('Combobox: has aria-label', exampleFile, 'combobox-aria-label', async (t) => { +ariaTest('Combobox: has aria-labelledby', exampleFile, 'combobox-aria-labelledby', async (t) => { t.plan(1); - await assertAriaLabelExists(t, ex.comboboxSelector); + await assertAriaLabelledby(t, ex.comboboxSelector); +}); + +ariaTest('Combobox: has aria-haspopup set to "dialog"', exampleFile, 'combobox-aria-haspopup', async (t) => { + t.plan(1); + await assertAttributeValues(t, ex.menubuttonSelector, 'aria-haspopup', 'dialog'); }); From bf7a423392f65ca203b6a4f2ad6d547ad42c7f90 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 7 May 2019 13:58:39 -0500 Subject: [PATCH 043/137] fixing mouse interaction issues --- examples/datepicker/css/datepicker.css | 1 + examples/datepicker/js/datepicker-combobox.js | 8 ++-- examples/datepicker/js/datepicker.js | 45 ++++++++++++++----- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index d6e954fa2e..e809eec62e 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -41,6 +41,7 @@ margin-top: 1em; border-radius: 5px; padding: 0; + background-color: #FFF; } .header { diff --git a/examples/datepicker/js/datepicker-combobox.js b/examples/datepicker/js/datepicker-combobox.js index bad560bc2a..594da185ef 100644 --- a/examples/datepicker/js/datepicker-combobox.js +++ b/examples/datepicker/js/datepicker-combobox.js @@ -46,7 +46,7 @@ ComboboxInput.prototype.init = function () { this.buttonNode.addEventListener('keydown', this.handleButtonKeyDown.bind(this)); if (this.buttonNode.nextElementSibling && - this.button Node.nextElementSibling.classList.contains('arrow')) { + this.buttonNode.nextElementSibling.classList.contains('arrow')) { this.imageNode = this.inputNode.nextElementSibling; } @@ -105,9 +105,9 @@ ComboboxInput.prototype.handleTouchStart = function (event) { }; ComboboxInput.prototype.handleFocus = function () { - console.log('[ComboboxInput][handleFocus][hasFocus]: ' + this.hasFocusFlag); + console.log('[ComboboxInput][handleFocus][ignoreFocusEvent]: ' + this.ignoreFocusEvent); if (!this.ignoreFocusEvent && this.isCollapsed()) { - setTimeout(this.datepicker.show.bind(this.datepicker), 100); + this.datepicker.show(); this.setMessage('Use the down arrow key to move focus to the datepicker grid.'); } @@ -117,7 +117,7 @@ ComboboxInput.prototype.handleFocus = function () { }; ComboboxInput.prototype.handleBlur = function () { - console.log('[ComboboxInput][handleBlur]'); + console.log('[ComboboxInput][handleBlur][ignoreBlurEvent]: ' + this.ignoreBlurEvent); if (!this.ignoreBlurEvent) { this.datepicker.hide(false); this.setMessage(''); diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index 1fe1498479..57575d6125 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -88,7 +88,13 @@ DatePicker.prototype.init = function () { this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); - document.addEventListener('click', this.handleDocumentClick.bind(this), true); + document.body.addEventListener('mousedown', this.handleBackgroundMouseDown.bind(this), true); + + + if (!this.backgroundNode) { + this.backgroundNode = document.createElement('div'); + document.body.appendChild(this.backgroundNode); + } // Create Grid of Dates @@ -348,18 +354,34 @@ DatePicker.prototype.getDaysInMonth = function (year, month) { DatePicker.prototype.show = function () { this.dialogNode.style.display = 'block'; - this.dateInput.setAriaExpanded(true); + this.dialogNode.style.zIndex = 2; + +/* + this.backgroundNode.style.display = 'block'; + this.backgroundNode.style.position = 'static'; + this.backgroundNode.style.zIndex = 4; + + this.backgroundNode.style.backgroundColor = '#444'; + this.backgroundNode.style.opacity = '0.5'; + this.backgroundNode.style.width = '100%'; + + var bottom = this.backgroundNode.style.top; + this.backgroundNode.style.position = 'absolute'; + this.backgroundNode.style.top = '0'; + this.backgroundNode.style.left = '0'; + this.backgroundNode.style.bottom = bottom; +*/ + + this.dateInput.setAriaExpanded(true); this.getDateInput(); this.updateGrid(); -}; -DatePicker.prototype.hasFocus = function () { return this.hasFocusFlag; }; DatePicker.prototype.isOpen = function () { - return this.dateInput.getAriaExpanded();; + return this.dateInput.getAriaExpanded(); }; DatePicker.prototype.hide = function (ignore) { @@ -368,6 +390,8 @@ DatePicker.prototype.hide = function (ignore) { ignore = true; } + this.backgroundNode.style.display = '1px'; + this.backgroundNode.style.width = '1px'; this.dialogNode.style.display = 'none'; this.dateInput.setAriaExpanded(false); @@ -376,14 +400,11 @@ DatePicker.prototype.hide = function (ignore) { this.dateInput.focus(); }; -DatePicker.prototype.handleDocumentClick = function (event) { - console.log('[DateInput][handleDocumentClick][target]: ' + (event.target !== this.dateInput.inputNode)); +DatePicker.prototype.handleBackgroundMouseDown = function (event) { + console.log('[DatePicker][handleBackgroundMouseDown][isOpen]: ' + this.isOpen()); - if (this.isOpen() && - !this.dialogNode.contains(event.target) && - !this.buttonNode.contains(event.target) && - (this.imageNode !== event.target) && - (this.dateInput.inputNode !== event.target)) { + if (this.isOpen()) { + this.dateInput.ignoreFocusEvent = true; this.hide(); event.stopPropagation(); event.preventDefault(); From d8ceabb78ff46507b7d7aef2ca1552063b784d75 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 20 May 2019 11:58:18 -0500 Subject: [PATCH 044/137] fixed mouse bugs with date picker example --- examples/datepicker/css/datepicker.css | 43 ++++--- examples/datepicker/datepicker-combobox.html | 16 ++- .../datepicker/images/down-arrow-gray.png | Bin 1325 -> 0 bytes examples/datepicker/images/up-arrow-gray.png | Bin 304 -> 0 bytes examples/datepicker/js/datepicker-combobox.js | 113 ++++++++++-------- examples/datepicker/js/datepicker.js | 88 ++++++++------ examples/datepicker/js/datepickerDay.js | 6 +- 7 files changed, 157 insertions(+), 109 deletions(-) delete mode 100644 examples/datepicker/images/down-arrow-gray.png delete mode 100644 examples/datepicker/images/up-arrow-gray.png diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css index e809eec62e..ce2a0e618c 100644 --- a/examples/datepicker/css/datepicker.css +++ b/examples/datepicker/css/datepicker.css @@ -2,34 +2,43 @@ margin-top: 1em; } -.datepicker button[aria-expanded] + span.arrow { - content: url('../images/down-arrow-gray.png'); - position: relative; - left: -60px; -} - -.datepicker button[aria-expanded=true] + span.arrow { - content: url('../images/up-arrow-gray.png'); - position: relative; - top: 3px; - left: -60px; -} .datepicker button.icon { border-style: none; text-align: left; background-color: white; position: relative; - left: -2px; - top: 4px; + left: -25px; + top: 3px; } -.datepicker .date label { - display: block; +.datepicker span.arrow { + margin: 0; + padding: 0; + display: none; + background: transparent; +} + +.datepicker span.arrow svg polygon { + stroke: gray; + stroke-width: 1px; + fill: gray; +} + +.datepicker[aria-expanded=false] span.arrow.up { + display: inline-block; + position: relative; + left: -23px; +} + +.datepicker[aria-expanded=true] span.arrow.down { + display: inline-block; + position: relative; + left: -23px; } .datepicker input { - margin-top: 1em; + margin: 0; width: 20%; } diff --git a/examples/datepicker/datepicker-combobox.html b/examples/datepicker/datepicker-combobox.html index 0db28ebaed..f22d930a3e 100644 --- a/examples/datepicker/datepicker-combobox.html +++ b/examples/datepicker/datepicker-combobox.html @@ -14,9 +14,9 @@ + -
    SuMoTuWeThFrSaSuMoTuWeThFrSa
    Down ArrowDown Arrow, Enter
    • Open the date picker dialog.
    • diff --git a/examples/datepicker/datepicker-menubutton.html b/examples/datepicker/datepicker-menubutton.html index 1c52aa1f08..9fc48f9fc5 100644 --- a/examples/datepicker/datepicker-menubutton.html +++ b/examples/datepicker/datepicker-menubutton.html @@ -56,17 +56,17 @@

      Example

      id="id-texbox-1" aria-autocomplete="none"> - - +
      Opens datepicker dialog for previous date textbox
      @@ -108,7 +108,6 @@

      Example

      aria-controls="id-dialog-label"> - Example aria-labelledby="id-dialog-label"> - - - - - - - + + + + + + + diff --git a/examples/datepicker/js/datepicker-combobox.js b/examples/datepicker/js/datepicker-combobox.js index 9722ac1784..5a36ad02a2 100644 --- a/examples/datepicker/js/datepicker-combobox.js +++ b/examples/datepicker/js/datepicker-combobox.js @@ -65,6 +65,7 @@ ComboboxInput.prototype.handleKeyDown = function (event) { switch (event.keyCode) { case this.keyCode.DOWN: + case this.keyCode.RETURN: this.datepicker.show(); this.ignoreBlurEvent = true; this.datepicker.setFocusDay(); @@ -247,4 +248,20 @@ ComboboxInput.prototype.setMessage = function (str) { return this.messageNode.textContent = str; }; +// Initialize combobox date picker + +window.addEventListener('load' , function () { + + var datePickers = document.querySelectorAll('[role=combobox].datepicker'); + + datePickers.forEach(function (dp) { + var inputNode = dp.querySelector('input'); + var buttonNode = dp.querySelector('button'); + var messageNode = dp.querySelector('.message'); + var dialogNode = dp.querySelector('[role=dialog]'); + + var datePicker = new DatePicker(dp, inputNode, buttonNode, messageNode, dialogNode); + datePicker.init(); + }); +}); diff --git a/examples/datepicker/js/datepicker-menubutton.js b/examples/datepicker/js/datepicker-menubutton.js index cebf613418..cabebb964d 100644 --- a/examples/datepicker/js/datepicker-menubutton.js +++ b/examples/datepicker/js/datepicker-menubutton.js @@ -35,23 +35,11 @@ var MenuButtonInput = function (inputNode, buttonNode, messageNode, datepicker) }; MenuButtonInput.prototype.init = function () { - this.inputNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - this.inputNode.addEventListener('focus', this.handleFocus.bind(this)); - this.inputNode.addEventListener('blur', this.handleBlur.bind(this)); this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); this.buttonNode.addEventListener('touchstart', this.handleTouchStart.bind(this)); this.buttonNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - if (this.buttonNode.nextElementSibling && - this.buttonNode.nextElementSibling.classList.contains('arrow')) { - this.imageNode = this.inputNode.nextElementSibling; - } - - if (this.imageNode) { - this.imageNode.addEventListener('click', this.handleClick.bind(this)); - } - this.setMessage(''); }; @@ -61,6 +49,7 @@ MenuButtonInput.prototype.handleKeyDown = function (event) { switch (event.keyCode) { case this.keyCode.DOWN: + case this.keyCode.RETURN: this.datepicker.show(); this.ignoreBlurEvent = true; this.datepicker.setFocusDay(); @@ -72,11 +61,6 @@ MenuButtonInput.prototype.handleKeyDown = function (event) { flag = true; break; - case this.keyCode.TAB: - this.ignoreBlurEvent = true; - this.datepicker.hide(false); - break; - default: break; } @@ -147,7 +131,7 @@ MenuButtonInput.prototype.handleButtonClick = function (event) { }; -MenuButtonInput.prototype.focus = function () { +MenuButtonInput.prototype.setFocus = function () { this.inputNode.focus(); }; @@ -202,11 +186,12 @@ window.addEventListener('load' , function () { var datePickers = document.querySelectorAll('.datepicker'); datePickers.forEach(function (dp) { - var dpInput = dp.querySelector('input'); - var dpButton = dp.querySelector('button'); - var dpMessage = dp.querySelector('.message'); - var dpDialog = dp.querySelector('[role=dialog]'); - var datePicker = new DatePicker(null, dpInput, dpButton, dpDialog, dpMessage); + var inputNode = dp.querySelector('input'); + var buttonNode = dp.querySelector('button'); + var messageNode = dp.querySelector('.message'); + var dialogNode = dp.querySelector('[role=dialog]'); + + var datePicker = new DatePicker(null, inputNode, buttonNode, messageNode, dialogNode); datePicker.init(); }); diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index f7aa373faf..f21d15bcf6 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -5,19 +5,19 @@ * File: datepicker.js */ -var DatePicker = function (comboboxNode) { +var DatePicker = function (comboboxNode, inputNode, buttonNode, messageNode, dialogNode) { this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - this.inputNode = comboboxNode.querySelector('input'); - this.buttonNode = comboboxNode.querySelector('button'); - this.messageNode = comboboxNode.querySelector('.message'); - this.dialogNode = comboboxNode.querySelector('[role=dialog]'); + this.inputNode = inputNode; + this.buttonNode = buttonNode; + this.messageNode = messageNode; + this.dialogNode = dialogNode; if (comboboxNode) { this.dateInput = new ComboboxInput(comboboxNode, this.inputNode, this.buttonNode, this.messageNode, this); } else { - this.dateInput = new MenuButtonInput(inputNode, this.buttonNode, this.messageNode, this); + this.dateInput = new MenuButtonInput(this.inputNode, this.buttonNode, this.messageNode, this); } this.MonthYearNode = this.dialogNode.querySelector('.monthYear'); @@ -253,7 +253,6 @@ DatePicker.prototype.showLastRow = function () { }; DatePicker.prototype.setFocusDay = function (flag) { - console.log('[DatePicker][setFocusDay]'); if (typeof flag !== 'boolean') { flag = true; @@ -266,15 +265,15 @@ DatePicker.prototype.setFocusDay = function (flag) { if ((d.day == this.day) && (d.month == this.month)) { this.currentDay = d; + d.domNode.setAttribute('tabindex', '0'); + if (flag) { if (!this.hasFocusFlag) { this.dateInput.setMessage('Use the cursor keys to navigate the date picker grid.'); } this.hasFocusFlag = true; - console.log('[DatePicker][setFocusDay][focus]'); d.domNode.focus(); - d.domNode.setAttribute('tabindex', '0'); } } } @@ -379,15 +378,10 @@ DatePicker.prototype.hide = function (ignore) { }; DatePicker.prototype.handleBackgroundMouseDown = function (event) { - console.log(''); - console.log('[DatePicker][handleBackgroundMouseDown]'); - console.log('[DatePicker][handleBackgroundMouseDown][isMouseDownOnBackground][A]: ' + this.isMouseDownOnBackground); - if (!this.inputNode.parentNode.contains(event.target) && !this.dialogNode.contains(event.target)) { this.isMouseDownOnBackground = true; - console.log('[DatePicker][handleBackgroundMouseDown][isMouseDownOnBackground][B]: ' + this.isMouseDownOnBackground); if (this.isOpen()) { this.hide(); @@ -803,15 +797,4 @@ DatePicker.prototype.getDateInput = function () { }; -// Initialize combobox date picker - -window.addEventListener('load' , function () { - - var datePickers = document.querySelectorAll('[role=combobox].datepicker'); - - datePickers.forEach(function (dp) { - var datePicker = new DatePicker(dp); - datePicker.init(); - }); -}); From 395aa57068004519534c9134ed97fe40e8439a9c Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 18 Jun 2019 15:08:14 -0500 Subject: [PATCH 049/137] fixed bug with next/previous buttons when focus is in the textbox --- examples/datepicker/js/datepicker.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js index f21d15bcf6..1b3dae8920 100644 --- a/examples/datepicker/js/datepicker.js +++ b/examples/datepicker/js/datepicker.js @@ -378,10 +378,12 @@ DatePicker.prototype.hide = function (ignore) { }; DatePicker.prototype.handleBackgroundMouseDown = function (event) { + console.log('[DatePicker][handleBackgroundMouseDown]'); if (!this.inputNode.parentNode.contains(event.target) && !this.dialogNode.contains(event.target)) { this.isMouseDownOnBackground = true; + console.log('[DatePicker][handleBackgroundMouseDown][isMouseDownOnBackground]: ' + this.isMouseDownOnBackground); if (this.isOpen()) { this.hide(); @@ -513,6 +515,9 @@ DatePicker.prototype.handleNextYearButton = function (event) { break; case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } this.moveToNextYear(); this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); @@ -566,6 +571,9 @@ DatePicker.prototype.handlePreviousYearButton = function (event) { break; case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } this.moveToPreviousYear(); this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); @@ -608,6 +616,9 @@ DatePicker.prototype.handleNextMonthButton = function (event) { break; case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } this.moveToNextMonth(); this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); @@ -650,6 +661,9 @@ DatePicker.prototype.handlePreviousMonthButton = function (event) { break; case 'click': + if (!this.hasFocusFlag) { + this.dateInput.ignoreBlurEvent = true; + } this.moveToPreviousMonth(); this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); From 6305c1cf1d4403085ad345e7a4fe1a771f94741d Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 18 Jun 2019 17:08:58 -0500 Subject: [PATCH 050/137] created intial example of menu button for opening date picker --- examples/datepicker/datepicker-combobox.html | 10 -- .../datepicker/datepicker-menubutton.html | 112 ++++-------------- .../datepicker/js/datepicker-menubutton.js | 2 +- 3 files changed, 22 insertions(+), 102 deletions(-) diff --git a/examples/datepicker/datepicker-combobox.html b/examples/datepicker/datepicker-combobox.html index 7522e3938b..8a284f9078 100644 --- a/examples/datepicker/datepicker-combobox.html +++ b/examples/datepicker/datepicker-combobox.html @@ -838,16 +838,6 @@

      Date Picker Dialog: Date Grid

      The default role for a th[scope="col"] element is columnheader. -
      - - - - - diff --git a/examples/datepicker/datepicker-menubutton.html b/examples/datepicker/datepicker-menubutton.html index 9fc48f9fc5..d2ce627b39 100644 --- a/examples/datepicker/datepicker-menubutton.html +++ b/examples/datepicker/datepicker-menubutton.html @@ -201,7 +201,7 @@

      Example

      Accessibility Features

      There are three main featues of the menu button date picker example:

        -
      • Textbox: Holds the value of the selected date and can open the date picker dialog box.
      • +
      • Date Textbox: Holds the value of the selected date.
      • Calendar Button: Opens the datepicker dialog box.
      • Date Picker Dialog Box: Displays a grid of dates for the user to select from and provide additional buttons to change the month and year of dates shown in the grid.
      @@ -209,19 +209,17 @@

      Accessibility Features

      The textbox has the accessible name of Date and button has the accessible name of Calendar.

      -

      Textbox

      +

      Date Textbox

      • Contains the date value.
      • -
      • Opens and closes the date picker dialog box through keyboard, click and touch events.
      • -
      • A live region provides information on how to open the date picker dialog when textbox receives focus (e.g. using down arrow key).
      • -
      • To support people who are sighted in understanding the down arrow key opens the date picker dialog box, a down arrow icon appears in the textbox when the textbox receives keyboard focus and the down arrow icon is removed when the textbox does not have keyboard focus.
      -

      Change Date Button

      +

      Calendar Button

      • Opens and closes the date picker dialog box through keyboard and click events.
      • +
      • A live region provides information on how to open the date picker dialog when textbox receives focus (e.g. using down arrow key).
      • The accessible name of the button is updated when a date is selected to include the current date.
      • The title attribute describes the relationship between the button and the textbox.
      @@ -243,30 +241,7 @@

      Date Picker Dialog

      Keyboard Support

      -

      Textbox

      -
      SuMoTuWeThFrSaSuMoTuWeThFrSa
      aira-label=Stringth -
        -
      • Define the accessible name as a day of week for column headers.
      • -
      -
      gridcell
      - - - - - - - - - - - - -
      KeyFunction
      Down Arrow -
        -
      • Open the date picker dialog.
      • -
      • Move focus to current date.
      • -
      -
      - - -
      -

      Change Date Button

      +

      Calendar Button

      @@ -300,7 +275,7 @@

      Date Picker Dialog

      - + @@ -358,7 +333,7 @@

      Date Picker Dialog: Date Grid

      @@ -435,7 +410,7 @@

      Date Picker Dialog: Date Grid

      @@ -458,8 +433,8 @@

      Date Picker Buttons (OK and Cancel)

      @@ -471,43 +446,9 @@

      Date Picker Buttons (OK and Cancel)

      Role, Property, State, and Tabindex Attributes

      -
      -

      Textbox

      -
      ESCClose the dialog and move focus back to the date input.Close the dialog and move focus back to the calendar button.
      TAB Space,
      Return
        -
      • Select the date, close the dialog box and move focus to the date input.
      • +
      • Select the date, close the dialog box and move focus to the calendar button.
      • The value of the date input is updated with the selected date in the from the selected date in the date grid.
      ESC
        -
      • Close the dialog and move focus back to date input.
      • +
      • Close the dialog and move focus back to calendar button.
      • The value of the date texbox is not updated.
      Space,
      Return
        -
      • The Cancel button closes the date picker dialog, moves focus to date input, does not update date in date input.
      • -
      • The OK button closes the date picker dialog, moves focus to date input, does update date in date input.
      • +
      • The Cancel button closes the date picker dialog, moves focus to calendar button, does not update date in date input.
      • +
      • The OK button closes the date picker dialog, moves focus to calendar button, does update date in date input.
      - - - - - - - - - - - - - - - - - - - - - - -
      RoleAttributeElementUsage
      textboxinput -
        -
      • The input element with no type attribute has the default role of textbox.
      • -
      • Accessible name comes from a label element using the for attribute.
      • -
      -
      aria-autocomplete=noneinput - aria-autocomplete=none the textbox does not support autocompletion when text is entered. -
      -
      -

      Change Date Button

      +

      Calendar Button

      @@ -526,30 +467,29 @@

      Change Date Button

      • The button has the default role of button.
      • Accessible name comes from aria-label attribute.
      • -
      • Button is removed from tab order of the page, since the dialog can be opened from the date input with the keyboard.
      - + - + - + @@ -560,20 +500,20 @@

      Change Date Button

      - + - + - + @@ -739,16 +679,6 @@

      Date Picker Dialog: Date Grid

      The default role for a th[scope="col"] element is columnheader. - - - - - - diff --git a/examples/datepicker/js/datepicker-menubutton.js b/examples/datepicker/js/datepicker-menubutton.js index cabebb964d..a2bc7ccd1f 100644 --- a/examples/datepicker/js/datepicker-menubutton.js +++ b/examples/datepicker/js/datepicker-menubutton.js @@ -132,7 +132,7 @@ MenuButtonInput.prototype.handleButtonClick = function (event) { MenuButtonInput.prototype.setFocus = function () { - this.inputNode.focus(); + this.buttonNode.focus(); }; MenuButtonInput.prototype.setAriaExpanded = function (flag) { From 30a09ae40d77accc5feb92e5c5fd123a1747348d Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 19 Jun 2019 10:18:56 -0500 Subject: [PATCH 051/137] added a file to support spin buttons --- examples/datepicker/datepicker-spinbuttons.html | 2 +- examples/datepicker/js/spinbutton.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 examples/datepicker/js/spinbutton.js diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html index bdcf2bf594..d5ffa68062 100644 --- a/examples/datepicker/datepicker-spinbuttons.html +++ b/examples/datepicker/datepicker-spinbuttons.html @@ -26,7 +26,7 @@
      -

      Date Picker Example Using Menu Button

      +

      Date Picker Example Using Spin Buttons

      NOTE: This example page is work in progress. Please provide feedback in diff --git a/examples/datepicker/js/spinbutton.js b/examples/datepicker/js/spinbutton.js new file mode 100644 index 0000000000..e69de29bb2 From f2043c73bf72645891a8dd5420e33fc0010283cd Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 20 Jun 2019 13:23:35 -0500 Subject: [PATCH 052/137] initial implementation of a datepicker using spin buttons --- .../datepicker/css/datepicker-spinbuttons.css | 64 +++++ .../datepicker/datepicker-spinbuttons.html | 192 +++++++++------ .../datepicker/js/datepicker-spinbuttons.js | 194 ++++++++------- examples/datepicker/js/spinbutton-date.js | 222 ++++++++++++++++++ examples/datepicker/js/spinbutton.js | 0 5 files changed, 508 insertions(+), 164 deletions(-) create mode 100644 examples/datepicker/css/datepicker-spinbuttons.css create mode 100644 examples/datepicker/js/spinbutton-date.js delete mode 100644 examples/datepicker/js/spinbutton.js diff --git a/examples/datepicker/css/datepicker-spinbuttons.css b/examples/datepicker/css/datepicker-spinbuttons.css new file mode 100644 index 0000000000..2983e9cedb --- /dev/null +++ b/examples/datepicker/css/datepicker-spinbuttons.css @@ -0,0 +1,64 @@ +.datepicker-spinbuttons { + margin-top: 1em; +} + + +.datepicker-spinbuttons .day { + width: 2em; +} + +.datepicker-spinbuttons .month { + width: 6em; +} + +.datepicker-spinbuttons .year { + width: 3em; +} + + +.datepicker-spinbuttons .spinbutton { + float: left; + text-align: center; +} + +.datepicker-spinbuttons .spinbutton .previous, +.datepicker-spinbuttons .spinbutton .next { + color: gray; +} + +.datepicker-spinbuttons .spinbutton .previous { + border-bottom: 1px solid black; +} + +.datepicker-spinbuttons .spinbutton .next { + border-top: 1px solid black; +} + + +.datepicker-spinbuttons .spinbutton .decrease svg polygon, +.datepicker-spinbuttons .spinbutton .increase svg polygon { + fill: #333; + stroke-width: 3px; + stroke: transparent; +} + +.datepicker-spinbuttons .spinbutton .decrease { + position: relative; + top: 4px; +} + +.datepicker-spinbuttons .spinbutton .decrease:hover svg polygon, +.datepicker-spinbuttons .spinbutton .increase:hover svg polygon { + fill: #005A9C; + stroke: #005A9C; +} + +.datepicker-spinbuttons .spinbutton:focus svg polygon { + fill: #005A9C; + stroke: #005A9C; +} + +div[role="separator"] { + clear: both; +} + diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html index d5ffa68062..d805efc617 100644 --- a/examples/datepicker/datepicker-spinbuttons.html +++ b/examples/datepicker/datepicker-spinbuttons.html @@ -13,7 +13,8 @@ - + + @@ -42,49 +43,55 @@

      Example

      -
      +
      -
      Date + +
      - January - - - + aria-valuemax="31" + aria-label="Day"> +
      + + - - - - +
      + +
      1
      + +
      + + - +
      -
      - 1st - - - + aria-valuemax="12" + aria-label="Month"> +
      + + - - - - +
      + +
      June
      + +
      + + - +
      Example aria-valuemin="2019" aria-valuemax="2040" aria-label="Year"> - 2019 - - - +
      + + - - - - +
      + +
      2019
      + +
      + + - +
      @@ -115,17 +124,17 @@

      Example

      Accessibility Features

      There are three main featues of the spin button date picker example:

        -
      • -
      • -
      • +
      • Day, month and year are separate spin buttons
      • +
      • The increase and decrease events are the div element containing the impages of the up and odwn arrows, making the effective target area larger for changing the value of a spin button.
      • +
      • Focusing a spin button increases the size of the increase and decrease bttons to make the element with focus easier to find and to track focus changes.

      Keyboard Support

      -

      Spin Button (Font Size)

      -

      The spin button for changing font size provides the following keyboard support described in the spin button design pattern.

      +

      Spin Button (Day, Month and Year)

      +

      The spin button for changing the day, month and year provides the following keyboard support described in the spin button design pattern.

      aria-label=String button
        -
      • The initial value of accessible name is Change Date.
      • +
      • The initial value of accessible name is Calendar.
      • When user selected date, the accessible name will update to current date.
      aria-haspopup=dialog button - Identifies the button as a menu button and follws the meu button design pattern. + Identifies the button as a menu button and follows the menu button design pattern.
      aria-expanded=true button
      aria-controls=IDREF button - Provides a reference to a screen reader for direct navigation to the dialog box. + Provides a reference to a screen reader for direct navigation to the date picker dialog box.
      title=stringaria-describedby=IDREF button - Provides a description of the relationship textbox and menu button, basically the button allows you to choose a new date using a date picker. + Provides a description to the user that calendar button is used to change the value of the textbox.
      aira-label=Stringth -
        -
      • Define the accessible name as a day of week for column headers.
      • -
      -
      gridcell
      @@ -134,21 +143,29 @@

      Spin Button (Font Size)

      - + - + - + - + - + - + - + - + + + + + + + + +
      Down ArrowDecreases the font size of the text in the textarea by 1 point.Decreases the spin button value by 1 item.
      Up ArrowIncreases the font size of the text in the textarea by 1 point.Increases the spin button value by 1 item.
      Page DownDecreases the font size of the text in the textarea by 5 points.Decreases the spin button value by 5 items.
      Page UpIncreases the font size of the text in the textarea by 5 points.Increases the spin button value by 5 items.
      EndSet spin button value to maximum value.
      HomeSet spin button value to mimimum value.
      @@ -161,8 +178,8 @@

      Role, Property, State, and Tabindex Attributes

      -

      Spin Button (Font Size)

      -

      The spin button for changing font size implements the following ARIA attributes described in the spin button design pattern.

      +

      Spin Button (Day, Month and Year)

      +

      The spin button for changing day, month and year use the following ARIA attributes described in the spin button design pattern.

      @@ -173,38 +190,59 @@

      Spin Button (Font Size)

      - + + + + + + + + + + + + + - + - + - + - + - + - + - + - + @@ -212,13 +250,18 @@

      Spin Button (Font Size)

      - + - +
      groupdiv +
        +
      • Identifies the group of spin buttons for defining a date.
      • +
      • The accessible name comes from the aria-labelledby attribute.
      • +
      +
      aria-labelledy="IDREF"divThe IDREF identifies the element with the visible text defining the accessible name for the group of spin buttons, i.e. "Date".
      spinbutton divIdentifies the div element as a spinbutton. +
        +
      • Identifies the div element as a spinbutton.
      • +
      • The accessible name comes from the aria-label attribute.
      • +
      +
      aria-label="Font size in points"aria-label="name" divDefines the accessible name for the spin button as "Font size in points".Defines the accessible name for each spin button (e.g. day, month and year).
      aria-valuenow="NUMBER_POINTS"aria-valuenow="number" div
        -
      • Indicates the current value of the spin button.
      • -
      • Updated by JavaScript as users adjust the value of the spin button.
      • +
      • Indicates the current numeric value of the spin button.
      • +
      • Updated by JavaScript as users change the value of the spin button.
      aria-valuetext="NUMBER_POINTS Points"aria-valuetext="month" div
        -
      • Provides a more user friendly announcement of the current value for screen reader users.
      • -
      • NUMBER_POINTS is updated by JavaScript as the user adjusts the value of the spin button.
      • -
      • As users adjust the value, instead of hearing only a number, they will hear the current value followed by the word Points, e.g., 10 Points. +
      • For the month spin button provides a more user friendly announcement of the current month for screen reader users.
      • +
      • As users adjust the value, instead of hearing only a number, they will hear the current month.
      aria-valuemin="8" divIndicates the minimum allowed value for the spin button, i.e., smallest supported font size.Indicates the minimum allowed value for the spin button.
      aria-valuemax="40" divIndicates the maximum allowed value for the spin button, i.e., largest supported font size. +
        +
      • Indicates the maximum allowed value for the spin button.
      • +
      • For the spin button for days, the maximum value is updated to reflect the number of days in the current month.
      • +
      +
      @@ -230,11 +273,14 @@

      Javascript and CSS Source Code

      diff --git a/examples/datepicker/js/datepicker-spinbuttons.js b/examples/datepicker/js/datepicker-spinbuttons.js index 6ff7014ce6..bba9d9fdf1 100644 --- a/examples/datepicker/js/datepicker-spinbuttons.js +++ b/examples/datepicker/js/datepicker-spinbuttons.js @@ -2,141 +2,153 @@ * This content is licensed according to the W3C Software License at * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * -* File: datepicker-DateSpinButton.js +* File: datepicker-spinbuttons.js */ -var DateSpinButton = function (domNode, toolbar) { +var DatePickerSpinButtons = function (domNode) { this.domNode = domNode; - this.toolbar = toolbar; - - this.valueDomNode = domNode.querySelector('.value'); - this.increaseDomNode = domNode.querySelector('.increase'); - this.decreaseDomNode = domNode.querySelector('.decrease'); - - this.valueMin = 8; - this.valueMax = 40; - this.valueNow = 12; - this.valueText = this.valueNow + ' Point'; - - this.keyCode = Object.freeze({ - 'UP': 38, - 'DOWN': 40, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36 - }); + this.monthNode = domNode.querySelector('.spinbutton.month'); + this.dayNode = domNode.querySelector('.spinbutton.day'); + this.yearNode = domNode.querySelector('.spinbutton.year'); + + this.valuesMonth = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + }; // Initialize slider -DateSpinButton.prototype.init = function () { +DatePickerSpinButtons.prototype.init = function () { - if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); - } + this.spinbuttonDay = new SpinButtonDate(this.dayNode, null, this.updateDay.bind(this)); + this.spinbuttonDay.init(); - if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); - } + this.spinbuttonMonth = new SpinButtonDate(this.monthNode, this.valuesMonth, this.updateMonth.bind(this)); + this.spinbuttonMonth.init(); - if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); - } - - this.setValue(this.valueNow); + this.spinbuttonYear = new SpinButtonDate(this.yearNode, null, this.updateYear.bind(this)); + this.spinbuttonYear.init(); + this.spinbuttonYear.noWrap(); - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.minYear = this.spinbuttonYear.getValueMin(); + this.maxYear = this.spinbuttonYear.getValueMax(); - this.increaseDomNode.addEventListener('click', this.handleIncreaseClick.bind(this)); - this.decreaseDomNode.addEventListener('click', this.handleDecreaseClick.bind(this)); + this.day = this.spinbuttonDay.getValue(); + this.month = this.spinbuttonMonth.getValue(); + this.year = this.spinbuttonYear.getValue(); + this.daysInMonth = this.getDaysInMonth(this.year, this.month); + this.updateSpinButtons(); }; -DateSpinButton.prototype.setValue = function (value) { +DatePickerSpinButtons.prototype.getDaysInMonth = function (year, month) { - if (value > this.valueMax) { - value = this.valueMax; + if (typeof year !== 'number') { + year = this.year; } - if (value < this.valueMin) { - value = this.valueMin; + if (typeof month !== 'number') { + month = this.month; } - this.valueNow = value; - this.valueText = value + ' Point'; + switch (month) { + + case 0: + case 2: + case 4: + case 6: + case 7: + case 9: + case 11: + return 31; + + case 1: + return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); - this.domNode.setAttribute('aria-valuenow', this.valueNow); - this.domNode.setAttribute('aria-valuetext', this.valueText); + case 3: + case 5: + case 8: + case 10: + return 30; + + default: + break; - if (this.valueDomNode) { - this.valueDomNode.innerHTML = this.valueText; } - this.toolbar.changeFontSize(value); + return -1; }; -DateSpinButton.prototype.handleKeyDown = function (event) { - - var flag = false; +DatePickerSpinButtons.prototype.updateDay = function (day) { + this.day = day; + this.updateSpinButtons(); +}; - switch (event.keyCode) { - case this.keyCode.DOWN: - this.setValue(this.valueNow - 1); - flag = true; - break; +DatePickerSpinButtons.prototype.updateMonth = function (month) { + this.month = month; + this.updateSpinButtons(); +}; - case this.keyCode.UP: - this.setValue(this.valueNow + 1); - flag = true; - break; +DatePickerSpinButtons.prototype.updateYear = function (year) { + this.year = year; + this.updateSpinButtons(); +}; - case this.keyCode.PAGEDOWN: - this.setValue(this.valueNow - 5); - flag = true; - break; +DatePickerSpinButtons.prototype.updatePreviousDayMonthAndYear = function () { + this.previousYear = this.year-1; - case this.keyCode.PAGEUP: - this.setValue(this.valueNow + 5); - flag = true; - break; + this.previousMonth = this.month ? this.month - 1 : 11; - case this.keyCode.HOME: - this.setValue(this.valueMin); - flag = true; - break; + this.previousDay = this.day - 1; + if (this.previousDay < 1) { + this.previousDay = this.getDaysInMonth(this.year, this.month); + } +}; - case this.keyCode.END: - this.setValue(this.valueMax); - flag = true; - break; +DatePickerSpinButtons.prototype.updateNextDayMonthAndYear = function () { + this.nextYear = this.year+1; + this.nextMonth = this.month >= 11 ? 0 : this.month + 1; - default: - break; + this.nextDay = this.day + 1; + var maxDay = this.getDaysInMonth(this.year, this.month); + if (this.nextDay > maxDay) { + this.nextDay = 1; } +}; - if (flag) { - event.preventDefault(); - event.stopPropagation(); +DatePickerSpinButtons.prototype.updateSpinButtons = function () { + + this.daysInMonth = this.getDaysInMonth(this.year, this.month); + this.spinbuttonDay.setValueMax(this.daysInMonth); + if (this.day > this.daysInMonth) { + this.spinbuttonDay.setValue(this.daysInMonth); + return; } -}; + this.updatePreviousDayMonthAndYear(); + this.updateNextDayMonthAndYear(); -DateSpinButton.prototype.handleIncreaseClick = function (event) { + this.spinbuttonDay.setPreviousValue(this.previousDay); + this.spinbuttonMonth.setPreviousValue(this.previousMonth); + this.spinbuttonYear.setPreviousValue(this.previousYear); + + this.spinbuttonDay.setNextValue(this.nextDay); + this.spinbuttonMonth.setNextValue(this.nextMonth); + this.spinbuttonYear.setNextValue(this.nextYear); - this.setValue(this.valueNow + 1); - event.preventDefault(); - event.stopPropagation(); }; -DateSpinButton.prototype.handleDecreaseClick = function (event) { +// Initialize menu button date picker - this.setValue(this.valueNow - 1); +window.addEventListener('load' , function () { - event.preventDefault(); - event.stopPropagation(); + var dpsbs = document.querySelectorAll('.datepicker-spinbuttons'); -}; + dpsbs.forEach(function (dpsb) { + var datepicker = new DatePickerSpinButtons(dpsb); + datepicker.init(); + }); + +}); diff --git a/examples/datepicker/js/spinbutton-date.js b/examples/datepicker/js/spinbutton-date.js new file mode 100644 index 0000000000..6946d3bd6f --- /dev/null +++ b/examples/datepicker/js/spinbutton-date.js @@ -0,0 +1,222 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: spinbutton-date.js +*/ + +var SpinButtonDate = function (domNode, values, callback) { + + this.domNode = domNode; + this.values = values; + this.callback = callback; + this.wrap = true; + + var initialValue = domNode.getAttribute('aria-valuetext'); + + this.valueNode = domNode.querySelector('.value'); + this.previousValueNode = domNode.querySelector('.previous'); + this.nextValueNode = domNode.querySelector('.next'); + + this.increaseNode = domNode.querySelector('.increase'); + this.decreaseNode = domNode.querySelector('.decrease'); + + if (values) { + this.valueMin = 0; + this.valueMax = values.length-1; + if (initialValue) { + this.valueNow = values.indexOf(initialValue); + this.valueText = initialValue; + } + else { + this.valueNow = values.length / 2; + this.valueText = values[this.valueNow]; + } + } + else { + this.valueMin = parseInt(domNode.getAttribute('aria-valuemin')); + this.valueMax = parseInt(domNode.getAttribute('aria-valuemax')); + this.valueNow = parseInt(domNode.getAttribute('aria-valuenow')); + this.valueText = domNode.getAttribute('aria-valuenow'); + } + + this.keyCode = Object.freeze({ + 'UP': 38, + 'DOWN': 40, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36 + }); +}; + +// Initialize slider +SpinButtonDate.prototype.init = function () { + + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + + this.increaseNode.addEventListener('click', this.handleIncreaseClick.bind(this)); + this.decreaseNode.addEventListener('click', this.handleDecreaseClick.bind(this)); +}; + +SpinButtonDate.prototype.noWrap = function () { + this.wrap = false; +}; + +SpinButtonDate.prototype.getValue = function () { + return this.valueNow; +}; + +SpinButtonDate.prototype.setValue = function (value) { + + if (value > this.valueMax) { + if (this.wrap) { + value = this.valueMin; + } + else { + value = this.valueMax; + } + } + + if (value < this.valueMin) { + if (this.wrap) { + value = this.valueMax; + } + else { + value = this.valueMin; + } + } + + this.valueNow = value; + if (this.values) { + this.valueText = this.values[this.valueNow]; + } + else { + if (typeof value === 'number') { + this.valueText = parseInt(value); + } + } + + this.domNode.setAttribute('aria-valuenow', this.valueNow); + this.domNode.setAttribute('aria-valuetext', this.valueText); + + this.valueNode.innerHTML = this.valueText; + + if (this.callback) { + this.callback(this.valueNow); + } + +}; + +SpinButtonDate.prototype.getValueMin = function (value) { + return parseInt(this.domNode.getAttribute('aria-valuemin')); +}; + +SpinButtonDate.prototype.getValueMax = function (value) { + return parseInt(this.domNode.getAttribute('aria-valuemax')); +}; + +SpinButtonDate.prototype.setValueMax = function (value) { + this.domNode.setAttribute('aria-valuemax', value); + this.valueMax = value; +}; + + +SpinButtonDate.prototype.setPreviousValue = function (value) { + + if (this.values) { + value = this.values[value]; + } + + if (typeof value === 'number') { + if (value < this.valueMin) { + value = ' '; + } + else { + value = parseInt(value); + } + } + + this.previousValueNode.innerHTML = value; +}; + +SpinButtonDate.prototype.setNextValue = function (value) { + if (this.values) { + value = this.values[value]; + } + + if (typeof value === 'number') { + if (value > this.valueMax) { + value = ' '; + } + else { + value = parseInt(value); + } + } + + this.nextValueNode.innerHTML = value; +}; + +SpinButtonDate.prototype.handleKeyDown = function (event) { + + var flag = false; + + switch (event.keyCode) { + case this.keyCode.DOWN: + this.setValue(this.valueNow - 1); + flag = true; + break; + + case this.keyCode.UP: + this.setValue(this.valueNow + 1); + flag = true; + break; + + case this.keyCode.PAGEDOWN: + this.setValue(this.valueNow - 5); + flag = true; + break; + + case this.keyCode.PAGEUP: + this.setValue(this.valueNow + 5); + flag = true; + break; + + case this.keyCode.HOME: + this.setValue(this.valueMin); + flag = true; + break; + + case this.keyCode.END: + this.setValue(this.valueMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + +}; + +SpinButtonDate.prototype.handleIncreaseClick = function (event) { + + this.setValue(this.valueNow + 1); + + event.preventDefault(); + event.stopPropagation(); + +}; + +SpinButtonDate.prototype.handleDecreaseClick = function (event) { + + this.setValue(this.valueNow - 1); + + event.preventDefault(); + event.stopPropagation(); + +}; diff --git a/examples/datepicker/js/spinbutton.js b/examples/datepicker/js/spinbutton.js deleted file mode 100644 index e69de29bb2..0000000000 From 21483cbbfc009ea45da2fe81493faa8fa43f369c Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 20 Jun 2019 13:37:24 -0500 Subject: [PATCH 053/137] added documenation about values wrapping for day and month --- examples/datepicker/datepicker-spinbuttons.html | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/datepicker/datepicker-spinbuttons.html b/examples/datepicker/datepicker-spinbuttons.html index d805efc617..8e51f4404d 100644 --- a/examples/datepicker/datepicker-spinbuttons.html +++ b/examples/datepicker/datepicker-spinbuttons.html @@ -145,11 +145,21 @@

      Spin Button (Day, Month and Year)

    Down ArrowDecreases the spin button value by 1 item. +
      +
    • Decreases the spin button value by 1 item.
    • +
    • When on the spin button for day is on the first day of the month, the value is set to the last day.
    • +
    • When on the spin button for month is on January, the value is set to December.
    • +
    +
    Up ArrowIncreases the spin button value by 1 item.
    Page Down
    Up Arrow
    • Increases the spin button value by 1 item.
    • When on the spin button for day is on the last day of the month, the value is set to the first day.
    • When on the spin button for month is on December, the value is set to January.
    +
    Page Down
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    Down Arrow -
      -
    • Decreases the spin button value by 1 item.
    • -
    • When on the spin button for day is on the first day of the month, the value is set to the last day.
    • -
    • When on the spin button for month is on January, the value is set to December.
    • -
    -
    Up Arrow -
      -
    • Increases the spin button value by 1 item.
    • -
    • When on the spin button for day is on the last day of the month, the value is set to the first day.
    • -
    • When on the spin button for month is on December, the value is set to January.
    • -
    -
    Page DownDecreases the spin button value by 5 items.
    Page UpIncreases the spin button value by 5 items.
    EndSet spin button value to maximum value.
    HomeSet spin button value to mimimum value.
    -
    - -
    - -
    -

    Role, Property, State, and Tabindex Attributes

    - - -
    -

    Spin Button (Day, Month and Year)

    -

    The spin button for changing day, month and year use the following ARIA attributes described in the spin button design pattern.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    groupdiv -
      -
    • Identifies the group of spin buttons for defining a date.
    • -
    • The accessible name comes from the aria-labelledby attribute.
    • -
    -
    aria-labelledy="IDREF"divThe IDREF identifies the element with the visible text defining the accessible name for the group of spin buttons, i.e. "Date".
    spinbuttondiv -
      -
    • Identifies the div element as a spinbutton.
    • -
    • The accessible name comes from the aria-label attribute.
    • -
    -
    aria-label="name"divDefines the accessible name for each spin button (e.g. day, month and year).
    aria-valuenow="number"div -
      -
    • Indicates the current numeric value of the spin button.
    • -
    • Updated by JavaScript as users change the value of the spin button.
    • -
    -
    aria-valuetext="month"div -
      -
    • For the month spin button provides a more user friendly announcement of the current month for screen reader users.
    • -
    • As users adjust the value, instead of hearing only a number, they will hear the current month.
    • -
    -
    aria-valuemin="8"divIndicates the minimum allowed value for the spin button.
    aria-valuemax="40"div -
      -
    • Indicates the maximum allowed value for the spin button.
    • -
    • For the spin button for days, the maximum value is updated to reflect the number of days in the current month.
    • -
    -
    -
    -
    - -
    -

    Javascript and CSS Source Code

    - -
    - -
    -

    HTML Source Code

    - -
    - - -
    -
    - - - diff --git a/examples/datepicker/js/datepicker-spinbuttons.js b/examples/datepicker/js/datepicker-spinbuttons.js deleted file mode 100644 index bba9d9fdf1..0000000000 --- a/examples/datepicker/js/datepicker-spinbuttons.js +++ /dev/null @@ -1,154 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: datepicker-spinbuttons.js -*/ - -var DatePickerSpinButtons = function (domNode) { - - this.domNode = domNode; - this.monthNode = domNode.querySelector('.spinbutton.month'); - this.dayNode = domNode.querySelector('.spinbutton.day'); - this.yearNode = domNode.querySelector('.spinbutton.year'); - - this.valuesMonth = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - -}; - -// Initialize slider -DatePickerSpinButtons.prototype.init = function () { - - this.spinbuttonDay = new SpinButtonDate(this.dayNode, null, this.updateDay.bind(this)); - this.spinbuttonDay.init(); - - this.spinbuttonMonth = new SpinButtonDate(this.monthNode, this.valuesMonth, this.updateMonth.bind(this)); - this.spinbuttonMonth.init(); - - this.spinbuttonYear = new SpinButtonDate(this.yearNode, null, this.updateYear.bind(this)); - this.spinbuttonYear.init(); - this.spinbuttonYear.noWrap(); - - this.minYear = this.spinbuttonYear.getValueMin(); - this.maxYear = this.spinbuttonYear.getValueMax(); - - this.day = this.spinbuttonDay.getValue(); - this.month = this.spinbuttonMonth.getValue(); - this.year = this.spinbuttonYear.getValue(); - this.daysInMonth = this.getDaysInMonth(this.year, this.month); - - this.updateSpinButtons(); -}; - -DatePickerSpinButtons.prototype.getDaysInMonth = function (year, month) { - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } - - switch (month) { - - case 0: - case 2: - case 4: - case 6: - case 7: - case 9: - case 11: - return 31; - - case 1: - return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); - - case 3: - case 5: - case 8: - case 10: - return 30; - - default: - break; - - } - - return -1; - -}; - -DatePickerSpinButtons.prototype.updateDay = function (day) { - this.day = day; - this.updateSpinButtons(); -}; - -DatePickerSpinButtons.prototype.updateMonth = function (month) { - this.month = month; - this.updateSpinButtons(); -}; - -DatePickerSpinButtons.prototype.updateYear = function (year) { - this.year = year; - this.updateSpinButtons(); -}; - -DatePickerSpinButtons.prototype.updatePreviousDayMonthAndYear = function () { - this.previousYear = this.year-1; - - this.previousMonth = this.month ? this.month - 1 : 11; - - this.previousDay = this.day - 1; - if (this.previousDay < 1) { - this.previousDay = this.getDaysInMonth(this.year, this.month); - } -}; - -DatePickerSpinButtons.prototype.updateNextDayMonthAndYear = function () { - this.nextYear = this.year+1; - this.nextMonth = this.month >= 11 ? 0 : this.month + 1; - - this.nextDay = this.day + 1; - var maxDay = this.getDaysInMonth(this.year, this.month); - if (this.nextDay > maxDay) { - this.nextDay = 1; - } -}; - -DatePickerSpinButtons.prototype.updateSpinButtons = function () { - - this.daysInMonth = this.getDaysInMonth(this.year, this.month); - this.spinbuttonDay.setValueMax(this.daysInMonth); - if (this.day > this.daysInMonth) { - this.spinbuttonDay.setValue(this.daysInMonth); - return; - } - - this.updatePreviousDayMonthAndYear(); - this.updateNextDayMonthAndYear(); - - this.spinbuttonDay.setPreviousValue(this.previousDay); - this.spinbuttonMonth.setPreviousValue(this.previousMonth); - this.spinbuttonYear.setPreviousValue(this.previousYear); - - this.spinbuttonDay.setNextValue(this.nextDay); - this.spinbuttonMonth.setNextValue(this.nextMonth); - this.spinbuttonYear.setNextValue(this.nextYear); - - - -}; - -// Initialize menu button date picker - -window.addEventListener('load' , function () { - - var dpsbs = document.querySelectorAll('.datepicker-spinbuttons'); - - dpsbs.forEach(function (dpsb) { - var datepicker = new DatePickerSpinButtons(dpsb); - datepicker.init(); - }); - -}); diff --git a/examples/datepicker/js/spinbutton-date.js b/examples/datepicker/js/spinbutton-date.js deleted file mode 100644 index 6946d3bd6f..0000000000 --- a/examples/datepicker/js/spinbutton-date.js +++ /dev/null @@ -1,222 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: spinbutton-date.js -*/ - -var SpinButtonDate = function (domNode, values, callback) { - - this.domNode = domNode; - this.values = values; - this.callback = callback; - this.wrap = true; - - var initialValue = domNode.getAttribute('aria-valuetext'); - - this.valueNode = domNode.querySelector('.value'); - this.previousValueNode = domNode.querySelector('.previous'); - this.nextValueNode = domNode.querySelector('.next'); - - this.increaseNode = domNode.querySelector('.increase'); - this.decreaseNode = domNode.querySelector('.decrease'); - - if (values) { - this.valueMin = 0; - this.valueMax = values.length-1; - if (initialValue) { - this.valueNow = values.indexOf(initialValue); - this.valueText = initialValue; - } - else { - this.valueNow = values.length / 2; - this.valueText = values[this.valueNow]; - } - } - else { - this.valueMin = parseInt(domNode.getAttribute('aria-valuemin')); - this.valueMax = parseInt(domNode.getAttribute('aria-valuemax')); - this.valueNow = parseInt(domNode.getAttribute('aria-valuenow')); - this.valueText = domNode.getAttribute('aria-valuenow'); - } - - this.keyCode = Object.freeze({ - 'UP': 38, - 'DOWN': 40, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36 - }); -}; - -// Initialize slider -SpinButtonDate.prototype.init = function () { - - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - - this.increaseNode.addEventListener('click', this.handleIncreaseClick.bind(this)); - this.decreaseNode.addEventListener('click', this.handleDecreaseClick.bind(this)); -}; - -SpinButtonDate.prototype.noWrap = function () { - this.wrap = false; -}; - -SpinButtonDate.prototype.getValue = function () { - return this.valueNow; -}; - -SpinButtonDate.prototype.setValue = function (value) { - - if (value > this.valueMax) { - if (this.wrap) { - value = this.valueMin; - } - else { - value = this.valueMax; - } - } - - if (value < this.valueMin) { - if (this.wrap) { - value = this.valueMax; - } - else { - value = this.valueMin; - } - } - - this.valueNow = value; - if (this.values) { - this.valueText = this.values[this.valueNow]; - } - else { - if (typeof value === 'number') { - this.valueText = parseInt(value); - } - } - - this.domNode.setAttribute('aria-valuenow', this.valueNow); - this.domNode.setAttribute('aria-valuetext', this.valueText); - - this.valueNode.innerHTML = this.valueText; - - if (this.callback) { - this.callback(this.valueNow); - } - -}; - -SpinButtonDate.prototype.getValueMin = function (value) { - return parseInt(this.domNode.getAttribute('aria-valuemin')); -}; - -SpinButtonDate.prototype.getValueMax = function (value) { - return parseInt(this.domNode.getAttribute('aria-valuemax')); -}; - -SpinButtonDate.prototype.setValueMax = function (value) { - this.domNode.setAttribute('aria-valuemax', value); - this.valueMax = value; -}; - - -SpinButtonDate.prototype.setPreviousValue = function (value) { - - if (this.values) { - value = this.values[value]; - } - - if (typeof value === 'number') { - if (value < this.valueMin) { - value = ' '; - } - else { - value = parseInt(value); - } - } - - this.previousValueNode.innerHTML = value; -}; - -SpinButtonDate.prototype.setNextValue = function (value) { - if (this.values) { - value = this.values[value]; - } - - if (typeof value === 'number') { - if (value > this.valueMax) { - value = ' '; - } - else { - value = parseInt(value); - } - } - - this.nextValueNode.innerHTML = value; -}; - -SpinButtonDate.prototype.handleKeyDown = function (event) { - - var flag = false; - - switch (event.keyCode) { - case this.keyCode.DOWN: - this.setValue(this.valueNow - 1); - flag = true; - break; - - case this.keyCode.UP: - this.setValue(this.valueNow + 1); - flag = true; - break; - - case this.keyCode.PAGEDOWN: - this.setValue(this.valueNow - 5); - flag = true; - break; - - case this.keyCode.PAGEUP: - this.setValue(this.valueNow + 5); - flag = true; - break; - - case this.keyCode.HOME: - this.setValue(this.valueMin); - flag = true; - break; - - case this.keyCode.END: - this.setValue(this.valueMax); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.preventDefault(); - event.stopPropagation(); - } - -}; - -SpinButtonDate.prototype.handleIncreaseClick = function (event) { - - this.setValue(this.valueNow + 1); - - event.preventDefault(); - event.stopPropagation(); - -}; - -SpinButtonDate.prototype.handleDecreaseClick = function (event) { - - this.setValue(this.valueNow - 1); - - event.preventDefault(); - event.stopPropagation(); - -}; From 1fc2bd36e51b9eb5bf50094d76fea9a688aef0f4 Mon Sep 17 00:00:00 2001 From: Matt King Date: Mon, 24 Jun 2019 19:27:37 -0700 Subject: [PATCH 058/137] Remove files copied to new issue1046-datepicker-dialog branch --- examples/datepicker/css/datepicker.css | 214 ----- .../datepicker/datepicker-menubutton.html | 778 ----------------- .../datepicker/js/datepicker-menubutton.js | 166 ---- examples/datepicker/js/datepicker.js | 823 ------------------ examples/datepicker/js/datepickerDay.js | 175 ---- 5 files changed, 2156 deletions(-) delete mode 100644 examples/datepicker/css/datepicker.css delete mode 100644 examples/datepicker/datepicker-menubutton.html delete mode 100644 examples/datepicker/js/datepicker-menubutton.js delete mode 100644 examples/datepicker/js/datepicker.js delete mode 100644 examples/datepicker/js/datepickerDay.js diff --git a/examples/datepicker/css/datepicker.css b/examples/datepicker/css/datepicker.css deleted file mode 100644 index 0e21ebd87e..0000000000 --- a/examples/datepicker/css/datepicker.css +++ /dev/null @@ -1,214 +0,0 @@ -.datepicker { - margin-top: 1em; -} - - -.datepicker button.icon { - border-style: none; - text-align: left; - background-color: white; - position: relative; - left: -25px; - top: 3px; -} - -.datepicker button.menubutton.icon { - left: -4px; -} - -.datepicker span.arrow { - margin: 0; - padding: 0; - display: none; - background: transparent; -} - -.datepicker span.arrow svg polygon { - stroke: gray; - stroke-width: 1px; - fill: gray; -} - -.datepicker[aria-expanded=false] span.arrow.up { - display: inline-block; - position: relative; - left: -23px; -} - -.datepicker[aria-expanded=true] span.arrow.down { - display: inline-block; - position: relative; - left: -23px; -} - -.datepicker input { - margin: 0; - width: 20%; -} - -.datepickerDialog { - width: 45%; - clear: both; - display: none; - border: 3px solid hsl(216, 80%, 55%); - margin-top: 1em; - border-radius: 5px; - padding: 0; - background-color: #FFF; -} - -.header { - cursor: default; - background-color: hsl(216, 80%, 55%); - padding: 7px; - font-weight: bold; - text-transform: uppercase; - color: white; - display: flex; - justify-content: space-around; -} - -.header span { - display: inline-block; -} - -.header button { - border-style: none; - background: transparent; -} - -.datepickerDialog button::-moz-focus-inner { - border: 0; -} - -.prevYear, -.prevMonth, -.nextMonth, -.nextYear { - padding: 4px; - width: 24px; - height: 24px; - color: white; -} - -.prevYear:focus, -.prevMonth:focus, -.nextMonth:focus, -.nextYear:focus { - padding: 2px; - border: 2px solid white; - border-radius: 4px; - outline: 0; -} - -.dialogButtonGroup { - text-align: right; - margin-top: 1em; - margin-bottom: 1em; - margin-right: 1em; -} - -.dialogButton { - padding: 5px; - margin-left: 1em; - width: 5em; - background-color: hsl(216, 80%, 92%); - font-size: 0.85em; - color: black; - outline: none; - border: 1px solid hsl(216, 80%, 92%); - border-radius: 5px; -} - -.dialogButton:focus { - padding: 4px; - border: 2px solid black; -} - -.fa-calendar-alt { - color: hsl(216, 89%, 72%); -} - -.datepicker .monthYear { - display: inline-block; - width: 12em; - text-align: center; -} - -table.dates { - width: 100%; - padding-left: 1em; - padding-right: 1em; - padding-top: 1em; -} - -table.dates th, -table.dates td { - text-align: center; -} - -.dateRow { - border: 1px solid black; -} - -.dateCell { - outline: 0; - border: 0; - padding: 0; - margin: 0; - height: 40px; - width: 40px; -} - -.dateButton { - padding: 0; - margin: 0; - line-height: inherit; - height: 100%; - width: 100%; - border: 1px solid #eee; - border-radius: 5px; - font-size: 15px; - background: #eee; -} - -.dateButton:focus, -.dateButton:hover { - padding: 0; - background-color: hsl(216, 80%, 92%); -} - -.dateButton:focus { - border-width: 2px; - border-color: rgb(100, 100, 100); - outline: 0; -} - -.dateButton[aria-selected] { - border-color: rgb(100, 100, 100); -} - -.dateButton[tabindex="0"] { - background-color: hsl(216, 80%, 92%); -} - -.disabled { - color: #afafaf; -} - -.disabled:hover { - color: black; -} - -.dateButton:disabled { - color: #777; - background-color: #fff; - border: none; - cursor: not-allowed; -} - -.datepicker .message { - position: absolute; - top: -30em; - left: -300em; -} diff --git a/examples/datepicker/datepicker-menubutton.html b/examples/datepicker/datepicker-menubutton.html deleted file mode 100644 index d2ce627b39..0000000000 --- a/examples/datepicker/datepicker-menubutton.html +++ /dev/null @@ -1,778 +0,0 @@ - - - - -Date Picker Example Using Menu Button| WAI-ARIA Authoring Practices 1.1 - - - - - - - - - - - - - - - - - -
    -

    Date Picker Example Using Menu Button

    -

    - NOTE: This example page is work in progress. - Please provide feedback in - issue 34. -

    -

    - The date picker is an example of the Menu Button design pattern that opens a dialog box. - The date picker dialog box in this example is opened when keyboard focus is moved to the calendar button and a space, return, down arrow key pressed or clicking on the calendar button. - The date picker dialog uses a grid pattern to show and select a date using the cursor keys. Additional buttons in the dialog box can be used for changing the month and year shown in the grid. -

    -
    -

    Example

    - - -
    - -
    - -
    - - - - - - -
    Opens datepicker dialog for previous date textbox
    - -
    - -
    - - -
    - -
    - -
    - -
    -

    Accessibility Features

    -

    There are three main featues of the menu button date picker example:

    -
      -
    • Date Textbox: Holds the value of the selected date.
    • -
    • Calendar Button: Opens the datepicker dialog box.
    • -
    • Date Picker Dialog Box: Displays a grid of dates for the user to select from and provide additional buttons to change the month and year of dates shown in the grid.
    • -
    - -

    The textbox has the accessible name of Date and button has the accessible name of Calendar.

    - - -

    Date Textbox

    - -
      -
    • Contains the date value.
    • -
    - -

    Calendar Button

    - -
      -
    • Opens and closes the date picker dialog box through keyboard and click events.
    • -
    • A live region provides information on how to open the date picker dialog when textbox receives focus (e.g. using down arrow key).
    • -
    • The accessible name of the button is updated when a date is selected to include the current date.
    • -
    • The title attribute describes the relationship between the button and the textbox.
    • -
    - -

    Date Picker Dialog

    - -
      -
    • The date picker dialog is a modal dialog box for selecting a date from a grid of dates for a particular month and year.
    • -
    • Additional buttons and keyboard commands are used to change month and year.
    • -
    • A live region announces changes in the month and year.
    • -
    • The dialog is opened through keyboard commands in the textbox or clicking on the change date button.
    • -
    - -
    - - - -
    -

    Keyboard Support

    - -
    -

    Calendar Button

    - - - - - - - - - - - - - -
    KeyFunction
    space, return and Down Arrow -
      -
    • Open the date picker dialog.
    • -
    • Move focus to current date.
    • -
    -
    -
    - -
    -

    Date Picker Dialog

    - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    ESCClose the dialog and move focus back to the calendar button.
    TAB -
      -
    • Move focus to next button or grid inside the dialog.
    • -
    • If on the last button (i.e. OK Button), moves button to the first button (i.e. Previous Year Button).
    • -
    -
    Shift +
    TAB
    -
      -
    • Move focus to previous button or grid inside the dialog.
    • -
    • If on the first button (i.e. Previous Year Button), moves button to the first button (i.e. OK Button).
    • -
    -
    -
    - -
    -

    Date Picker Dialog: Calendar Buttons

    - - - - - - - - - - - - - -
    KeyFunction
    Space,
    Return
    - Change the month and/or year for selecting a date from the grid or dates. -
    -
    - -
    -

    Date Picker Dialog: Date Grid

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    Space,
    Return
    -
      -
    • Select the date, close the dialog box and move focus to the calendar button.
    • -
    • The value of the date input is updated with the selected date in the from the selected date in the date grid.
    • -
    -
    Up ArrowMove the focus to the same day of the previous week.
    Down ArrowMove the focus to the same day of the next week.
    Right ArrowMove the focus to the next day.
    Left ArrowMove the focus to the previous day.
    HomeMove the focus to the first day (e.g Sunday) of the current week.
    EndMove the focus to the last day (e.g. Saturday) of the current week.
    PageUp -
      -
    • Change the gird of dates to the previous month.
    • -
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • -
    -
    Shift+
    PageUp
    -
      -
    • Change the grid of dates to the previous Year.
    • -
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • -
    -
    PageDown -
      -
    • Change the grid of dates to the next month.
    • -
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • -
    -
    Shift+
    PageDown
    -
      -
    • Change the grid of dates to the next Year.
    • -
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • -
    -
    TABMove focus to next button inside the dialog.
    Shift +
    TAB
    Move focus to previous button inside the dialog.
    ESC -
      -
    • Close the dialog and move focus back to calendar button.
    • -
    • The value of the date texbox is not updated.
    • -
    -
    -
    - -
    -

    Date Picker Buttons (OK and Cancel)

    - - - - - - - - - - - - - -
    KeyFunction
    Space,
    Return
    -
      -
    • The Cancel button closes the date picker dialog, moves focus to calendar button, does not update date in date input.
    • -
    • The OK button closes the date picker dialog, moves focus to calendar button, does update date in date input.
    • -
    -
    -
    -
    - -
    -

    Role, Property, State, and Tabindex Attributes

    - - -
    -

    Calendar Button

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    buttonbutton -
      -
    • The button has the default role of button.
    • -
    • Accessible name comes from aria-label attribute.
    • -
    -
    aria-label=Stringbutton -
      -
    • The initial value of accessible name is Calendar.
    • -
    • When user selected date, the accessible name will update to current date.
    • -
    -
    aria-haspopup=dialogbutton - Identifies the button as a menu button and follows the menu button design pattern. -
    aria-expanded=truebutton -
      -
    • Is defined when the date picker dialog is open.
    • -
    • When the date picker dialog is closed, the aria-expanded attribute is removed from element.
    • -
    -
    aria-controls=IDREFbutton - Provides a reference to a screen reader for direct navigation to the date picker dialog box. -
    aria-describedby=IDREFbutton - Provides a description to the user that calendar button is used to change the value of the textbox. -
    -
    - -
    -

    Date Picker Dialog

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    dialog - div - -
      -
    • Identifies div element as a dialog box container.
    • -
    • The accessible name comes from the aria-labelledby attribute.
    • -
    -
    aria-modal=truediv - Identifies that dialog box as a modal dialog box. -
    aria-labelledby=IDREFSdiv - Defines the accessible name for the dialog box. -
    -
    - -
    -

    Date Picker Dialog: Calendar Navigation Buttons

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    button - button - -
      -
    • The button element has the default role of button.
    • -
    • The accessible name comes from the aria-label attribute.
    • -
    -
    aria-controls=IDREFbutton - Provide a reference for screen reader to navigate to the date grid. -
    aria-label=Stringbutton - Defines the accessible name of the button (e.g. "Next Year"). -
    status - span - -
      -
    • When the month and/or year changes the content of the span element is updated.
    • -
    • Identifies the span element that contains the current month and year as a polite live region.
    • -
    -
    -
    - -
    -

    Date Picker Dialog: Date Grid

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    grid - table - -
      -
    • Identifies the table element serving as the grid widget container.
    • -
    • The accessible name comes from the aria-labelledby attribute.
    • -
    • Since the grid role is applied to a table element, the row, colheader, and gridcell roles do not need to be specified because they are implied by tr, th, and td tags.
    • -
    -
    aira-labelledby=IDREFtable -
      -
    • Define the accessible name for the grid using current month and year.
    • -
    -
    row - tr - - The tr element has a default role of row. -
    columnheader - th - - The default role for a th[scope="col"] element is columnheader. -
    gridcell - td - - Since the table element has a grid role, all descendant td elements will have the role of gridcell. -
    button - td > button - -
      -
    • The button element is used to identify the dates in calendar grid and are a child of the gridcell.
    • -
    • Accessible name for the button is the date which is defined by the text content of the button element.
    • -
    -
    - tabindex=0 - - td > button - - Identify the currently selected date and make it part of tab sequence of the dialog box. -
    - tabindex=-1 - - td > button - - Exclude the button from tab sequence to support grid cell navigation. -
    aria-selected=truetd > button -
      -
    • Identifies the currently selected date in the date input.
    • -
    • The aria-selected attribute is only set on the button representing the date in the date input, all other buttons do not have an aria-selected attribute.
    • -
    -
    -
    -
    - -
    -

    Javascript and CSS Source Code

    - -
    - -
    -

    HTML Source Code

    - -
    - - -
    -
    - - - diff --git a/examples/datepicker/js/datepicker-menubutton.js b/examples/datepicker/js/datepicker-menubutton.js deleted file mode 100644 index 2987d4b72b..0000000000 --- a/examples/datepicker/js/datepicker-menubutton.js +++ /dev/null @@ -1,166 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: datepicker-menubutton.js -*/ - -var MenuButtonInput = function (inputNode, buttonNode, messageNode, datepicker) { - this.inputNode = inputNode; - this.buttonNode = buttonNode; - this.messageNode = messageNode; - this.imageNode = false; - - this.datepicker = datepicker; - - this.ignoreFocusEvent = false; - this.ignoreBlurEvent = false; - - this.hasFocusFlag = false; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -MenuButtonInput.prototype.init = function () { - - this.buttonNode.addEventListener('click', this.handleClick.bind(this)); - this.buttonNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - - this.buttonNode.addEventListener('focus', this.handleFocus.bind(this)); - this.buttonNode.addEventListener('blur', this.handleBlur.bind(this)); - - - this.setMessage(''); -}; - -MenuButtonInput.prototype.handleKeyDown = function (event) { - var flag = false; - - switch (event.keyCode) { - - case this.keyCode.DOWN: - case this.keyCode.ENTER: - this.datepicker.show(); - this.datepicker.setFocusDay(); - flag = true; - break; - - case this.keyCode.ESC: - this.datepicker.hide(false); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenuButtonInput.prototype.handleFocus = function () { - if (this.isCollapsed()) { - this.setMessage('Use the down arrow key or the following change date button to move focus to the datepicker grid.'); - } -}; - -MenuButtonInput.prototype.handleBlur = function () { - this.setMessage(''); -}; - -MenuButtonInput.prototype.handleClick = function (event) { - - if (this.isCollapsed()) { - this.datepicker.show(); - } - else { - this.datepicker.hide(); - } - - event.stopPropagation(); - event.preventDefault(); - -}; - - -MenuButtonInput.prototype.setFocus = function () { - this.buttonNode.setAttribute('aria-label', 'Calendar, current date is ' + this.datepicker.getDateForButtonLabel()) - this.buttonNode.focus(); -}; - -MenuButtonInput.prototype.setAriaExpanded = function (flag) { - - if (flag) { - if (this.comboboxNode) { - this.comboboxNode.setAttribute('aria-expanded', 'true'); - } - this.buttonNode.setAttribute('aria-expanded', 'true'); - } - else { - if (this.comboboxNode) { - this.comboboxNode.setAttribute('aria-expanded', 'false'); - } - this.buttonNode.setAttribute('aria-expanded', 'false'); - } - -}; - -MenuButtonInput.prototype.getAriaExpanded = function () { - if (this.comboboxNode) { - return this.comboboxNode.getAttribute('aria-expanded') === 'true'; - } - return this.buttonNode.getAttribute('aria-expanded') === 'true'; -}; - -MenuButtonInput.prototype.isCollapsed = function () { - return this.inputNode.getAttribute('aria-expanded') !== 'true'; -}; - -MenuButtonInput.prototype.setDate = function (month, day, year) { - this.inputNode.value = (month + 1) + '/' + (day + 1) + '/' + year; -}; - -MenuButtonInput.prototype.getDate = function () { - return this.inputNode.value; -}; - -MenuButtonInput.prototype.setMessage = function (str) { - return this.messageNode.textContent = str; -}; - -MenuButtonInput.prototype.hasFocus = function () { - return this.hasFocusflag; -}; - -// Initialize menu button date picker - -window.addEventListener('load' , function () { - - var datePickers = document.querySelectorAll('.datepicker'); - - datePickers.forEach(function (dp) { - var inputNode = dp.querySelector('input'); - var buttonNode = dp.querySelector('button'); - var messageNode = dp.querySelector('.message'); - var dialogNode = dp.querySelector('[role=dialog]'); - - var datePicker = new DatePicker(null, inputNode, buttonNode, messageNode, dialogNode); - datePicker.init(); - }); - -}); diff --git a/examples/datepicker/js/datepicker.js b/examples/datepicker/js/datepicker.js deleted file mode 100644 index 318f14d1d9..0000000000 --- a/examples/datepicker/js/datepicker.js +++ /dev/null @@ -1,823 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: datepicker.js -*/ - -var DatePicker = function (comboboxNode, inputNode, buttonNode, messageNode, dialogNode) { - this.dayLabels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - this.inputNode = inputNode; - this.buttonNode = buttonNode; - this.messageNode = messageNode; - this.dialogNode = dialogNode; - - if (comboboxNode) { - this.dateInput = new ComboboxInput(comboboxNode, this.inputNode, this.buttonNode, this.messageNode, this); - } - else { - this.dateInput = new MenuButtonInput(this.inputNode, this.buttonNode, this.messageNode, this); - } - - this.MonthYearNode = this.dialogNode.querySelector('.monthYear'); - - this.prevYearNode = this.dialogNode.querySelector('.prevYear'); - this.prevMonthNode = this.dialogNode.querySelector('.prevMonth'); - this.nextMonthNode = this.dialogNode.querySelector('.nextMonth'); - this.nextYearNode = this.dialogNode.querySelector('.nextYear'); - - this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); - this.cancelButtonNode = this.dialogNode.querySelector('button[value="cancel"]'); - - this.tbodyNode = this.dialogNode.querySelector('table.dates tbody'); - - this.lastRowNode = null; - - var date = new Date(); - - this.year = date.getFullYear(); - this.month = date.getMonth(); - this.day = date.getDate() - 1; - - this.daysInCurrentMonth = this.getDaysInMonth(); - this.daysInLastMonth = this.getDaysInLastMonth(); - - this.days = []; - - this.selectedDay = new Date(this.year, this.month, this.day); - - this.currentDay = null; - - this.hasFocusFlag = false; - this.isMouseDownOnBackground = false; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); - -}; - -DatePicker.prototype.init = function () { - - this.dateInput.init(); - - this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); - this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); - - this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); - this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); - - this.prevMonthNode.addEventListener('click', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('click', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('click', this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('click', this.handleNextYearButton.bind(this)); - - this.prevMonthNode.addEventListener('keydown', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('keydown', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); - - this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); - - document.body.addEventListener('mousedown', this.handleBackgroundMouseDown.bind(this), true); - document.body.addEventListener('mouseup', this.handleBackgroundMouseUp.bind(this), true); - - // Create Grid of Dates - - this.tbodyNode.innerHTML = ''; - var index = 0; - for (var i = 0; i < 6;i++) { - var row = this.tbodyNode.insertRow(i); - this.lastRowNode = row; - row.classList.add('dateRow'); - for (var j = 0;j < 7; j++) { - var cell = document.createElement('td'); - cell.classList.add('dateCell'); - var cellButton = document.createElement('button'); - cellButton.classList.add('dateButton'); - cell.appendChild(cellButton); - row.appendChild(cell); - var dpDay = new DatePickerDay(cellButton, this, index, i, j); - dpDay.init(); - this.days.push(dpDay); - index++; - } - } - - this.updateGrid(); - this.setFocusDay(); -}; - -DatePicker.prototype.updateGrid = function (year, month) { - - var i; - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } - - this.MonthYearNode.innerHTML = this.monthLabels[month] + ' ' + year; - - this.daysInCurrentMonth = this.getDaysInMonth(year, month); - - var lastMonth = month - 1; - var lastYear = year; - if (lastMonth < 0) { - lastMonth = 11; - lastYear = year - 1; - } - - var daysInLastMonth = this.getDaysInMonth(lastYear, lastMonth); - this.daysInLastMonth = daysInLastMonth; - - var nextMonth = month + 1; - var nextYear = year; - if (nextMonth > 11) { - nextMonth = 1; - nextYear = year + 1; - } - - var firstDayOfMonth = new Date(year, month, 1); - var dayOfWeek = firstDayOfMonth.getDay(); - - for (i = dayOfWeek - 1; i >= 0; i--) { - daysInLastMonth--; - this.days[i].updateDay(true, lastYear, lastMonth, daysInLastMonth); - } - - for (i = 0; i < this.daysInCurrentMonth; i++) { - var dpDay = this.days[dayOfWeek + i]; - dpDay.updateDay(false, year, month, i); - if ((this.selectedDay.getFullYear() === year) && - (this.selectedDay.getMonth() === month) && - (this.selectedDay.getDate() === i)) { - dpDay.domNode.setAttribute('aria-selected', 'true'); - } - } - - var remainingButtons = 42 - this.daysInCurrentMonth - dayOfWeek; - - if (remainingButtons >= 7) { - remainingButtons = remainingButtons - 7; - this.hideLastRow(); - } - else { - this.showLastRow(); - } - - for (i = 0; i < remainingButtons; i++) { - this.days[dayOfWeek + this.daysInCurrentMonth + i].updateDay(true, nextYear, nextMonth, i); - } - -}; - -DatePicker.prototype.onFirstRow = function () { - var cd = this.currentDay; - var flag = cd.row === 0; - flag = flag || ((cd.row === 1) && this.days[cd.index - 7].isDisabled()); - return flag; -}; - -DatePicker.prototype.onLastRow = function () { - var cd = this.currentDay; - var flag = cd.row === 5; - flag = flag || ((cd.row === 3) && this.days[cd.index + 7].isDisabled()); - flag = flag || ((cd.row === 4) && this.days[cd.index + 7].isDisabled()); - return flag; -}; - - -// If after updating the grid the current day is on a disabled button move it to an enabled button in the same column -DatePicker.prototype.adjustCurrentDay = function (onFirstRow, onLastRow) { - var cd = this.currentDay; - - if (typeof onFirstRow !== 'boolean') { - onFirstRow = false; - } - - if (typeof onLastRow !== 'boolean') { - onLastRow = false; - } - - if (cd.isDisabled()) { - if (cd.row === 0) { - this.day = this.days[cd.index + 7].day; - } - else { - if (this.days[cd.index - 7].isDisabled()) { - this.day = this.days[cd.index - 14].day; - } - else { - this.day = this.days[cd.index - 7].day; - } - } - } - else { - if (onFirstRow && (cd.row === 1) && (!this.days[cd.index - 7].isDisabled())) { - this.day = this.days[cd.index - 7].day; - } - else { - if (onLastRow && - ((cd.row === 3) || (cd.row === 4)) && - (!this.days[cd.index + 7].isDisabled())) { - this.day = this.days[cd.index + 7].day; - } - else { - this.day = cd.day; - } - } - } -}; - -DatePicker.prototype.hideLastRow = function () { - this.lastRowNode.style.visibility = 'hidden'; -}; - -DatePicker.prototype.showLastRow = function () { - this.lastRowNode.style.visibility = 'visible'; -}; - -DatePicker.prototype.setFocusDay = function (flag) { - - if (typeof flag !== 'boolean') { - flag = true; - } - - this.dateInput.setMessage(''); - - function checkDay (d) { - d.domNode.setAttribute('tabindex', '-1'); - if ((d.day == this.day) && - (d.month == this.month)) { - this.currentDay = d; - d.domNode.setAttribute('tabindex', '0'); - - if (flag) { - if (!this.hasFocusFlag) { - this.dateInput.setMessage('Use the cursor keys to navigate the date picker grid.'); - } - - this.hasFocusFlag = true; - d.domNode.focus(); - } - } - } - - this.days.forEach(checkDay.bind(this)); -}; - -DatePicker.prototype.updateDate = function (year, month, day) { - this.year = year; - this.month = month; - this.day = day; -}; - -DatePicker.prototype.getDaysInLastMonth = function (year, month) { - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } - - var lastMonth = month - 1; - var lastYear = year; - if (lastMonth < 0) { - lastMonth = 11; - lastYear = year - 1; - } - - return this.getDaysInMonth(lastYear, lastMonth); - -}; - -DatePicker.prototype.getDaysInMonth = function (year, month) { - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } - - switch (month) { - - case 0: - case 2: - case 4: - case 6: - case 7: - case 9: - case 11: - return 31; - - case 1: - return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); - - case 3: - case 5: - case 8: - case 10: - return 30; - - default: - break; - - } - - return -1; - -}; - -DatePicker.prototype.show = function () { - - this.dialogNode.style.display = 'block'; - this.dialogNode.style.zIndex = 2; - - this.dateInput.setAriaExpanded(true); - this.getDateInput(); - this.updateGrid(); - - return this.hasFocusFlag; -}; - -DatePicker.prototype.isOpen = function () { - return this.dateInput.getAriaExpanded(); -}; - -DatePicker.prototype.hide = function (ignore) { - - if (typeof ignore !== 'boolean') { - ignore = true; - } - - this.dialogNode.style.display = 'none'; - this.dateInput.setAriaExpanded(false); - - this.hasFocusFlag = false; - this.dateInput.ignoreFocusEvent = ignore; - this.dateInput.setFocus(); -}; - -DatePicker.prototype.handleBackgroundMouseDown = function (event) { - console.log('[DatePicker][handleBackgroundMouseDown]'); - if (!this.inputNode.parentNode.contains(event.target) && - !this.dialogNode.contains(event.target)) { - - this.isMouseDownOnBackground = true; - console.log('[DatePicker][handleBackgroundMouseDown][isMouseDownOnBackground]: ' + this.isMouseDownOnBackground); - - if (this.isOpen()) { - this.hide(); - event.stopPropagation(); - event.preventDefault(); - } - } -}; - -DatePicker.prototype.handleBackgroundMouseUp = function (event) { - console.log('[DatePicker][handleBackgroundMouseUp]'); - this.isMouseDownOnBackground = false; -}; - - -DatePicker.prototype.handleOkButton = function (event) { - var flag = false; - - switch (event.type) { - case 'keydown': - - switch (event.keyCode) { - case this.keyCode.ENTER: - case this.keyCode.SPACE: - - this.setTextboxDate(); - - this.hide(); - flag = true; - break; - - case this.keyCode.TAB: - if (!event.shiftKey) { - this.prevYearNode.focus(); - flag = true; - } - break; - - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - default: - break; - - } - break; - - case 'click': - this.setTextboxDate(); - this.hide(); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.handleCancelButton = function (event) { - var flag = false; - - switch (event.type) { - case 'keydown': - - switch (event.keyCode) { - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.hide(); - flag = true; - break; - - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - default: - break; - - } - break; - - case 'click': - this.hide(); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.handleNextYearButton = function (event) { - var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); - - switch (event.type) { - - case 'keydown': - - switch (event.keyCode) { - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.moveToNextYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - if (!this.hasFocusFlag) { - this.dateInput.ignoreBlurEvent = true; - } - this.moveToNextYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.handlePreviousYearButton = function (event) { - var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); - - switch (event.type) { - - case 'keydown': - - switch (event.keyCode) { - - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.moveToPreviousYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - flag = true; - break; - - case this.keyCode.TAB: - if (event.shiftKey) { - this.okButtonNode.focus(); - flag = true; - } - break; - - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - default: - break; - } - - break; - - case 'click': - if (!this.hasFocusFlag) { - this.dateInput.ignoreBlurEvent = true; - } - this.moveToPreviousYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.handleNextMonthButton = function (event) { - var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); - - switch (event.type) { - - case 'keydown': - - switch (event.keyCode) { - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.moveToNextMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - if (!this.hasFocusFlag) { - this.dateInput.ignoreBlurEvent = true; - } - this.moveToNextMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.handlePreviousMonthButton = function (event) { - var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); - - switch (event.type) { - - case 'keydown': - - switch (event.keyCode) { - case this.keyCode.ESC: - this.hide(); - flag = true; - break; - - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.moveToPreviousMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - if (!this.hasFocusFlag) { - this.dateInput.ignoreBlurEvent = true; - } - this.moveToPreviousMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); - this.setFocusDay(false); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePicker.prototype.moveToNextYear = function () { - this.year++; - this.updateGrid(); -}; - -DatePicker.prototype.moveToPreviousYear = function () { - this.year--; - this.updateGrid(); -}; - -DatePicker.prototype.moveToPreviousMonth = function () { - this.month--; - if (this.month < 0) { - this.month = 11; - this.year--; - } - this.updateGrid(); -}; - -DatePicker.prototype.moveToNextMonth = function () { - this.month++; - if (this.month > 11) { - this.month = 0; - this.year++; - } - this.updateGrid(); -}; - -DatePicker.prototype.moveToDay = function (day, month, year) { - this.day = day; - this.month = month; - this.year = year; - this.updateGrid(); - this.setFocusDay(); -}; - -DatePicker.prototype.moveFocusToNextDay = function () { - - this.day++; - if (this.daysInCurrentMonth <= this.day) { - this.day = 0; - this.moveToNextMonth(); - } - this.setFocusDay(); -}; - -DatePicker.prototype.moveFocusToNextWeek = function () { - - this.day += 7; - if (this.daysInCurrentMonth <= this.day) { - this.day = this.day - this.daysInCurrentMonth; - this.moveToNextMonth(); - } - this.setFocusDay(); -}; - -DatePicker.prototype.moveFocusToPreviousDay = function () { - - this.day--; - if (this.day < 0) { - this.moveToPreviousMonth(); - this.day = this.daysInCurrentMonth - 1; - } - this.setFocusDay(); -}; - -DatePicker.prototype.moveFocusToPreviousWeek = function () { - this.day -= 7; - if (this.day < 0) { - this.day = this.daysInLastMonth + this.day; - this.moveToPreviousMonth(); - } - this.setFocusDay(); -}; - -DatePicker.prototype.moveFocusToFirstDayOfWeek = function () { - - this.day = this.day - this.currentDay.column; - - if (this.day < 0) { - this.day = this.daysInLastMonth + this.day; - this.moveToPreviousMonth(); - } - this.setFocusDay(); - -}; - -DatePicker.prototype.moveFocusToLastDayOfWeek = function () { - - this.day = this.day + (6 - this.currentDay.column); - - if (this.daysInCurrentMonth <= this.day) { - this.day = this.day - this.daysInCurrentMonth; - this.moveToNextMonth(); - } - this.setFocusDay(); - -}; - -DatePicker.prototype.setTextboxDate = function () { - this.dateInput.setDate(this.month, this.day, this.year); -}; - -DatePicker.prototype.getDateInput = function () { - - var parts = this.dateInput.getDate().split('/'); - - if ((parts.length === 3) && - Number.isInteger(parseInt(parts[0])) && - Number.isInteger(parseInt(parts[1])) && - Number.isInteger(parseInt(parts[2]))) { - this.month = parseInt(parts[0]) - 1; - this.day = parseInt(parts[1]) - 1; - this.year = parseInt(parts[2]); - } - else { - // If not a valid date (MM/DD/YY) initialize with todays date - var date = new Date(); - - this.year = date.getFullYear(); - this.month = date.getMonth(); - this.day = date.getDate() - 1; - } - - this.daysInCurrentMonth = this.getDaysInMonth(); - this.daysInLastMonth = this.getDaysInLastMonth(); - - this.selectedDay = new Date(this.year, this.month, this.day); - -}; - -DatePicker.prototype.getDateForButtonLabel = function () { - this.selectedDay = new Date(this.year, this.month, this.day + 1); - var label = this.dayLabels[this.selectedDay.getDay()]; - label += ' ' + this.monthLabels[this.selectedDay.getMonth()]; - label += ' ' + (this.selectedDay.getDate()); - label += ', ' + this.selectedDay.getFullYear(); - return label; -}; - diff --git a/examples/datepicker/js/datepickerDay.js b/examples/datepicker/js/datepickerDay.js deleted file mode 100644 index aaf6c5d7a9..0000000000 --- a/examples/datepicker/js/datepickerDay.js +++ /dev/null @@ -1,175 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: datepickerDay.js -*/ - -var DatePickerDay = function (domNode, datepicker, index, row, column) { - - this.index = index; - this.row = row; - this.column = column; - - this.year = -1; - this.month = -1; - this.day = -1; - - this.domNode = domNode; - this.datepicker = datepicker; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -DatePickerDay.prototype.init = function () { - this.domNode.setAttribute('tabindex', '-1'); - this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - - this.domNode.innerHTML = '-1'; - -}; - -DatePickerDay.prototype.isDisabled = function () { - return this.domNode.classList.contains('disabled'); -}; - -DatePickerDay.prototype.updateDay = function (disable, year, month, day) { - - if (disable) { - this.domNode.classList.add('disabled'); - } - else { - this.domNode.classList.remove('disabled'); - } - - this.year = year; - this.month = month; - this.day = day; - - this.domNode.innerHTML = day + 1; - this.domNode.setAttribute('tabindex', '-1'); - this.domNode.removeAttribute('aria-selected'); - -}; - -DatePickerDay.prototype.handleKeyDown = function (event) { - var flag = false; - var onFirstRow = this.datepicker.onFirstRow(); - var onLastRow = this.datepicker.onLastRow(); - - switch (event.keyCode) { - - case this.keyCode.ESC: - this.datepicker.hide(); - break; - - case this.keyCode.TAB: - this.datepicker.cancelButtonNode.focus(); - if (event.shiftKey) { - this.datepicker.nextYearNode.focus(); - } - flag = true; - break; - - case this.keyCode.ENTER: - case this.keyCode.SPACE: - this.datepicker.setTextboxDate(); - this.datepicker.hide(); - flag = true; - break; - - case this.keyCode.RIGHT: - this.datepicker.moveFocusToNextDay(); - flag = true; - break; - - case this.keyCode.LEFT: - this.datepicker.moveFocusToPreviousDay(); - flag = true; - break; - - case this.keyCode.DOWN: - this.datepicker.moveFocusToNextWeek(); - flag = true; - break; - - case this.keyCode.UP: - this.datepicker.moveFocusToPreviousWeek(); - flag = true; - break; - - case this.keyCode.PAGEUP: - if (event.shiftKey) { - this.datepicker.moveToPreviousYear(); - } - else { - this.datepicker.moveToPreviousMonth(); - } - this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); - this.datepicker.setFocusDay(); - flag = true; - break; - - case this.keyCode.PAGEDOWN: - if (event.shiftKey) { - this.datepicker.moveToNextYear(); - } - else { - this.datepicker.moveToNextMonth(); - } - this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); - this.datepicker.setFocusDay(); - flag = true; - break; - - case this.keyCode.HOME: - this.datepicker.moveFocusToFirstDayOfWeek(); - flag = true; - break; - - case this.keyCode.END: - this.datepicker.moveFocusToLastDayOfWeek(); - flag = true; - break; - - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } - -}; - -DatePickerDay.prototype.handleMouseDown = function (event) { - this.datepicker.day = this.day; - - if (this.isDisabled()) { - this.datepicker.moveToDay(this.day, this.month, this.year); - } - else { - if (!this.datepicker.dateInput.hasFocus()) { - this.datepicker.dateInput.ignoreBlurEvent = true; - } - this.datepicker.setTextboxDate(); - this.datepicker.hide(); - } - - event.stopPropagation(); - event.preventDefault(); - -}; From 1d88594a1d7733d169d66949089182a8a7f1469b Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 6 Jul 2019 16:18:58 -0500 Subject: [PATCH 059/137] updating example to be compatible with date gird used in dialog date picker example --- ...datepicker.css => datepicker-combobox.css} | 103 ++++++++++++------ .../aria1.1pattern/datepicker-combobox.html | 29 ++--- ...ickerDay.js => datepicker-combobox-day.js} | 12 +- ...icker.js => datepicker-combobox-dialog.js} | 88 +++++++-------- .../aria1.1pattern/js/datepicker-combobox.js | 11 +- 5 files changed, 137 insertions(+), 106 deletions(-) rename examples/combobox/aria1.1pattern/css/{datepicker.css => datepicker-combobox.css} (61%) rename examples/combobox/aria1.1pattern/js/{datepickerDay.js => datepicker-combobox-day.js} (89%) rename examples/combobox/aria1.1pattern/js/{datepicker.js => datepicker-combobox-dialog.js} (85%) diff --git a/examples/combobox/aria1.1pattern/css/datepicker.css b/examples/combobox/aria1.1pattern/css/datepicker-combobox.css similarity index 61% rename from examples/combobox/aria1.1pattern/css/datepicker.css rename to examples/combobox/aria1.1pattern/css/datepicker-combobox.css index 078b08b29d..22870103ae 100644 --- a/examples/combobox/aria1.1pattern/css/datepicker.css +++ b/examples/combobox/aria1.1pattern/css/datepicker-combobox.css @@ -2,7 +2,6 @@ margin-top: 1em; } - .datepicker button.icon { border-style: none; text-align: left; @@ -12,7 +11,7 @@ top: 3px; } -.datepicker button.menubutton.icon { +.datepicker button.icon { left: -4px; } @@ -77,38 +76,76 @@ background: transparent; } -.datepickerDialog button::-moz-focus-inner { +.datepicker .datepickerDialog { + position: absolute; + width: 45%; + clear: both; + display: none; + border: 3px solid hsl(216, 80%, 55%); + margin-top: 1em; + border-radius: 5px; + padding: 0; + background-color: #fff; +} + +.datepicker .header { + cursor: default; + background-color: hsl(216, 80%, 55%); + padding: 7px; + font-weight: bold; + text-transform: uppercase; + color: white; + display: flex; + justify-content: space-around; +} + +.datepicker .header h2 { + margin: 0; + padding: 0; + display: inline-block; + font-size: 1em; + color: white; + text-transform: none; + font-weight: bold; +} + +.datepicker .header button { + border-style: none; + background: transparent; +} + +.datepicker .datepickerDialog button::-moz-focus-inner { border: 0; } -.prevYear, -.prevMonth, -.nextMonth, -.nextYear { +.datepicker .prevYear, +.datepicker .prevMonth, +.datepicker .nextMonth, +.datepicker .nextYear { padding: 4px; width: 24px; height: 24px; color: white; } -.prevYear:focus, -.prevMonth:focus, -.nextMonth:focus, -.nextYear:focus { +.datepicker .prevYear:focus, +.datepicker .prevMonth:focus, +.datepicker .nextMonth:focus, +.datepicker .nextYear:focus { padding: 2px; border: 2px solid white; border-radius: 4px; outline: 0; } -.dialogButtonGroup { +.datepicker .dialogButtonGroup { text-align: right; margin-top: 1em; margin-bottom: 1em; margin-right: 1em; } -.dialogButton { +.datepicker .dialogButton { padding: 5px; margin-left: 1em; width: 5em; @@ -120,12 +157,12 @@ border-radius: 5px; } -.dialogButton:focus { +.datepicker .dialogButton:focus { padding: 4px; border: 2px solid black; } -.fa-calendar-alt { +.datepicker .fa-calendar-alt { color: hsl(216, 89%, 72%); } @@ -135,23 +172,23 @@ text-align: center; } -table.dates { +.datepicker table.dates { width: 100%; padding-left: 1em; padding-right: 1em; padding-top: 1em; } -table.dates th, -table.dates td { +.datepicker table.dates th, +.datepicker table.dates td { text-align: center; } -.dateRow { +.datepicker .dateRow { border: 1px solid black; } -.dateCell { +.datepicker .dateCell { outline: 0; border: 0; padding: 0; @@ -160,7 +197,7 @@ table.dates td { width: 40px; } -.dateButton { +.datepicker .dateButton { padding: 0; margin: 0; line-height: inherit; @@ -172,35 +209,35 @@ table.dates td { background: #eee; } -.dateButton:focus, -.dateButton:hover { +.datepicker .dateButton:focus, +.datepicker .dateButton:hover { padding: 0; background-color: hsl(216, 80%, 92%); } -.dateButton:focus { +.datepicker .dateButton:focus { border-width: 2px; border-color: rgb(100, 100, 100); outline: 0; } -.dateButton[aria-selected] { +.datepicker .dateButton[aria-selected] { border-color: rgb(100, 100, 100); } -.dateButton[tabindex="0"] { +.datepicker .dateButton[tabindex="0"] { background-color: hsl(216, 80%, 92%); } -.disabled { +.datepicker .disabled { color: #afafaf; } -.disabled:hover { +.datepicker .disabled:hover { color: black; } -.dateButton:disabled { +.datepicker .dateButton:disabled { color: #777; background-color: #fff; border: none; @@ -208,7 +245,9 @@ table.dates td { } .datepicker .message { - position: absolute; - top: -30em; - left: -300em; + padding-top: 0.25em; + padding-left: 1em; + height: 1.75em; + background: hsl(216, 80%, 55%); + color: white; } diff --git a/examples/combobox/aria1.1pattern/datepicker-combobox.html b/examples/combobox/aria1.1pattern/datepicker-combobox.html index 8a284f9078..d340a10b3a 100644 --- a/examples/combobox/aria1.1pattern/datepicker-combobox.html +++ b/examples/combobox/aria1.1pattern/datepicker-combobox.html @@ -6,17 +6,16 @@ - - + + + + + - - - - - + - - + +
    -

    Date Picker Example Using ARIA 1.1 Combobox

    +

    Date Picker Example Using ARIA 1.1 Combobox (test)

    NOTE: This example page is work in progress. Please provide feedback in From 478953791715165c6b7297b6256cde637f76b3e6 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 14 Jul 2019 12:30:21 -0500 Subject: [PATCH 067/137] test commit 2 --- examples/combobox/aria1.1pattern/combo-11-datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index 85bbea8297..311769bf6b 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -28,7 +28,7 @@

    -

    Date Picker Example Using ARIA 1.1 Combobox (test)

    +

    Date Picker Example Using ARIA 1.1 Combobox

    NOTE: This example page is work in progress. Please provide feedback in From 7b5afebd26a3cd80da107de5a45a307336515f68 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 10:32:03 -0500 Subject: [PATCH 068/137] updated date picker code to use the Date object like the datepicker dialog example --- .../aria1.1pattern/combo-11-datepicker.html | 27 +- .../js/datepicker-combobox-day.js | 35 +- .../js/datepicker-combobox-dialog.js | 365 +++++------------- .../aria1.1pattern/js/datepicker-combobox.js | 4 +- 4 files changed, 117 insertions(+), 314 deletions(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index 311769bf6b..b2c67aecb6 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -634,18 +634,6 @@

    Change Date Button

    - - button - - button - -
      -
    • The button has the default role of button.
    • -
    • Accessible name comes from aria-labelledby attribute.
    • -
    • Button is removed from tab order of the page, since the dialog can be opened from the date input with the keyboard.
    • -
    - - tabindex=-1 @@ -752,19 +740,6 @@

    Date Picker Dialog: Calendar Navigation Buttons

    - - button - - - button - - -
      -
    • The button element has the default role of button.
    • -
    • The accessible name comes from the aria-label attribute.
    • -
    - - aria-label=String @@ -781,7 +756,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

      -
    • When the month and/or year changes the content of the span element is updated.
    • +
    • When the month and/or year changes the content of the h2 element is updated.
    • The use of the polite value means the screen reader will not interrupt other speech to announce the change in month and/or year.
    diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js index 6ba847bc70..2b5b811191 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js @@ -11,9 +11,7 @@ var DatePickerComboboxDay = function (domNode, datepicker, index, row, column) { this.row = row; this.column = column; - this.year = -1; - this.month = -1; - this.day = -1; + this.day = new Date(); this.domNode = domNode; this.datepicker = datepicker; @@ -48,7 +46,7 @@ DatePickerComboboxDay.prototype.isDisabled = function () { return this.domNode.classList.contains('disabled'); }; -DatePickerComboboxDay.prototype.updateDay = function (disable, year, month, day) { +DatePickerComboboxDay.prototype.updateDay = function (disable, day) { if (disable) { this.domNode.classList.add('disabled'); @@ -57,20 +55,28 @@ DatePickerComboboxDay.prototype.updateDay = function (disable, year, month, day) this.domNode.classList.remove('disabled'); } - this.year = year; - this.month = month; - this.day = day; + this.day = new Date(day); - this.domNode.innerHTML = day + 1; + this.domNode.innerHTML = this.day.getDate(); this.domNode.setAttribute('tabindex', '-1'); this.domNode.removeAttribute('aria-selected'); + var d = this.day.getDate().toString(); + if (this.day.getDate() < 9) { + d = '0' + d; + } + + var m = this.day.getMonth() + 1; + if (this.day.getMonth() < 9) { + m = '0' + m; + } + + this.domNode.setAttribute('data-date', this.day.getFullYear() + '-' + m + '-' + d); + }; DatePickerComboboxDay.prototype.handleKeyDown = function (event) { var flag = false; - var onFirstRow = this.datepicker.onFirstRow(); - var onLastRow = this.datepicker.onLastRow(); switch (event.keyCode) { @@ -123,7 +129,6 @@ DatePickerComboboxDay.prototype.handleKeyDown = function (event) { else { this.datepicker.moveToPreviousMonth(); } - this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); this.datepicker.setFocusDay(); flag = true; break; @@ -135,7 +140,6 @@ DatePickerComboboxDay.prototype.handleKeyDown = function (event) { else { this.datepicker.moveToNextMonth(); } - this.datepicker.adjustCurrentDay(onFirstRow, onLastRow); this.datepicker.setFocusDay(); flag = true; break; @@ -163,13 +167,10 @@ DatePickerComboboxDay.prototype.handleMouseDown = function (event) { this.datepicker.day = this.day; if (this.isDisabled()) { - this.datepicker.moveToDay(this.day, this.month, this.year); + this.datepicker.moveFocusToDay(this.day); } else { - if (!this.datepicker.dateInput.hasFocus()) { - this.datepicker.dateInput.ignoreBlurEvent = true; - } - this.datepicker.setTextboxDate(); + this.datepicker.setTextboxDate(this.day); this.datepicker.hide(); } diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js index 6eae257e48..f63bfd890d 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js @@ -42,14 +42,13 @@ var DatePickerCombobox = function (comboboxNode, inputNode, buttonNode, dialogNo this.month = date.getMonth(); this.day = date.getDate() - 1; - this.daysInCurrentMonth = this.getDaysInMonth(); - this.daysInLastMonth = this.getDaysInLastMonth(); - this.days = []; - this.selectedDay = new Date(this.year, this.month, this.day); + this.focusDay = new Date(); + this.selectedDay = new Date(0,0,1); - this.currentDay = null; + this.daysInCurrentMonth = this.getDaysInMonth(); + this.daysInLastMonth = this.getDaysInLastMonth(); this.hasFocusFlag = false; this.isMouseDownOnBackground = false; @@ -99,11 +98,11 @@ DatePickerCombobox.prototype.init = function () { this.tbodyNode.innerHTML = ''; var index = 0; - for (var i = 0; i < 6;i++) { + for (var i = 0; i < 6; i++) { var row = this.tbodyNode.insertRow(i); this.lastRowNode = row; row.classList.add('dateRow'); - for (var j = 0;j < 7; j++) { + for (var j = 0; j < 7; j++) { var cell = document.createElement('td'); cell.classList.add('dateCell'); var cellButton = document.createElement('button'); @@ -121,129 +120,39 @@ DatePickerCombobox.prototype.init = function () { this.setFocusDay(); }; -DatePickerCombobox.prototype.updateGrid = function (year, month) { - - var i; - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } +DatePickerCombobox.prototype.updateGrid = function () { - this.MonthYearNode.innerHTML = this.monthLabels[month] + ' ' + year; + var i, flag; + var fd = this.focusDay; - this.daysInCurrentMonth = this.getDaysInMonth(year, month); + this.MonthYearNode.innerHTML = this.monthLabels[fd.getMonth()] + ' ' + fd.getFullYear(); - var lastMonth = month - 1; - var lastYear = year; - if (lastMonth < 0) { - lastMonth = 11; - lastYear = year - 1; - } - - var daysInLastMonth = this.getDaysInMonth(lastYear, lastMonth); - this.daysInLastMonth = daysInLastMonth; - - var nextMonth = month + 1; - var nextYear = year; - if (nextMonth > 11) { - nextMonth = 1; - nextYear = year + 1; - } - - var firstDayOfMonth = new Date(year, month, 1); + var firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1); + var daysInMonth = new Date(fd.getFullYear(), fd.getMonth() + 1, 0).getDate(); var dayOfWeek = firstDayOfMonth.getDay(); - for (i = dayOfWeek - 1; i >= 0; i--) { - daysInLastMonth--; - this.days[i].updateDay(true, lastYear, lastMonth, daysInLastMonth); - } + firstDayOfMonth.setDate(firstDayOfMonth.getDate() - dayOfWeek); - for (i = 0; i < this.daysInCurrentMonth; i++) { - var dpDay = this.days[dayOfWeek + i]; - dpDay.updateDay(false, year, month, i); - if ((this.selectedDay.getFullYear() === year) && - (this.selectedDay.getMonth() === month) && - (this.selectedDay.getDate() === i)) { - dpDay.domNode.setAttribute('aria-selected', 'true'); + var d = new Date(firstDayOfMonth); + + for (i = 0; i < this.days.length; i++) { + flag = d.getMonth() != fd.getMonth(); + this.days[i].updateDay(flag, d); + if ((d.getFullYear() == this.selectedDay.getFullYear()) && + (d.getMonth() == this.selectedDay.getMonth()) && + (d.getDate() == this.selectedDay.getDate())) { + this.days[i].domNode.setAttribute('aria-selected', 'true'); } + d.setDate(d.getDate() + 1); } - var remainingButtons = 42 - this.daysInCurrentMonth - dayOfWeek; - - if (remainingButtons >= 7) { - remainingButtons = remainingButtons - 7; + if ((dayOfWeek + daysInMonth) < 36) { this.hideLastRow(); } else { this.showLastRow(); } - for (i = 0; i < remainingButtons; i++) { - this.days[dayOfWeek + this.daysInCurrentMonth + i].updateDay(true, nextYear, nextMonth, i); - } - -}; - -DatePickerCombobox.prototype.onFirstRow = function () { - var cd = this.currentDay; - var flag = cd.row === 0; - flag = flag || ((cd.row === 1) && this.days[cd.index - 7].isDisabled()); - return flag; -}; - -DatePickerCombobox.prototype.onLastRow = function () { - var cd = this.currentDay; - var flag = cd.row === 5; - flag = flag || ((cd.row === 3) && this.days[cd.index + 7].isDisabled()); - flag = flag || ((cd.row === 4) && this.days[cd.index + 7].isDisabled()); - return flag; -}; - - -// If after updating the grid the current day is on a disabled button move it to an enabled button in the same column -DatePickerCombobox.prototype.adjustCurrentDay = function (onFirstRow, onLastRow) { - var cd = this.currentDay; - - if (typeof onFirstRow !== 'boolean') { - onFirstRow = false; - } - - if (typeof onLastRow !== 'boolean') { - onLastRow = false; - } - - if (cd.isDisabled()) { - if (cd.row === 0) { - this.day = this.days[cd.index + 7].day; - } - else { - if (this.days[cd.index - 7].isDisabled()) { - this.day = this.days[cd.index - 14].day; - } - else { - this.day = this.days[cd.index - 7].day; - } - } - } - else { - if (onFirstRow && (cd.row === 1) && (!this.days[cd.index - 7].isDisabled())) { - this.day = this.days[cd.index - 7].day; - } - else { - if (onLastRow && - ((cd.row === 3) || (cd.row === 4)) && - (!this.days[cd.index + 7].isDisabled())) { - this.day = this.days[cd.index + 7].day; - } - else { - this.day = cd.day; - } - } - } }; DatePickerCombobox.prototype.hideLastRow = function () { @@ -260,17 +169,15 @@ DatePickerCombobox.prototype.setFocusDay = function (flag) { flag = true; } - this.dateInput.setMessage(''); + var fd = this.focusDay; function checkDay (d) { d.domNode.setAttribute('tabindex', '-1'); - if ((d.day == this.day) && - (d.month == this.month)) { - this.currentDay = d; + if ((d.day.getDate() == fd.getDate()) && + (d.day.getMonth() == fd.getMonth()) && + (d.day.getFullYear() == fd.getFullYear())) { d.domNode.setAttribute('tabindex', '0'); - if (flag) { - this.hasFocusFlag = true; d.domNode.focus(); } } @@ -279,70 +186,26 @@ DatePickerCombobox.prototype.setFocusDay = function (flag) { this.days.forEach(checkDay.bind(this)); }; -DatePickerCombobox.prototype.updateDate = function (year, month, day) { - this.year = year; - this.month = month; - this.day = day; -}; - -DatePickerCombobox.prototype.getDaysInLastMonth = function (year, month) { - - if (typeof year !== 'number') { - year = this.year; +DatePickerCombobox.prototype.updateDate = function (day) { + var d = this.focusDay; + this.focusDay = day; + if ((d.getMonth() !== day.getMonth()) || + (d.getFullYear() !== day.getFullYear())) { + this.updateGrid(); + this.setFocusDay(); } - - if (typeof month !== 'number') { - month = this.month; - } - - var lastMonth = month - 1; - var lastYear = year; - if (lastMonth < 0) { - lastMonth = 11; - lastYear = year - 1; - } - - return this.getDaysInMonth(lastYear, lastMonth); - }; -DatePickerCombobox.prototype.getDaysInMonth = function (year, month) { - - if (typeof year !== 'number') { - year = this.year; - } - - if (typeof month !== 'number') { - month = this.month; - } - - switch (month) { - - case 0: - case 2: - case 4: - case 6: - case 7: - case 9: - case 11: - return 31; - - case 1: - return (((this.yearIndex % 4 === 0) && (this.yearIndex % 100 !== 0) && (this.yearIndex % 400 === 0)) ? 29 : 28); - - case 3: - case 5: - case 8: - case 10: - return 30; - - default: - break; - - } - - return -1; +DatePickerCombobox.prototype.getDaysInLastMonth = function () { + var fd = this.focusDay; + var lastDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 0); + return lastDayOfMonth.getDate(); +}; +DatePickerCombobox.prototype.getDaysInMonth = function () { + var fd = this.focusDay; + var lastDayOfMonth = new Date(fd.getFullYear(), fd.getMonth() + 1, 0); + return lastDayOfMonth.getDate(); }; DatePickerCombobox.prototype.show = function () { @@ -487,8 +350,6 @@ DatePickerCombobox.prototype.handleCancelButton = function (event) { DatePickerCombobox.prototype.handleNextYearButton = function (event) { var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); switch (event.type) { @@ -503,7 +364,6 @@ DatePickerCombobox.prototype.handleNextYearButton = function (event) { case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToNextYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); flag = true; break; @@ -516,7 +376,6 @@ DatePickerCombobox.prototype.handleNextYearButton = function (event) { this.dateInput.ignoreBlurEvent = true; } this.moveToNextYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); break; @@ -532,8 +391,6 @@ DatePickerCombobox.prototype.handleNextYearButton = function (event) { DatePickerCombobox.prototype.handlePreviousYearButton = function (event) { var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); switch (event.type) { @@ -544,7 +401,6 @@ DatePickerCombobox.prototype.handlePreviousYearButton = function (event) { case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToPreviousYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); flag = true; break; @@ -572,7 +428,6 @@ DatePickerCombobox.prototype.handlePreviousYearButton = function (event) { this.dateInput.ignoreBlurEvent = true; } this.moveToPreviousYear(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); break; @@ -588,8 +443,6 @@ DatePickerCombobox.prototype.handlePreviousYearButton = function (event) { DatePickerCombobox.prototype.handleNextMonthButton = function (event) { var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); switch (event.type) { @@ -604,7 +457,6 @@ DatePickerCombobox.prototype.handleNextMonthButton = function (event) { case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToNextMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); flag = true; break; @@ -617,7 +469,6 @@ DatePickerCombobox.prototype.handleNextMonthButton = function (event) { this.dateInput.ignoreBlurEvent = true; } this.moveToNextMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); break; @@ -633,8 +484,6 @@ DatePickerCombobox.prototype.handleNextMonthButton = function (event) { DatePickerCombobox.prototype.handlePreviousMonthButton = function (event) { var flag = false; - var onFirstRow = this.onFirstRow(); - var onLastRow = this.onLastRow(); switch (event.type) { @@ -649,7 +498,6 @@ DatePickerCombobox.prototype.handlePreviousMonthButton = function (event) { case this.keyCode.ENTER: case this.keyCode.SPACE: this.moveToPreviousMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); flag = true; break; @@ -662,7 +510,6 @@ DatePickerCombobox.prototype.handlePreviousMonthButton = function (event) { this.dateInput.ignoreBlurEvent = true; } this.moveToPreviousMonth(); - this.adjustCurrentDay(onFirstRow, onLastRow); this.setFocusDay(false); flag = true; break; @@ -678,12 +525,12 @@ DatePickerCombobox.prototype.handlePreviousMonthButton = function (event) { }; DatePickerCombobox.prototype.moveToNextYear = function () { - this.year++; + this.focusDay.setFullYear(this.focusDay.getFullYear() + 1); this.updateGrid(); }; DatePickerCombobox.prototype.moveToPreviousYear = function () { - this.year--; + this.focusDay.setFullYear(this.focusDay.getFullYear() - 1); this.updateGrid(); }; @@ -697,87 +544,70 @@ DatePickerCombobox.prototype.moveToPreviousMonth = function () { }; DatePickerCombobox.prototype.moveToNextMonth = function () { - this.month++; - if (this.month > 11) { - this.month = 0; - this.year++; - } + this.focusDay.setMonth(this.focusDay.getMonth() + 1); this.updateGrid(); }; -DatePickerCombobox.prototype.moveToDay = function (day, month, year) { - this.day = day; - this.month = month; - this.year = year; +DatePickerCombobox.prototype.moveToPreviousMonth = function () { + this.focusDay.setMonth(this.focusDay.getMonth() - 1); this.updateGrid(); - this.setFocusDay(); }; -DatePickerCombobox.prototype.moveFocusToNextDay = function () { +DatePickerCombobox.prototype.moveFocusToDay = function (day) { + var d = this.focusDay; + + this.focusDay = day; - this.day++; - if (this.daysInCurrentMonth <= this.day) { - this.day = 0; - this.moveToNextMonth(); + if ((d.getMonth() != this.focusDay.getMonth()) || + (d.getYear() != this.focusDay.getYear())) { + this.updateGrid(); } this.setFocusDay(); }; -DatePickerCombobox.prototype.moveFocusToNextWeek = function () { +DatePickerCombobox.prototype.moveFocusToNextDay = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() + 1); + this.moveFocusToDay(d); +}; - this.day += 7; - if (this.daysInCurrentMonth <= this.day) { - this.day = this.day - this.daysInCurrentMonth; - this.moveToNextMonth(); - } - this.setFocusDay(); +DatePickerCombobox.prototype.moveFocusToNextWeek = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() + 7); + this.moveFocusToDay(d); }; DatePickerCombobox.prototype.moveFocusToPreviousDay = function () { - - this.day--; - if (this.day < 0) { - this.moveToPreviousMonth(); - this.day = this.daysInCurrentMonth - 1; - } - this.setFocusDay(); + var d = new Date(this.focusDay); + d.setDate(d.getDate() - 1); + this.moveFocusToDay(d); }; DatePickerCombobox.prototype.moveFocusToPreviousWeek = function () { - this.day -= 7; - if (this.day < 0) { - this.day = this.daysInLastMonth + this.day; - this.moveToPreviousMonth(); - } - this.setFocusDay(); + var d = new Date(this.focusDay); + d.setDate(d.getDate() - 7); + this.moveFocusToDay(d); }; DatePickerCombobox.prototype.moveFocusToFirstDayOfWeek = function () { - - this.day = this.day - this.currentDay.column; - - if (this.day < 0) { - this.day = this.daysInLastMonth + this.day; - this.moveToPreviousMonth(); - } - this.setFocusDay(); - + var d = new Date(this.focusDay); + d.setDate(d.getDate() - d.getDay()); + this.moveFocusToDay(d); }; DatePickerCombobox.prototype.moveFocusToLastDayOfWeek = function () { - - this.day = this.day + (6 - this.currentDay.column); - - if (this.daysInCurrentMonth <= this.day) { - this.day = this.day - this.daysInCurrentMonth; - this.moveToNextMonth(); - } - this.setFocusDay(); - + var d = new Date(this.focusDay); + d.setDate(d.getDate() + (6 - d.getDay())); + this.moveFocusToDay(d); }; -DatePickerCombobox.prototype.setTextboxDate = function () { - this.dateInput.setDate(this.month, this.day, this.year); +DatePickerCombobox.prototype.setTextboxDate = function (day) { + if (day) { + this.dateInput.setDate(day); + } + else { + this.dateInput.setDate(this.focusDay); + } }; DatePickerCombobox.prototype.getDateInput = function () { @@ -788,28 +618,25 @@ DatePickerCombobox.prototype.getDateInput = function () { Number.isInteger(parseInt(parts[0])) && Number.isInteger(parseInt(parts[1])) && Number.isInteger(parseInt(parts[2]))) { - this.month = parseInt(parts[0]) - 1; - this.day = parseInt(parts[1]) - 1; - this.year = parseInt(parts[2]); + this.focusDay = new Date(parseInt(parts[2]), parseInt(parts[0]) - 1, parseInt(parts[1])); + this.selectedDay = new Date(this.focusDay); } else { // If not a valid date (MM/DD/YY) initialize with todays date - var date = new Date(); - - this.year = date.getFullYear(); - this.month = date.getMonth(); - this.day = date.getDate() - 1; + this.focusDay = new Date(); + this.selectedDay = new Date(0,0,1); } - this.daysInCurrentMonth = this.getDaysInMonth(); - this.daysInLastMonth = this.getDaysInLastMonth(); - - this.selectedDay = new Date(this.year, this.month, this.day); - }; -DatePickerCombobox.prototype.getDateForButtonLabel = function () { - this.selectedDay = new Date(this.year, this.month, this.day + 1); +DatePickerCombobox.prototype.getDateForButtonLabel = function (year, month, day) { + if (typeof year !== 'number' || typeof month !== 'number' || typeof day !== 'number') { + this.selectedDay = this.focusDay; + } + else { + this.selectedDay = new Date(year, month, day); + } + var label = this.dayLabels[this.selectedDay.getDay()]; label += ' ' + this.monthLabels[this.selectedDay.getMonth()]; label += ' ' + (this.selectedDay.getDate()); diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox.js index 33f162a25d..4b1fd1a6a7 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox.js @@ -227,8 +227,8 @@ ComboboxInput.prototype.isCollapsed = function () { return this.comboboxNode.getAttribute('aria-expanded') !== 'true'; }; -ComboboxInput.prototype.setDate = function (month, day, year) { - this.inputNode.value = (month + 1) + '/' + (day + 1) + '/' + year; +ComboboxInput.prototype.setDate = function (day) { + this.inputNode.value = (day.getMonth() + 1) + '/' + day.getDate() + '/' + day.getFullYear(); }; ComboboxInput.prototype.getDate = function () { From fdc4af76785bc10678d58d94f99e5a6e8db39f75 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 11:24:45 -0500 Subject: [PATCH 069/137] improved code by adding helper functions and removing a redundant function --- .../js/datepicker-combobox-dialog.js | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js index f63bfd890d..dd4582101d 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js @@ -120,6 +120,17 @@ DatePickerCombobox.prototype.init = function () { this.setFocusDay(); }; +DatePickerCombobox.prototype.isSameDay = function (day1, day2) { + return (day1.getFullYear() == day2.getFullYear()) && + (day1.getMonth() == day2.getMonth()) && + (day1.getDate() == day2.getDate()); +}; + +DatePickerCombobox.prototype.isNotSameMonth = function (day1, day2) { + return (day1.getFullYear() != day2.getFullYear()) || + (day1.getMonth() != day2.getMonth()); +}; + DatePickerCombobox.prototype.updateGrid = function () { var i, flag; @@ -138,9 +149,7 @@ DatePickerCombobox.prototype.updateGrid = function () { for (i = 0; i < this.days.length; i++) { flag = d.getMonth() != fd.getMonth(); this.days[i].updateDay(flag, d); - if ((d.getFullYear() == this.selectedDay.getFullYear()) && - (d.getMonth() == this.selectedDay.getMonth()) && - (d.getDate() == this.selectedDay.getDate())) { + if (this.isSameDay(d, this.selectedDay)) { this.days[i].domNode.setAttribute('aria-selected', 'true'); } d.setDate(d.getDate() + 1); @@ -173,9 +182,7 @@ DatePickerCombobox.prototype.setFocusDay = function (flag) { function checkDay (d) { d.domNode.setAttribute('tabindex', '-1'); - if ((d.day.getDate() == fd.getDate()) && - (d.day.getMonth() == fd.getMonth()) && - (d.day.getFullYear() == fd.getFullYear())) { + if (this.isSameDay(d.day, fd)) { d.domNode.setAttribute('tabindex', '0'); if (flag) { d.domNode.focus(); @@ -189,8 +196,7 @@ DatePickerCombobox.prototype.setFocusDay = function (flag) { DatePickerCombobox.prototype.updateDate = function (day) { var d = this.focusDay; this.focusDay = day; - if ((d.getMonth() !== day.getMonth()) || - (d.getFullYear() !== day.getFullYear())) { + if (this.isNotSameMonth(d, day)) { this.updateGrid(); this.setFocusDay(); } @@ -534,15 +540,6 @@ DatePickerCombobox.prototype.moveToPreviousYear = function () { this.updateGrid(); }; -DatePickerCombobox.prototype.moveToPreviousMonth = function () { - this.month--; - if (this.month < 0) { - this.month = 11; - this.year--; - } - this.updateGrid(); -}; - DatePickerCombobox.prototype.moveToNextMonth = function () { this.focusDay.setMonth(this.focusDay.getMonth() + 1); this.updateGrid(); From c8495c3e6fcdd89ddcfcd7c7316bc3f91e361afc Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 11:27:39 -0500 Subject: [PATCH 070/137] updated file name to be consistent wih other examples --- .../{combo-11-datepicker.html => datepicker-combo.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/combobox/aria1.1pattern/{combo-11-datepicker.html => datepicker-combo.html} (100%) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/datepicker-combo.html similarity index 100% rename from examples/combobox/aria1.1pattern/combo-11-datepicker.html rename to examples/combobox/aria1.1pattern/datepicker-combo.html From 1d3bd0b4d217f1da58b70926e77c8411b57f6f6c Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 11:42:45 -0500 Subject: [PATCH 071/137] restored previous file name for compatibility with ava testing --- .../{datepicker-combo.html => combo-11-datepicker.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/combobox/aria1.1pattern/{datepicker-combo.html => combo-11-datepicker.html} (100%) diff --git a/examples/combobox/aria1.1pattern/datepicker-combo.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html similarity index 100% rename from examples/combobox/aria1.1pattern/datepicker-combo.html rename to examples/combobox/aria1.1pattern/combo-11-datepicker.html From 8e73ffc9bd9c6c57bb61a3599c08de8e6842114b Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 12:56:45 -0500 Subject: [PATCH 072/137] improved code readibility by using helper function to test if same month --- .../combobox/aria1.1pattern/js/datepicker-combobox-dialog.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js index dd4582101d..3aaab91502 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js @@ -555,8 +555,7 @@ DatePickerCombobox.prototype.moveFocusToDay = function (day) { this.focusDay = day; - if ((d.getMonth() != this.focusDay.getMonth()) || - (d.getYear() != this.focusDay.getYear())) { + if (this.isNotSameMonth(d, this.focusDay)) { this.updateGrid(); } this.setFocusDay(); From 66929542a6b668320d50726ec2ff2a37ab88bfae Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 15 Jul 2019 13:11:16 -0500 Subject: [PATCH 073/137] removed some unused variables --- .../aria1.1pattern/js/datepicker-combobox-dialog.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js index 3aaab91502..270433b5cb 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js @@ -36,12 +36,6 @@ var DatePickerCombobox = function (comboboxNode, inputNode, buttonNode, dialogNo this.lastRowNode = null; - var date = new Date(); - - this.year = date.getFullYear(); - this.month = date.getMonth(); - this.day = date.getDate() - 1; - this.days = []; this.focusDay = new Date(); From 8c379760fafee55cf7d91e2adcb7d306921d56b1 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 16 Jul 2019 08:53:28 -0500 Subject: [PATCH 074/137] removed unused properties and used helper function to compare days --- .../combobox/aria1.1pattern/js/datepicker-combobox-day.js | 8 ++------ .../aria1.1pattern/js/datepicker-combobox-dialog.js | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js index 2b5b811191..4fce102a11 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-day.js @@ -5,13 +5,9 @@ * File: datepicker-combobox-day.js */ -var DatePickerComboboxDay = function (domNode, datepicker, index, row, column) { +var DatePickerComboboxDay = function (domNode, datepicker) { - this.index = index; - this.row = row; - this.column = column; - - this.day = new Date(); + this.day = new Date(); this.domNode = domNode; this.datepicker = datepicker; diff --git a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js index 270433b5cb..38000f44dd 100644 --- a/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js +++ b/examples/combobox/aria1.1pattern/js/datepicker-combobox-dialog.js @@ -91,7 +91,6 @@ DatePickerCombobox.prototype.init = function () { // Create Grid of Dates this.tbodyNode.innerHTML = ''; - var index = 0; for (var i = 0; i < 6; i++) { var row = this.tbodyNode.insertRow(i); this.lastRowNode = row; @@ -103,10 +102,9 @@ DatePickerCombobox.prototype.init = function () { cellButton.classList.add('dateButton'); cell.appendChild(cellButton); row.appendChild(cell); - var dpDay = new DatePickerComboboxDay(cellButton, this, index, i, j); + var dpDay = new DatePickerComboboxDay(cellButton, this); dpDay.init(); this.days.push(dpDay); - index++; } } @@ -172,11 +170,9 @@ DatePickerCombobox.prototype.setFocusDay = function (flag) { flag = true; } - var fd = this.focusDay; - function checkDay (d) { d.domNode.setAttribute('tabindex', '-1'); - if (this.isSameDay(d.day, fd)) { + if (this.isSameDay(d.day, this.focusDay)) { d.domNode.setAttribute('tabindex', '0'); if (flag) { d.domNode.focus(); From 2aa02e0c937f08f47288a6f614e0016f4e81a215 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 17 Jul 2019 10:44:29 -0500 Subject: [PATCH 075/137] updated docuemntation and added ids for developing regression tests --- .../aria1.1pattern/combo-11-datepicker.html | 299 +++++++----------- 1 file changed, 112 insertions(+), 187 deletions(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index b2c67aecb6..d1a8f69469 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -36,7 +36,7 @@

    Date Picker Example Using ARIA 1.1 Combobox

    The date picker is an example of the combobox design pattern that opens a dialog box. - The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the change date button. + The date picker dialog box in this example is opened when keyboard focus is moved to the text box and a down arrow key pressed or clicking on the choose date button. The date picker dialog uses a grid pattern to show and select a date using the cursor keys. Additional buttons in the dialog box can be used for changing the month and year shown in the grid.

    @@ -77,9 +77,7 @@

    Example

    @@ -217,14 +215,14 @@

    Accessibility Features

    There are three main featues of the combobox date picker example:

    • Textbox: Holds the value of the selected date and can open the date picker dialog box.
    • -
    • Change Date Button: Opens the datepicker dialog box.
    • +
    • Choose Date Button: Opens the datepicker dialog box.
    • Date Picker Dialog Box: Displays a grid of dates for the user to select from and provide additional buttons to change the month and year of dates shown in the grid.

    The combobox, textbox and button all have the same accessible name.

    -

    Textbox

    +

    Date Textbox

    • Contains the date value.
    • @@ -233,7 +231,7 @@

      Textbox

    • To support people who are sighted in understanding the down arrow key opens the date picker dialog box, a down arrow icon appears in the textbox when the textbox receives keyboard focus and the down arrow icon is removed when the textbox does not have keyboard focus.
    -

    Change Date Button

    +

    Choose Date Button

    • Opens and closes the date picker dialog box through click events.
    • @@ -248,7 +246,7 @@

      Date Picker Dialog

    • Additional buttons and keyboard commands are used to change month and year.
    • A live region announces changes in the month and year.
    • A live region announces the use of cursor keys when a date in the gird of dates gets focus. The message is also visible at the bottom dialog box to provide the same information to keyboard users.
    • -
    • The dialog is opened through keyboard commands in the textbox or clicking on the change date button.
    • +
    • The dialog is opened through keyboard commands in the textbox or clicking on the choose date button.

    Mobile Support

    @@ -278,8 +276,11 @@

    Textbox

    Down Arrow, Enter
      -
    • Open the date picker dialog.
    • -
    • Move focus to current date.
    • +
    • Open the date picker dialog.
    • +
    • + Move focus to selected date in the calendar grid, i.e., the date displayed in the date textbox. + If no date has been selected, places focus on the current date. +
    @@ -288,7 +289,7 @@

    Textbox

    -

    Change Date Button

    +

    Choose Date Button

    @@ -297,13 +298,19 @@

    Change Date Button

    - + @@ -320,25 +327,27 @@

    Date Picker Dialog

    - + - + - + - + @@ -356,10 +365,10 @@

    Date Picker Dialog: Calendar Buttons

    - + @@ -376,49 +385,52 @@

    Date Picker Dialog: Date Grid

    - - + + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - -
    Space,
    Return
      -
    • Toggles date picker dialog.
    • -
    • NOTE: This button has been removed from tab order of the page by setting tabindex=-1, since the date picker dialog can be opened using the keyboard in the date input (e.g. down arrow key).
    • +
    • Open the date picker dialog.
    • +
    • + Move focus to selected date in the calendar grid, i.e., the date displayed in the date textbox. + If no date has been selected, places focus on the current date. +
    +

    Note: + The button has been excluded from Tab sequence of the page, since the dialog can be opened from the textbox. +

    ESCClose the dialog and move focus back to the date input.Close the dialog and move focus back to the date textbox.
    TAB
      -
    • Move focus to next button or grid inside the dialog.
    • -
    • If on the last button (i.e. OK Button), moves button to the first button (i.e. Previous Year Button).
    • +
    • Moves focus to next element in the dialog Tab sequence.
    • +
    • Note that, as specified in the grid design pattern, only one button in the calendar grid is in the Tab sequence.
    • +
    • If focus is on the last button (i.e., OK), moves focus to the first button (i.e. Previous Year).
    Shift +
    TAB
      -
    • Move focus to previous button or grid inside the dialog.
    • -
    • If on the first button (i.e. Previous Year Button), moves button to the first button (i.e. OK Button).
    • +
    • Moves focus to previous element in the dialog Tab sequence.
    • +
    • Note that, as specified in the grid design pattern, only one button in the calendar grid is in the Tab sequence.
    • +
    • If focus is on the first button (i.e., Previous Year), moves focus to the last button (i.e. OK).
    Space,
    Return
    - Change the month and/or year for selecting a date from the grid of dates. + Change the month and/or year displayed in the calendar grid.
    Space,
    Return
    Space,
    Enter
      -
    • Select the date, close the dialog box and move focus to the date input.
    • -
    • The value of the date input is updated with the selected date in the from the selected date in the date grid.
    • +
        +
      • Select the date, close the dialog, and move focus to the Choose Date button.
      • +
      • Update the value of the Date input with the selected date.
      • +
      • Update the accessible name of the Choose Date button to include the selected date.
      • +
    Up Arrow Move the focus to the same day of the previous week.
    Down Arrow Move the focus to the same day of the next week.
    Right Arrow Move the focus to the next day.
    Left Arrow Move the focus to the previous day.
    Home Move the focus to the first day (e.g Sunday) of the current week.
    End Move the focus to the last day (e.g. Saturday) of the current week.
    PageUp
      -
    • Change the grid of dates to the previous month.
    • -
    • The focus will be on the same day of that week, if it does not exist, then it will move the focus to the same day of previous or next week
    • +
    • Changes the grid of dates to the previous month.
    • +
    • Sets focus on the same day of the same week. If that day does not exist, then moves focus to the same day of the previous or next week.
    Shift+
    PageUp
      @@ -427,7 +439,7 @@

      Date Picker Dialog: Date Grid

    PageDown
      @@ -436,7 +448,7 @@

      Date Picker Dialog: Date Grid

    Shift+
    PageDown
      @@ -445,23 +457,6 @@

      Date Picker Dialog: Date Grid

    TABMove focus to next button inside the dialog.
    Shift +
    TAB
    Move focus to previous button inside the dialog.
    ESC -
      -
    • Close the dialog and move focus back to date input.
    • -
    • The value of the date texbox is not updated.
    • -
    -
    @@ -476,12 +471,13 @@

    Date Picker Buttons (OK and Cancel)

    - + Space,
    Return + Activates the button:
      -
    • The Cancel button closes the date picker dialog, moves focus to date input, does not update date in date input.
    • -
    • The OK button closes the date picker dialog, moves focus to date input, does update date in date input.
    • +
    • Cancel: Closes the dialog, moves focus to Date Textbox button, does not update date in date textbox.
    • +
    • OK: Closes the dialog, moves focus to Date Textbox button, updates date in date textbox.
    @@ -510,19 +506,14 @@

    Combobox

    div -
      -
    • Identifies the texbox as having a combobox behavior.
    • -
    • Accessible name is defined using the aria-labelledby attribute.
    • -
    + Identifes the element as a combobox. aria-labelledby=IDREF div - - The accessible name is Date and has the same name as the names for the combobox and textbox. - + Refers to the label containing the word Date, which also defines the accessible name for the input element. @@ -576,17 +567,6 @@

    Textbox

    - - textbox - - input - -
      -
    • The input element with no type attribute has the default role of textbox.
    • -
    • Accessible name comes from a label element using the for attribute and has the same name as the combobox and change date button.
    • -
    - - aria-autocomplete=none @@ -609,12 +589,8 @@

    Textbox

    div
      -
    • Provides keyboard help information to screen reader users on how to open the date picker dialog.
    • -
    • When the textbox receives keyboard focus the content of the div element is updated to provide information on using the down arrow key to open the date picker dialog.
    • -
    • When the textbox looses keyboard focus the content of the div is cleared, so that the message will be repeated to screen reader users each time the textbox received focus.
    • -
    • The content is updated with a little delay after the focus event to help screen readers wait to speak the message after the focus change information.
    • -
    • The use of the polite value means the screen reader will not interrupt other speech to announce the change in month and/or year.
    • -
    • This div is hidden using CSS display=none so it is not visible in graphical renderings +
    • Indicates the element that displays information about keyboard commands for opening the dialog box should be automatically announced by screen readers.
    • +
    • The script slightly delays display of the information so screen readers are more likely to read it after information related to change of focus.
    @@ -623,7 +599,7 @@

    Textbox

    -

    Change Date Button

    +

    Choose Date Button

    @@ -638,32 +614,25 @@

    Change Date Button

    - - - - - - - + - + +
    tabindex=-1 button - Removes the button from the tab order of the page. -
    aria-labelledby=IDREFbutton
      -
    • Initial value of accessible name is Date, same as combobox and textbox.
    • -
    • When user selected date, the accessible name will update to current date.
    • +
    • Exclude the button from tab sequence of the page.
    • +
    • The textbox supports opening the dialog through the Keyboard.
    aria-expanded=truearia-label="string" button
      -
    • Is defined when the date picker dialog is open.
    • -
    • When the date picker dialog is closed, the aria-expanded attribute is removed from element.
    • +
    • The initial value of accessible name is Choose Date.
    • +
    • When users select a date, the accessible name is updated to also include the selected date.
    @@ -680,47 +649,34 @@

    Date Picker Dialog

    - + dialog div - -
      -
    • Identifies div element as a dialog box container.
    • -
    • The accessible name comes from the aria-labelledby attribute.
    • -
    - + Identifies the element as a dialog . - + aria-modal=true div - - Identifies that dialog box as a modal dialog box. - + Indicates the dialog is modal. - + aria-labelledby=IDREFS div - - Defines the accessible name for the dialog box. - + Refers to the heading containing the currently displayed month and year, which defines the accessible name for the dialog. - + aria-live=polite div
      -
    • Provides keyboard help information to screen reader users on using the cursor keys to navigate the grid of dates.
    • -
    • When the textbox receives keyboard focus the content of the div element is updated to provide information on using the down arrow key to open the date picker dialog.
    • -
    • When the textbox looses keyboard focus the content of the div is cleared, so that the message will be repeated to screen reader users each time the textbox received focus.
    • -
    • The content is updated with a little delay after the focus event to help screen readers wait to speak the message after the focus change information.
    • -
    • The use of the polite value means the screen reader will not interrupt other speech to announce the change in month and/or year.
    • -
    • This div is hidden using CSS display=none so it is not visible in graphical renderings +
    • Indicates the element that displays information about keyboard commands for navigating the grid should be automatically announced by screen readers.
    • +
    • The script slightly delays display of the information so screen readers are more likely to read it after information related to change of focus.
    @@ -740,7 +696,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

    - + aria-label=String button @@ -748,7 +704,7 @@

    Date Picker Dialog: Calendar Navigation Buttons

    Defines the accessible name of the button (e.g. "Next Year"). - + aria-live=polite @@ -777,110 +733,79 @@

    Date Picker Dialog: Date Grid

    - + grid table -
      -
    • Identifies the table element serving as the grid widget container.
    • -
    • The accessible name comes from the aria-labelledby attribute.
    • +
        +
      • Identifies the table element as a grid widget.
      • Since the grid role is applied to a table element, the row, colheader, and gridcell roles do not need to be specified because they are implied by tr, th, and td tags.
      - + aira-labelledby=IDREF table -
        -
      • Define the accessible name for the grid using current month and year.
      • -
      - - - - row - - - tr - - - The tr element has a default role of row. - - - - columnheader - - - th - - - The default role for a th[scope="col"] element is columnheader. - - - - gridcell - - - td - - - Since the table element has a grid role, all descendant td elements will have the role of gridcell. + Defines the accessible name for the grid using the h2 that shows the month and year of the dates displayed in the grid. - - button - - - td > button - - -
        -
      • The button element is used to represent each date in the grid and are a child of the gridcell.
      • -
      • Accessible name for the button is the date which is defined by the text content of the button element.
      • -
      - - - + - tabindex=0 + tabindex="0" - td > button + button - Identify the currently selected date and make it part of tab sequence of the dialog box. +
        +
      • Makes the button focusable and includes it in the dialog Tab sequence.
      • +
      • Set dynamically by the JavaScript when the element is to be included in the dialog Tab sequence.
      • +
      • At any given time, only one button within the grid is in the dialog Tab sequence.
      • +
      • This approach to managing focus is described in the section on roving tabindex.
      • +
      - + - tabindex=-1 + tabindex="-1" - td > button + button - Exclude the button from tab sequence to support grid cell navigation. +
        +
      • Makes the button focusable and excludes it from the dialog Tab sequence.
      • +
      • Changed dynamically to 0 by the JavaScript when the button is to be included in the dialog Tab sequence.
      • +
      • At any given time, only one button within the grid is in the dialog Tab sequence.
      • +
      • This approach to managing focus is described in the section on roving tabindex.
      • +
      - + - aria-selected=true - td > button + aria-selected="true" + button
        -
      • Identifies the currently selected date in the date input.
      • -
      • The aria-selected attribute is only set on the button representing the date in the date input, all other buttons do not have an aria-selected attribute.
      • +
      • Identifies the button for the currently selected date, i.e., the date value present in the date textbox.
      • +
      • Only set on the button representing the currently selected date, no other buttons have aria-selected specified.
      +

      + Note: Since the names of the days of the week in the column headers are abbreviated to two characters, they may be difficult to understand when announced by a screen reader. + An alternative column header name can be provided to screen readers by applying the abbr attribute to the th elements. + So, each th element includes a abbr attribute containing the full spelling of the name of the day for that column. +

      From ac18697a6a0dd174d99928b8a7438919903a03c3 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 17 Jul 2019 11:17:05 -0500 Subject: [PATCH 076/137] started integration of dialog box regression tests --- .../aria1.1pattern/combo-11-datepicker.html | 3 +- .../aria1.1pattern/js/datepicker-combobox.js | 1 + test/tests/combo-11-datepicker.js | 267 +++++++++++++++++- 3 files changed, 268 insertions(+), 3 deletions(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index d1a8f69469..0606e1457f 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -89,6 +89,7 @@

      Example

    From 227f0963b997a6cbc514dbce110ebb7f20333236 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 17 Jul 2019 15:00:32 -0500 Subject: [PATCH 083/137] updated documentation --- examples/combobox/aria1.1pattern/combo-11-datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index af0824072e..8d7047ee3e 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -840,7 +840,7 @@

    HTML Source Code

    From 5c81da2604419d9e6397df0204353cf4d708ff2b Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 17 Jul 2019 15:01:43 -0500 Subject: [PATCH 084/137] updated documentation --- examples/combobox/aria1.1pattern/combo-11-datepicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index 8d7047ee3e..52c551933a 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -840,7 +840,7 @@

    HTML Source Code

    From 5bd7f9287c1e7f6f27ef1f5aac7063f75b723ccd Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 6 Aug 2019 15:47:58 -0500 Subject: [PATCH 085/137] updated properties to move role and arai-expanded to input box, changed aria-owns to aria-controls and removed disabled days form the date grid --- .../aria1.1pattern/combo-11-datepicker.html | 82 +++++----------- .../css/datepicker-combobox.css | 96 +++++++++---------- .../js/datepicker-combobox-day.js | 52 +++++----- .../js/datepicker-combobox-dialog.js | 85 ++++++++-------- .../aria1.1pattern/js/datepicker-combobox.js | 31 +++--- examples/dialog-modal/datepicker-dialog.html | 10 +- 6 files changed, 157 insertions(+), 199 deletions(-) diff --git a/examples/combobox/aria1.1pattern/combo-11-datepicker.html b/examples/combobox/aria1.1pattern/combo-11-datepicker.html index 52c551933a..258d043341 100644 --- a/examples/combobox/aria1.1pattern/combo-11-datepicker.html +++ b/examples/combobox/aria1.1pattern/combo-11-datepicker.html @@ -46,12 +46,7 @@

    Example