@instructure/ui-calendar 11.7.3-snapshot-39 → 11.7.3-snapshot-41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/es/Calendar/v1/Day/index.js +2 -1
  3. package/es/Calendar/v1/Day/props.js +1 -1
  4. package/es/Calendar/v1/index.js +9 -3
  5. package/es/Calendar/v1/props.js +1 -1
  6. package/es/Calendar/v2/Day/index.js +2 -1
  7. package/es/Calendar/v2/Day/props.js +1 -1
  8. package/es/Calendar/v2/index.js +20 -3
  9. package/es/Calendar/v2/props.js +1 -1
  10. package/lib/Calendar/v1/Day/index.js +2 -1
  11. package/lib/Calendar/v1/Day/props.js +1 -1
  12. package/lib/Calendar/v1/index.js +9 -3
  13. package/lib/Calendar/v1/props.js +1 -1
  14. package/lib/Calendar/v2/Day/index.js +2 -1
  15. package/lib/Calendar/v2/Day/props.js +1 -1
  16. package/lib/Calendar/v2/index.js +20 -3
  17. package/lib/Calendar/v2/props.js +1 -1
  18. package/package.json +16 -16
  19. package/src/Calendar/v1/Day/index.tsx +6 -1
  20. package/src/Calendar/v1/Day/props.ts +6 -0
  21. package/src/Calendar/v1/README.md +2 -0
  22. package/src/Calendar/v1/index.tsx +8 -3
  23. package/src/Calendar/v1/props.ts +6 -0
  24. package/src/Calendar/v2/Day/index.tsx +6 -1
  25. package/src/Calendar/v2/Day/props.ts +6 -0
  26. package/src/Calendar/v2/README.md +2 -0
  27. package/src/Calendar/v2/index.tsx +38 -20
  28. package/src/Calendar/v2/props.ts +6 -0
  29. package/tsconfig.build.tsbuildinfo +1 -1
  30. package/types/Calendar/v1/Day/index.d.ts +1 -0
  31. package/types/Calendar/v1/Day/index.d.ts.map +1 -1
  32. package/types/Calendar/v1/Day/props.d.ts +5 -0
  33. package/types/Calendar/v1/Day/props.d.ts.map +1 -1
  34. package/types/Calendar/v1/index.d.ts +1 -0
  35. package/types/Calendar/v1/index.d.ts.map +1 -1
  36. package/types/Calendar/v1/props.d.ts +5 -0
  37. package/types/Calendar/v1/props.d.ts.map +1 -1
  38. package/types/Calendar/v2/Day/index.d.ts +1 -0
  39. package/types/Calendar/v2/Day/index.d.ts.map +1 -1
  40. package/types/Calendar/v2/Day/props.d.ts +5 -0
  41. package/types/Calendar/v2/Day/props.d.ts.map +1 -1
  42. package/types/Calendar/v2/index.d.ts +1 -0
  43. package/types/Calendar/v2/index.d.ts.map +1 -1
  44. package/types/Calendar/v2/props.d.ts +5 -0
  45. package/types/Calendar/v2/props.d.ts.map +1 -1
