Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: solve several a11y issues #1184

Merged
merged 7 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/components/Carousel.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sidebarDepth: 2

The **Carousel** component is a slideshow for cycling through a set of elements — images or text like - a carousel, referred to as slides, by sequentially displaying a subset of one or more slides.
One slide is displayed at a time, and users can activate a next or previous slide control that hides the current slide and "rotates" the next or previous slide into view.
Tthe component implements the W3C ARIA APG [Carousel Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).
The component implements the W3C ARIA APG [Carousel Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).

</div>

Expand Down
110 changes: 56 additions & 54 deletions packages/docs/components/Datepicker.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/docs/components/Field.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ The **Field** component is used to add functionality to controls and to attach/g
| horizontal | Group label and control on the same line for horizontal forms | boolean | - | <code style='white-space: nowrap; padding: 0;'>false</code> |
| label | Field label | string | - | |
| labelFor | Same as native `for` set on the label | string | - | |
| labelId | A unique HTML id for the field label to associate an input with | string | - | <code style='white-space: nowrap; padding: 0;'>useId()</code> |
| labelSize | Vertical size of input | string | `small`, `medium`, `large` | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>field: {<br>&nbsp;&nbsp;labelsize: undefined<br>}</code> |
| message | Help message text | string | - | |
| messageId | A unique HTML id for the field message to associate an input with | string | - | <code style='white-space: nowrap; padding: 0;'>useId()</code> |
| messageTag | Message element tag name | DynamicComponent | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>field: {<br>&nbsp;&nbsp;messageTag: "p"<br>}</code> |
| mobileBreakpoint | Mobile breakpoint as `max-width` value | string | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>field: {<br>&nbsp;&nbsp;mobileBreakpoint: undefined<br>}</code> |
| override | Override existing theme classes completely | boolean | - | |
Expand Down
85 changes: 44 additions & 41 deletions packages/docs/components/Timepicker.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/oruga/src/components/carousel/Carousel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const emits = defineEmits<{

const rootRef = useTemplateRef("rootElement");

// provided data is a computed ref to enjure reactivity
// provided data is a computed ref to ensure reactivity
const provideData = computed<CarouselComponent>(() => ({
activeIndex: activeIndex.value,
indicators: props.indicators,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function switchGallery(value): void {
@click="switchGallery(true)">
<o-carousel-item v-for="(item, i) in items" :key="i" clickable>
<div class="image">
<img :src="item.image" />
<img :src="item.image" :alt="item.title" />
</div>
</o-carousel-item>

Expand All @@ -79,7 +79,7 @@ function switchGallery(value): void {
clickable
item-class="img-indicator"
item-active-class="img-indicator-active">
<img :src="item.image" />
<img :src="item.image" :alt="item.title" />
</o-carousel-item>
</o-carousel>
</template>
Expand Down
2 changes: 1 addition & 1 deletion packages/oruga/src/components/carousel/examples/list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const items = [
:items-to-list="itemsToList"
:repeat="repeat">
<o-carousel-item v-for="(item, i) in items" :key="i">
<img :src="item.image" />
<img :src="item.image" :alt="item.title" />
</o-carousel-item>
</o-carousel>
<p><b>Current slide index:</b> {{ carousel }}</p>
Expand Down
2 changes: 1 addition & 1 deletion packages/oruga/src/components/carousel/examples/readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
The **Carousel** component is a slideshow for cycling through a set of elements — images or text like - a carousel, referred to as slides, by sequentially displaying a subset of one or more slides.
One slide is displayed at a time, and users can activate a next or previous slide control that hides the current slide and "rotates" the next or previous slide into view.
Tthe component implements the W3C ARIA APG [Carousel Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).
The component implements the W3C ARIA APG [Carousel Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { afterEach, describe, expect, test } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";
import { axe } from "jest-axe";
import { nextTick } from "vue";

import OCheckbox from "../Checkbox.vue";
import type { CheckboxProps } from "../props";

describe("Checkbox axe tests", () => {
enableAutoUnmount(afterEach);

const a11yCases = [
const a11yCases: { title: string; props?: CheckboxProps<unknown> }[] = [
{
title: "axe checkbox - base case",
props: { label: "Checkbox Label" },
Expand Down Expand Up @@ -43,6 +45,7 @@ describe("Checkbox axe tests", () => {
props: { ...props },
attachTo: document.body,
});
await nextTick(); // await child items got rendered

expect(await axe(wrapper.element)).toHaveNoViolations();
});
Expand Down
6 changes: 6 additions & 0 deletions packages/oruga/src/components/datepicker/Datepicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ const props = withDefaults(
getDefault("datepicker.ariaNextLabel", "Next Page"),
ariaPreviousLabel: () =>
getDefault("datepicker.ariaNextLabel", "Previous Page"),
ariaSelectMonthLabel: () =>
getDefault("datepicker.ariaSelectMonthLabel", "Select Month"),
ariaSelectYearLabel: () =>
getDefault("datepicker.ariaSelectYearLabel", "Select Year"),
inputClasses: () => getDefault("datepicker.inputClasses"),
dropdownClasses: () => getDefault("datepicker.dropdownClasses"),
selectClasses: () => getDefault("datepicker.selectClasses"),
Expand Down Expand Up @@ -614,6 +618,7 @@ defineExpose({ focus: () => pickerRef.value?.focus(), value: vmodel });
:disabled="disabled"
:size="size"
:options="listOfMonths"
:aria-label="ariaSelectMonthLabel"
:use-html5-validation="false"
@keydown.left.stop.prevent="prev"
@keydown.right.stop.prevent="next" />
Expand All @@ -624,6 +629,7 @@ defineExpose({ focus: () => pickerRef.value?.focus(), value: vmodel });
:disabled="disabled"
:size="size"
:options="listOfYears"
:aria-label="ariaSelectYearLabel"
:use-html5-validation="false"
@keydown.left.stop.prevent="prev"
@keydown.right.stop.prevent="next"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const active = ref(false);
<o-button
icon-left="calendar"
variant="primary"
aria-label="Open Calendar"
@click="active = !active" />
</o-field>
</section>
Expand Down
5 changes: 4 additions & 1 deletion packages/oruga/src/components/datepicker/examples/slots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ function selectMonth(month: number | undefined): void {
:first-day-of-week="1"
placeholder="Click to select...">
<template #trigger>
<o-button icon-left="calendar" variant="primary" />
<o-button
icon-left="calendar"
variant="primary"
aria-label="Open Calendar" />
</template>

<template #header>
Expand Down
6 changes: 5 additions & 1 deletion packages/oruga/src/components/datepicker/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,12 @@ export type DatepickerProps<
) => string);
/** Accessibility next button aria label */
ariaNextLabel?: string;
/** Accessibility previous button aria label */
/** Accessibility previous button aria label */
ariaPreviousLabel?: string;
/** Accessibility month select aria label */
ariaSelectMonthLabel?: string;
/** Accessibility year select aria label */
ariaSelectYearLabel?: string;
} & DatepickerClasses;

// class props (will not be displayed in the docs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`ODatepicker > render correctly 1`] = `
"<div data-oruga="datepicker" class="o-dpck">
<div data-oruga="dropdown" class="o-drop o-tpck__dropdown o-drop--position-bottom-left">
<div class="o-drop__trigger" aria-haspopup="listbox" aria-disabled="false" aria-controls="v-0">
<div class="o-drop__trigger" aria-haspopup="menu" aria-disabled="false" aria-controls="v-0">
<!--
@slot Override the trigger element, default is label prop
@binding {boolean} active - dropdown active state
Expand Down Expand Up @@ -43,7 +43,7 @@ exports[`ODatepicker > render correctly 1`] = `
</button>
<div class="o-dpck__header__list">
<div class="o-ctrl-sel" data-oruga="select">
<!--v-if--><select id="v-3" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if--><select aria-label="Select Month" id="v-3" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand All @@ -64,7 +64,7 @@ exports[`ODatepicker > render correctly 1`] = `
<!--v-if-->
</div>
<div class="o-ctrl-sel" data-oruga="select">
<!--v-if--><select id="v-4" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if--><select aria-label="Select Year" id="v-4" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`ODatetimepicker tests > render correctly 1`] = `
"<div data-oruga="datetimepicker" class="o-dpck o-dtpck__date">
<div data-oruga="dropdown" class="o-drop o-tpck__dropdown o-drop--position-bottom-left">
<div class="o-drop__trigger" aria-haspopup="listbox" aria-disabled="false" aria-controls="v-0">
<div class="o-drop__trigger" aria-haspopup="menu" aria-disabled="false" aria-controls="v-0">
<!--
@slot Override the trigger element, default is label prop
@binding {boolean} active - dropdown active state
Expand Down Expand Up @@ -43,7 +43,7 @@ exports[`ODatetimepicker tests > render correctly 1`] = `
</button>
<div class="o-dpck__header__list">
<div class="o-ctrl-sel" data-oruga="select">
<!--v-if--><select id="v-3" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if--><select aria-label="Select Month" id="v-3" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand All @@ -64,7 +64,7 @@ exports[`ODatetimepicker tests > render correctly 1`] = `
<!--v-if-->
</div>
<div class="o-ctrl-sel" data-oruga="select">
<!--v-if--><select id="v-4" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if--><select aria-label="Select Year" id="v-4" data-oruga-input="select" class="o-sel o-sel-arrow" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand Down Expand Up @@ -344,7 +344,7 @@ exports[`ODatetimepicker tests > render correctly 1`] = `
@slot Override the label, default is label prop
-->
<div class="" data-oruga="select">
<!--v-if--><select id="v-7" data-oruga-input="select" class="o-tpck__select" autocomplete="off">
<!--v-if--><select aria-label="Select Hour" id="v-7" data-oruga-input="select" class="o-tpck__select" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand Down Expand Up @@ -377,7 +377,7 @@ exports[`ODatetimepicker tests > render correctly 1`] = `
<!--v-if-->
</div><span class="o-tpck__separtor">:</span>
<div class="" data-oruga="select">
<!--v-if--><select id="v-8" data-oruga-input="select" class="o-tpck__select" autocomplete="off">
<!--v-if--><select aria-label="Select Minute" id="v-8" data-oruga-input="select" class="o-tpck__select" autocomplete="off">
<!--v-if-->
<!--
@slot Override the options, default is options prop
Expand Down
6 changes: 3 additions & 3 deletions packages/oruga/src/components/dropdown/Dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const emits = defineEmits<{
const triggerRef = ref<HTMLElement>();
const menuRef = ref<HTMLElement | Component>();

// provided data is a computed ref to enjure reactivity
// provided data is a computed ref to ensure reactivity
const provideData = computed<DropdownComponent<T>>(() => ({
disabled: props.disabled,
multiple: isTrueish(props.multiple),
Expand Down Expand Up @@ -533,14 +533,14 @@ defineExpose({ $trigger: triggerRef, $content: menuRef, value: vmodel });
:class="triggerClasses"
:role="selectable ? 'combobox' : undefined"
:tabindex="disabled ? -1 : null"
aria-haspopup="listbox"
:aria-haspopup="selectable ? 'listbox' : 'menu'"
:aria-expanded="selectable ? isActive : undefined"
:aria-activedescendant="
focusedItem ? `${menuId}-${focusedItem.identifier}` : undefined
"
:aria-disabled="disabled"
:aria-controls="menuId"
:aria-labelledby="labelId"
:aria-labelledby="selectable ? labelId : undefined"
:aria-label="selectable ? ariaLabel : undefined"
@click="onTriggerClick"
@contextmenu="onTriggerContextMenu"
Expand Down
2 changes: 1 addition & 1 deletion packages/oruga/src/components/dropdown/DropdownItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const itemValue = props.value ?? useId();

const rootRef = useTemplateRef<Element>("rootElement");

// provided data is a computed ref to enjure reactivity
// provided data is a computed ref to ensure reactivity
const providedData = computed<DropdownItemComponent<T>>(() => ({
...props,
$el: rootRef.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ exports[`ODropdown tests > render correctly with items 1`] = `

exports[`ODropdown tests > render correctly with options 1`] = `
"<div data-oruga="dropdown" class="o-drop o-drop--position-bottom-left">
<div class="o-drop__trigger" aria-haspopup="listbox" aria-disabled="false" aria-controls="v-0">
<div class="o-drop__trigger" aria-haspopup="menu" aria-disabled="false" aria-controls="v-0">
<!--
@slot Override the trigger element, default is label prop
@binding {boolean} active - dropdown active state
Expand Down
20 changes: 9 additions & 11 deletions packages/oruga/src/components/field/Field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ const props = withDefaults(defineProps<FieldProps>(), {
label: undefined,
labelSize: () => getDefault("field.labelsize"),
labelFor: undefined,
labelId: () => useId(),
message: undefined,
messageTag: () => getDefault("field.messageTag", "p"),
messageId: () => useId(),
grouped: false,
groupMultiline: false,
horizontal: false,
Expand All @@ -45,12 +47,6 @@ const props = withDefaults(defineProps<FieldProps>(), {

const { isMobile } = useMatchMedia(props.mobileBreakpoint);

/** a unique id for the field message to associate an input with */
const messageId = useId();

/** a unique id for the field label to associate an input with */
const labelId = useId();

/** the unique id for the input to associate the label with */
const inputId = ref(props.labelFor);
watch(
Expand Down Expand Up @@ -142,20 +138,20 @@ function setInputId(value: string): void {
}

const inputAttrs = computed(() => ({
"aria-labelledby": labelId,
"aria-labelledby": props.labelId,
...(fieldVariant.value === "error"
? { "aria-errormessage": messageId }
: { "aria-describedby": messageId }),
? { "aria-errormessage": props.messageId }
: { "aria-describedby": props.messageId }),
}));

// Provided data is a computed ref to enjure reactivity.
// Provided data is a computed ref to ensure reactivity.
const provideData = computed<FieldData>(() => ({
$el: rootRef.value,
props,
hasInnerField: hasInnerField.value,
variant: fieldVariant.value,
message: fieldMessage.value,
labelId,
labelId: props.labelId,
inputAttrs: inputAttrs.value,
addInnerField,
setInputId,
Expand Down Expand Up @@ -278,6 +274,8 @@ const innerFieldClasses = defineClasses(
v-else
:variant="fieldVariant"
:addons="false"
:label-id="labelId"
:message-id="messageId"
:message-tag="messageTag"
:message-class="messageClass">
<!-- render inner default slot element -->
Expand Down
Loading