package/CHANGELOG.md CHANGED
@@ -3,12 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [11.7.3-snapshot-39](https://github.com/instructure/instructure-ui/compare/v11.7.2...v11.7.3-snapshot-39) (2026-05-07)
6
+ ## [11.7.3-snapshot-41](https://github.com/instructure/instructure-ui/compare/v11.7.2...v11.7.3-snapshot-41) (2026-05-07)
7
7
 
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
11
  * **many:** update dependencies, remove lots of Babel plugins, remove Webpack 4 support ([f916fca](https://github.com/instructure/instructure-ui/commit/f916fcafdddcb2d7de401f93e8ff92cfdfa47bba))
12
+ * **ui-date-time-input,ui-date-input,ui-calendar:** set missing aria-selected on calendar days, BREAKING CHANGE: add mandatory prop selectedLabel, change datePickerDialog prop to mandatory ([08abed5](https://github.com/instructure/instructure-ui/commit/08abed5e8c8b29aecab0368e01e8e4ffd0c7fda7))
12
13
 
13
14
 
14
15
 
@@ -116,6 +116,7 @@ let Day = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_class
116
116
  interaction,
117
117
  isOutsideMonth,
118
118
  isSelected,
119
+ selectedLabel,
119
120
  isToday,
120
121
  onClick,
121
122
  onKeyDown,
@@ -148,7 +149,7 @@ let Day = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_class
148
149
  children: _jsx("span", {
149
150
  css: styles?.day,
150
151
  children: _jsx(AccessibleContent, {
151
- alt: label,
152
+ alt: selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label,
152
153
  children: callRenderProp(children)
153
154
  })
154
155
  })
@@ -22,5 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- const allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
25
+ const allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'selectedLabel', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
26
26
  export { allowedProps };
@@ -328,8 +328,12 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
328
328
  children: row.map((day, i) => _jsx("td", {
329
329
  role: role,
330
330
  children: role === 'presentation' ? safeCloneElement(day, {
331
- 'aria-describedby': this._weekdayHeaderIds[i]
332
- }) : day
331
+ 'aria-describedby': this._weekdayHeaderIds[i],
332
+ role: 'option'
333
+ }) : safeCloneElement(day, {
334
+ 'aria-describedby': this._weekdayHeaderIds[i],
335
+ role: 'button'
336
+ })
333
337
  }, day.props.date))
334
338
  }, `row${row[0].props.date}`));
335
339
  }
@@ -376,7 +380,8 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
376
380
  }
377
381
  renderDefaultdays() {
378
382
  const {
379
- selectedDate
383
+ selectedDate,
384
+ selectedLabel
380
385
  } = this.props;
381
386
  const {
382
387
  visibleMonth,
@@ -416,6 +421,7 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
416
421
  isOutsideMonth: !date.isSame(visibleMonth, 'month'),
417
422
  label: date.format('D MMMM YYYY') // used by screen readers
418
423
  ,
424
+ selectedLabel: selectedLabel,
419
425
  onClick: this.handleDayClick,
420
426
  interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled',
421
427
  children: date.format('DD')
@@ -22,5 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- const allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'timezone', 'visibleMonth'];
25
+ const allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'selectedLabel', 'timezone', 'visibleMonth'];
26
26
  export { allowedProps };
@@ -115,6 +115,7 @@ let Day = (_dec = withStyleNew(generateStyle), _dec(_class = class Day extends C
115
115
  interaction,
116
116
  isOutsideMonth,
117
117
  isSelected,
118
+ selectedLabel,
118
119
  isToday,
119
120
  onClick,
120
121
  onKeyDown,
@@ -147,7 +148,7 @@ let Day = (_dec = withStyleNew(generateStyle), _dec(_class = class Day extends C
147
148
  children: _jsx("span", {
148
149
  css: styles?.day,
149
150
  children: _jsx(AccessibleContent, {
150
- alt: label,
151
+ alt: selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label,
151
152
  children: callRenderProp(children)
152
153
  })
153
154
  })
@@ -22,5 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- const allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
25
+ const allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'selectedLabel', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
26
26
  export { allowedProps };
@@ -322,13 +322,29 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
322
322
  if (!days[index]) days.push([]);
323
323
  days[index].push(day);
324
324
  return days; // 7xN 2D array of `Day`s
325
- }, []).map(row => _jsx("tr", {
325
+ }, [])
326
+ // TODO: Implement proper WAI-ARIA grid pattern for table mode to enable aria-selected on days.
327
+ // Currently in table mode, days use role="button" which does not support aria-selected
328
+ // (axe violation), so selection is announced via selectedLabel text only.
329
+ //
330
+ // To implement properly (per https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/):
331
+ // 1. Add role="grid" to the <table> in table mode (renderBody)
332
+ // 2. Add role="row" to each <tr>
333
+ // 3. Set role="gridcell" + aria-selected on each <td> (not on the Day)
334
+ // 4. Keep Day as role="button" inside the gridcell
335
+ // This allows aria-selected to live on the <td role="gridcell"> which is spec-correct,
336
+ // while the Day button remains the interactive element.
337
+ .map(row => _jsx("tr", {
326
338
  role: role,
327
339
  children: row.map((day, i) => _jsx("td", {
328
340
  role: role,
329
341
  children: role === 'presentation' ? safeCloneElement(day, {
330
- 'aria-describedby': this._weekdayHeaderIds[i]
331
- }) : day
342
+ 'aria-describedby': this._weekdayHeaderIds[i],
343
+ role: 'option'
344
+ }) : safeCloneElement(day, {
345
+ 'aria-describedby': this._weekdayHeaderIds[i],
346
+ role: 'button'
347
+ })
332
348
  }, day.props.date))
333
349
  }, `row${row[0].props.date}`));
334
350
  }
@@ -415,6 +431,7 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
415
431
  isOutsideMonth: !date.isSame(visibleMonth, 'month'),
416
432
  label: date.format('D MMMM YYYY') // used by screen readers
417
433
  ,
434
+ selectedLabel: this.props.selectedLabel,
418
435
  onClick: this.handleDayClick,
419
436
  interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled',
420
437
  children: date.format('DD')
@@ -22,5 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- const allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'timezone', 'visibleMonth'];
25
+ const allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'selectedLabel', 'timezone', 'visibleMonth'];
26
26
  export { allowedProps };
@@ -124,6 +124,7 @@ let Day = exports.Day = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.
124
124
  interaction,
125
125
  isOutsideMonth,
126
126
  isSelected,
127
+ selectedLabel,
127
128
  isToday,
128
129
  onClick,
129
130
  onKeyDown,
@@ -156,7 +157,7 @@ let Day = exports.Day = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.
156
157
  children: (0, _jsxRuntime.jsx)("span", {
157
158
  css: styles?.day,
158
159
  children: (0, _jsxRuntime.jsx)(_AccessibleContent.AccessibleContent, {
159
- alt: label,
160
+ alt: selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label,
160
161
  children: (0, _callRenderProp.callRenderProp)(children)
161
162
  })
162
163
  })
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
- const allowedProps = exports.allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
31
+ const allowedProps = exports.allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'selectedLabel', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
@@ -339,8 +339,12 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
339
339
  children: row.map((day, i) => (0, _jsxRuntime.jsx)("td", {
340
340
  role: role,
341
341
  children: role === 'presentation' ? (0, _safeCloneElement.safeCloneElement)(day, {
342
- 'aria-describedby': this._weekdayHeaderIds[i]
343
- }) : day
342
+ 'aria-describedby': this._weekdayHeaderIds[i],
343
+ role: 'option'
344
+ }) : (0, _safeCloneElement.safeCloneElement)(day, {
345
+ 'aria-describedby': this._weekdayHeaderIds[i],
346
+ role: 'button'
347
+ })
344
348
  }, day.props.date))
345
349
  }, `row${row[0].props.date}`));
346
350
  }
@@ -387,7 +391,8 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
387
391
  }
388
392
  renderDefaultdays() {
389
393
  const {
390
- selectedDate
394
+ selectedDate,
395
+ selectedLabel
391
396
  } = this.props;
392
397
  const {
393
398
  visibleMonth,
@@ -427,6 +432,7 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
427
432
  isOutsideMonth: !date.isSame(visibleMonth, 'month'),
428
433
  label: date.format('D MMMM YYYY') // used by screen readers
429
434
  ,
435
+ selectedLabel: selectedLabel,
430
436
  onClick: this.handleDayClick,
431
437
  interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled',
432
438
  children: date.format('DD')
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
- const allowedProps = exports.allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'timezone', 'visibleMonth'];
31
+ const allowedProps = exports.allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'selectedLabel', 'timezone', 'visibleMonth'];
@@ -123,6 +123,7 @@ let Day = exports.Day = (_dec = (0, _emotion.withStyleNew)(_styles.default), _de
123
123
  interaction,
124
124
  isOutsideMonth,
125
125
  isSelected,
126
+ selectedLabel,
126
127
  isToday,
127
128
  onClick,
128
129
  onKeyDown,
@@ -155,7 +156,7 @@ let Day = exports.Day = (_dec = (0, _emotion.withStyleNew)(_styles.default), _de
155
156
  children: (0, _jsxRuntime.jsx)("span", {
156
157
  css: styles?.day,
157
158
  children: (0, _jsxRuntime.jsx)(_AccessibleContent.AccessibleContent, {
158
- alt: label,
159
+ alt: selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label,
159
160
  children: (0, _callRenderProp.callRenderProp)(children)
160
161
  })
161
162
  })
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
- const allowedProps = exports.allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
31
+ const allowedProps = exports.allowedProps = ['children', 'date', 'label', 'interaction', 'isSelected', 'selectedLabel', 'isToday', 'isOutsideMonth', 'onClick', 'onKeyDown', 'elementRef', 'as'];
@@ -332,13 +332,29 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
332
332
  if (!days[index]) days.push([]);
333
333
  days[index].push(day);
334
334
  return days; // 7xN 2D array of `Day`s
335
- }, []).map(row => (0, _jsxRuntime.jsx)("tr", {
335
+ }, [])
336
+ // TODO: Implement proper WAI-ARIA grid pattern for table mode to enable aria-selected on days.
337
+ // Currently in table mode, days use role="button" which does not support aria-selected
338
+ // (axe violation), so selection is announced via selectedLabel text only.
339
+ //
340
+ // To implement properly (per https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/):
341
+ // 1. Add role="grid" to the <table> in table mode (renderBody)
342
+ // 2. Add role="row" to each <tr>
343
+ // 3. Set role="gridcell" + aria-selected on each <td> (not on the Day)
344
+ // 4. Keep Day as role="button" inside the gridcell
345
+ // This allows aria-selected to live on the <td role="gridcell"> which is spec-correct,
346
+ // while the Day button remains the interactive element.
347
+ .map(row => (0, _jsxRuntime.jsx)("tr", {
336
348
  role: role,
337
349
  children: row.map((day, i) => (0, _jsxRuntime.jsx)("td", {
338
350
  role: role,
339
351
  children: role === 'presentation' ? (0, _safeCloneElement.safeCloneElement)(day, {
340
- 'aria-describedby': this._weekdayHeaderIds[i]
341
- }) : day
352
+ 'aria-describedby': this._weekdayHeaderIds[i],
353
+ role: 'option'
354
+ }) : (0, _safeCloneElement.safeCloneElement)(day, {
355
+ 'aria-describedby': this._weekdayHeaderIds[i],
356
+ role: 'button'
357
+ })
342
358
  }, day.props.date))
343
359
  }, `row${row[0].props.date}`));
344
360
  }
@@ -425,6 +441,7 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
425
441
  isOutsideMonth: !date.isSame(visibleMonth, 'month'),
426
442
  label: date.format('D MMMM YYYY') // used by screen readers
427
443
  ,
444
+ selectedLabel: this.props.selectedLabel,
428
445
  onClick: this.handleDayClick,
429
446
  interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled',
430
447
  children: date.format('DD')
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
- const allowedProps = exports.allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'timezone', 'visibleMonth'];
31
+ const allowedProps = exports.allowedProps = ['as', 'children', 'currentDate', 'disabledDates', 'locale', 'onDateSelected', 'onRequestRenderNextMonth', 'onRequestRenderPrevMonth', 'renderNavigationLabel', 'renderNextMonthButton', 'renderPrevMonthButton', 'renderWeekdayLabels', 'role', 'selectedDate', 'selectedLabel', 'timezone', 'visibleMonth'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-calendar",
3
- "version": "11.7.3-snapshot-39",
3
+ "version": "11.7.3-snapshot-41",
4
4
  "description": "A calendar component.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -15,27 +15,27 @@
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
17
  "@babel/runtime": "^7.29.2",
18
- "@instructure/console": "11.7.3-snapshot-39",
19
- "@instructure/emotion": "11.7.3-snapshot-39",
20
- "@instructure/shared-types": "11.7.3-snapshot-39",
21
- "@instructure/ui-a11y-content": "11.7.3-snapshot-39",
22
- "@instructure/ui-buttons": "11.7.3-snapshot-39",
23
- "@instructure/ui-i18n": "11.7.3-snapshot-39",
24
- "@instructure/ui-react-utils": "11.7.3-snapshot-39",
25
- "@instructure/ui-simple-select": "11.7.3-snapshot-39",
26
- "@instructure/ui-themes": "11.7.3-snapshot-39",
27
- "@instructure/ui-utils": "11.7.3-snapshot-39",
28
- "@instructure/ui-view": "11.7.3-snapshot-39",
29
- "@instructure/uid": "11.7.3-snapshot-39",
30
- "@instructure/ui-icons": "11.7.3-snapshot-39"
18
+ "@instructure/console": "11.7.3-snapshot-41",
19
+ "@instructure/shared-types": "11.7.3-snapshot-41",
20
+ "@instructure/emotion": "11.7.3-snapshot-41",
21
+ "@instructure/ui-buttons": "11.7.3-snapshot-41",
22
+ "@instructure/ui-a11y-content": "11.7.3-snapshot-41",
23
+ "@instructure/ui-icons": "11.7.3-snapshot-41",
24
+ "@instructure/ui-simple-select": "11.7.3-snapshot-41",
25
+ "@instructure/ui-i18n": "11.7.3-snapshot-41",
26
+ "@instructure/ui-themes": "11.7.3-snapshot-41",
27
+ "@instructure/ui-react-utils": "11.7.3-snapshot-41",
28
+ "@instructure/ui-view": "11.7.3-snapshot-41",
29
+ "@instructure/ui-utils": "11.7.3-snapshot-41",
30
+ "@instructure/uid": "11.7.3-snapshot-41"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@testing-library/jest-dom": "^6.6.3",
34
34
  "@testing-library/react": "15.0.7",
35
35
  "@testing-library/user-event": "^14.6.1",
36
36
  "vitest": "^3.2.2",
37
- "@instructure/ui-color-utils": "11.7.3-snapshot-39",
38
- "@instructure/ui-babel-preset": "11.7.3-snapshot-39"
37
+ "@instructure/ui-babel-preset": "11.7.3-snapshot-41",
38
+ "@instructure/ui-color-utils": "11.7.3-snapshot-41"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18 <=19"
@@ -117,6 +117,7 @@ class Day extends Component<CalendarDayProps> {
117
117
  interaction,
118
118
  isOutsideMonth,
119
119
  isSelected,
120
+ selectedLabel,
120
121
  isToday,
121
122
  onClick,
122
123
  onKeyDown,
@@ -164,7 +165,11 @@ class Day extends Component<CalendarDayProps> {
164
165
  data-cid="Calendar.Day"
165
166
  >
166
167
  <span css={styles?.day}>
167
- <AccessibleContent alt={label}>
168
+ <AccessibleContent
169
+ alt={
170
+ selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label
171
+ }
172
+ >
168
173
  {callRenderProp(children)}
169
174
  </AccessibleContent>
170
175
  </span>
@@ -57,6 +57,11 @@ type CalendarDayOwnProps = {
57
57
  * Is the `<Calendar.Day />` selected
58
58
  */
59
59
  isSelected?: boolean
60
+ /**
61
+ * Screen reader label appended to the date label when the day is selected.
62
+ * Used to announce the selected state to assistive technologies.
63
+ */
64
+ selectedLabel?: string
60
65
  /**
61
66
  * Is the `<Calendar.Day />` today
62
67
  */
@@ -114,6 +119,7 @@ const allowedProps: AllowedPropKeys = [
114
119
  'label',
115
120
  'interaction',
116
121
  'isSelected',
122
+ 'selectedLabel',
117
123
  'isToday',
118
124
  'isOutsideMonth',
119
125
  'onClick',
@@ -33,6 +33,7 @@ type: example
33
33
  currentDate="2023-12-15"
34
34
  disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
35
35
  selectedDate={selectedDate}
36
+ selectedLabel="selected"
36
37
  onRequestRenderNextMonth={(_e, requestedMonth) =>
37
38
  setVisibleMonth(requestedMonth)
38
39
  }
@@ -64,6 +65,7 @@ type: example
64
65
  currentDate="2024-02-29"
65
66
  disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
66
67
  selectedDate={selectedDate}
68
+ selectedLabel="selected"
67
69
  onRequestRenderNextMonth={(_e, requestedMonth) =>
68
70
  setVisibleMonth(requestedMonth)
69
71
  }
@@ -436,9 +436,13 @@ class Calendar extends Component<CalendarProps, CalendarState> {
436
436
  <td key={day.props.date} role={role}>
437
437
  {role === 'presentation'
438
438
  ? safeCloneElement(day, {
439
- 'aria-describedby': this._weekdayHeaderIds[i]
439
+ 'aria-describedby': this._weekdayHeaderIds[i],
440
+ role: 'option'
440
441
  })
441
- : day}
442
+ : safeCloneElement(day, {
443
+ 'aria-describedby': this._weekdayHeaderIds[i],
444
+ role: 'button'
445
+ })}
442
446
  </td>
443
447
  ))}
444
448
  </tr>
@@ -488,7 +492,7 @@ class Calendar extends Component<CalendarProps, CalendarState> {
488
492
  }
489
493
 
490
494
  renderDefaultdays() {
491
- const { selectedDate } = this.props
495
+ const { selectedDate, selectedLabel } = this.props
492
496
  const { visibleMonth, today } = this.state
493
497
  // Sets it to the first local day of the week counting back from the start of the month.
494
498
  // Note that first day depends on the locale, e.g. it's Sunday in the US and
@@ -525,6 +529,7 @@ class Calendar extends Component<CalendarProps, CalendarState> {
525
529
  isToday={date.isSame(today, 'day')}
526
530
  isOutsideMonth={!date.isSame(visibleMonth, 'month')}
527
531
  label={date.format('D MMMM YYYY')} // used by screen readers
532
+ selectedLabel={selectedLabel}
528
533
  onClick={this.handleDayClick}
529
534
  interaction={this.isDisabledDate(date) ? 'disabled' : 'enabled'}
530
535
  >
@@ -130,6 +130,11 @@ type CalendarOwnProps = {
130
130
  * ISO date string for the selected date. It needs onDateSelected to be specified too.
131
131
  */
132
132
  selectedDate?: string
133
+ /**
134
+ * Screen reader label appended to the date label when the day is selected.
135
+ * Used to announce the selected state to assistive technologies.
136
+ */
137
+ selectedLabel?: string
133
138
  /**
134
139
  * A timezone identifier in the format: *Area/Location*
135
140
  *
@@ -194,6 +199,7 @@ const allowedProps: AllowedPropKeys = [
194
199
  'renderWeekdayLabels',
195
200
  'role',
196
201
  'selectedDate',
202
+ 'selectedLabel',
197
203
  'timezone',
198
204
  'visibleMonth'
199
205
  ]
@@ -116,6 +116,7 @@ class Day extends Component<CalendarDayProps> {
116
116
  interaction,
117
117
  isOutsideMonth,
118
118
  isSelected,
119
+ selectedLabel,
119
120
  isToday,
120
121
  onClick,
121
122
  onKeyDown,
@@ -163,7 +164,11 @@ class Day extends Component<CalendarDayProps> {
163
164
  data-cid="Calendar.Day"
164
165
  >
165
166
  <span css={styles?.day}>
166
- <AccessibleContent alt={label}>
167
+ <AccessibleContent
168
+ alt={
169
+ selectedLabel && isSelected ? `${label}, ${selectedLabel}` : label
170
+ }
171
+ >
167
172
  {callRenderProp(children)}
168
173
  </AccessibleContent>
169
174
  </span>
@@ -57,6 +57,11 @@ type CalendarDayOwnProps = {
57
57
  * Is the `<Calendar.Day />` selected
58
58
  */
59
59
  isSelected?: boolean
60
+ /**
61
+ * Screen reader label appended to the date label when the day is selected.
62
+ * Used to announce the selected state to assistive technologies.
63
+ */
64
+ selectedLabel: string
60
65
  /**
61
66
  * Is the `<Calendar.Day />` today
62
67
  */
@@ -117,6 +122,7 @@ const allowedProps: AllowedPropKeys = [
117
122
  'label',
118
123
  'interaction',
119
124
  'isSelected',
125
+ 'selectedLabel',
120
126
  'isToday',
121
127
  'isOutsideMonth',
122
128
  'onClick',
@@ -33,6 +33,7 @@ type: example
33
33
  currentDate="2023-12-15"
34
34
  disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
35
35
  selectedDate={selectedDate}
36
+ selectedLabel="selected"
36
37
  onRequestRenderNextMonth={(_e, requestedMonth) =>
37
38
  setVisibleMonth(requestedMonth)
38
39
  }
@@ -64,6 +65,7 @@ type: example
64
65
  currentDate="2024-02-29"
65
66
  disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
66
67
  selectedDate={selectedDate}
68
+ selectedLabel="selected"
67
69
  onRequestRenderNextMonth={(_e, requestedMonth) =>
68
70
  setVisibleMonth(requestedMonth)
69
71
  }
@@ -422,26 +422,43 @@ class Calendar extends Component<CalendarProps, CalendarState> {
422
422
  `[Calendar] should have exactly ${Calendar.DAY_COUNT} children. ${length} provided.`
423
423
  )
424
424
 
425
- return childrenArr
426
- .reduce((days: ReactElement<any>[][], day, i) => {
427
- const index = Math.floor(i / 7)
428
- if (!days[index]) days.push([])
429
- days[index].push(day)
430
- return days // 7xN 2D array of `Day`s
431
- }, [])
432
- .map((row) => (
433
- <tr key={`row${row[0].props.date}`} role={role}>
434
- {row.map((day, i) => (
435
- <td key={day.props.date} role={role}>
436
- {role === 'presentation'
437
- ? safeCloneElement(day, {
438
- 'aria-describedby': this._weekdayHeaderIds[i]
439
- })
440
- : day}
441
- </td>
442
- ))}
443
- </tr>
444
- ))
425
+ return (
426
+ childrenArr
427
+ .reduce((days: ReactElement<any>[][], day, i) => {
428
+ const index = Math.floor(i / 7)
429
+ if (!days[index]) days.push([])
430
+ days[index].push(day)
431
+ return days // 7xN 2D array of `Day`s
432
+ }, [])
433
+ // TODO: Implement proper WAI-ARIA grid pattern for table mode to enable aria-selected on days.
434
+ // Currently in table mode, days use role="button" which does not support aria-selected
435
+ // (axe violation), so selection is announced via selectedLabel text only.
436
+ //
437
+ // To implement properly (per https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/):
438
+ // 1. Add role="grid" to the <table> in table mode (renderBody)
439
+ // 2. Add role="row" to each <tr>
440
+ // 3. Set role="gridcell" + aria-selected on each <td> (not on the Day)
441
+ // 4. Keep Day as role="button" inside the gridcell
442
+ // This allows aria-selected to live on the <td role="gridcell"> which is spec-correct,
443
+ // while the Day button remains the interactive element.
444
+ .map((row) => (
445
+ <tr key={`row${row[0].props.date}`} role={role}>
446
+ {row.map((day, i) => (
447
+ <td key={day.props.date} role={role}>
448
+ {role === 'presentation'
449
+ ? safeCloneElement(day, {
450
+ 'aria-describedby': this._weekdayHeaderIds[i],
451
+ role: 'option'
452
+ })
453
+ : safeCloneElement(day, {
454
+ 'aria-describedby': this._weekdayHeaderIds[i],
455
+ role: 'button'
456
+ })}
457
+ </td>
458
+ ))}
459
+ </tr>
460
+ ))
461
+ )
445
462
  }
446
463
 
447
464
  locale(): string {
@@ -524,6 +541,7 @@ class Calendar extends Component<CalendarProps, CalendarState> {
524
541
  isToday={date.isSame(today, 'day')}
525
542
  isOutsideMonth={!date.isSame(visibleMonth, 'month')}
526
543
  label={date.format('D MMMM YYYY')} // used by screen readers
544
+ selectedLabel={this.props.selectedLabel}
527
545
  onClick={this.handleDayClick}
528
546
  interaction={this.isDisabledDate(date) ? 'disabled' : 'enabled'}
529
547
  >
@@ -130,6 +130,11 @@ type CalendarOwnProps = {
130
130
  * ISO date string for the selected date. It needs onDateSelected to be specified too.
131
131
  */
132
132
  selectedDate?: string
133
+ /**
134
+ * Screen reader label appended to the date label when the day is selected.
135
+ * Used to announce the selected state to assistive technologies.
136
+ */
137
+ selectedLabel: string
133
138
  /**
134
139
  * A timezone identifier in the format: *Area/Location*
135
140
  *
@@ -194,6 +199,7 @@ const allowedProps: AllowedPropKeys = [
194
199
  'renderWeekdayLabels',
195
200
  'role',
196
201
  'selectedDate',
202
+ 'selectedLabel',
197
203
  'timezone',
198
204
  'visibleMonth'
199
205
  ]