Skip to content

Commit

Permalink
Merge pull request #664 from UnknownSean8/feat/a11y-expansion-cardTop…
Browse files Browse the repository at this point in the history
…icSelection

feat: topics loop selection and tabbing A11y
  • Loading branch information
andrewtavis authored Jan 29, 2024
2 parents 01b82e3 + 610080c commit aa1e44c
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 95 deletions.
156 changes: 77 additions & 79 deletions frontend/components/card/CardTopicSelection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@
<input
v-model="query"
@focus="inputFocus = true"
@keydown.tab.exact.prevent="tabToFirstTopic()"
@keydown="resetTabIndex()"
id="query"
:display-value="() => query"
:placeholder="$t('components.card-topic-selection.selector-placeholder')"
class="w-full py-2 pl-4 topicInput rounded-md text-light-special-text dark:text-dark-special-text bg-light-content dark:bg-dark-content elem-shadow-sm focus-brand"
/>
<ul class="hidden gap-2 sm:flex sm:flex-wrap">
<ShieldTopic
v-for="(t, index) of filteredTopics"
v-for="t of filteredTopics"
@click="selectTopic(t)"
@keydown.enter.prevent="selectTopic(t)"
@keydown.tab.prevent="tabToConnect($event)"
@keydown="keydownEvent(index, $event)"
@keydown="keydownEvent($event)"
:key="t.value"
:topic="t.label"
class="topic max-sm:w-full"
Expand All @@ -37,11 +36,10 @@
>
<ShieldTopic
v-if="moreOptionsShown || inputFocus"
v-for="(t, index) of filteredTopics"
v-for="t of filteredTopics"
@click="selectTopic(t)"
@keydown.enter.prevent="selectTopic(t)"
@keydown.tab.prevent="tabToConnect($event)"
@keydown="mobileKeyboardEvent(index, $event)"
@keydown="mobileKeyboardEvent($event)"
:key="t.value + '-selected-only'"
:topic="t.label"
class="mobileTopic max-sm:w-full"
Expand All @@ -50,13 +48,12 @@
/>
<ShieldTopic
v-else
v-for="(t, index) of selectedTopicTags.sort((a, b) =>
v-for="t of selectedTopicTags.sort((a, b) =>
a.value.localeCompare(b.value)
)"
@click="selectTopic(t)"
@keydown.enter.prevent="selectTopic(t)"
@keydown.tab.prevent="tabToConnect($event)"
@keydown="mobileKeyboardEvent(index, $event)"
@keydown="mobileKeyboardEvent($event)"
:key="t.value"
:topic="t.label"
class="mobileTopic max-sm:w-full"
Expand Down Expand Up @@ -98,91 +95,66 @@ const moreOptionsShown = ref(false);
const inputFocus = ref(false);
const emit = defineEmits(["update:modelValue"]);
// Tab from topic input to the first topic
const tabToFirstTopic = () => {
let firstTopic: HTMLElement | null;
if (window.matchMedia("(min-width: 640px)").matches) {
firstTopic = document.querySelector(".topic");
} else {
firstTopic = document.querySelector(".mobileTopic");
}
firstTopic?.focus();
};
// Tab from topic to T&C checkbox
const tabToConnect = (e: KeyboardEvent) => {
e.preventDefault();
const topicInput: HTMLElement | null = document.querySelector(".topicInput");
const connect: HTMLElement | null = document.querySelector(".connect");
// Shift-Tab back to topic input from any topic
if (e.shiftKey) {
topicInput?.focus();
return;
}
const resetTabIndex = () => {
const topic: HTMLElement[] = Array.from(document.querySelectorAll(".topic"));
const mobileTopic: HTMLElement[] = Array.from(
document.querySelectorAll(".mobileTopic")
);
connect?.focus();
topic.forEach((topic) => (topic.tabIndex = -1));
topic[0].tabIndex = 0;
mobileTopic.forEach((topic) => (topic.tabIndex = -1));
mobileTopic[0].tabIndex = 0;
};
const keydownEvent = (index: number, e: KeyboardEvent) => {
e.preventDefault();
let index = 0;
const keydownEvent = (e: KeyboardEvent) => {
const topics: HTMLElement[] = Array.from(document.querySelectorAll(".topic"));
const upTop = topics[index].getBoundingClientRect().top - 38;
const upLeft = topics[index].getBoundingClientRect().left - 38;
const upResult = topics.filter(
(topic) =>
topic.getBoundingClientRect().top == upTop &&
topic.getBoundingClientRect().left >= upLeft
);
const downTop = topics[index].getBoundingClientRect().top + 38;
const downLeft = topics[index].getBoundingClientRect().left - 38;
const downResult = topics.filter(
(topic) =>
topic.getBoundingClientRect().top == downTop &&
topic.getBoundingClientRect().left >= downLeft
);
switch (e.code) {
case "ArrowUp":
if (upResult.length != 0) {
upResult[0].focus();
case "ArrowLeft":
e.preventDefault();
if (index > 0) {
index--;
} else {
const lastTopicInRow = topics.filter(
(topic) => topic.getBoundingClientRect().top == upTop
);
lastTopicInRow[lastTopicInRow.length - 1].focus();
index = topics.length - 1;
}
break;
case "ArrowDown":
if (downResult.length != 0) {
downResult[0].focus();
case "ArrowRight":
e.preventDefault();
if (index < topics.length - 1) {
index++;
} else {
const lastTopicInRow = topics.filter(
(topic) => topic.getBoundingClientRect().top == downTop
);
lastTopicInRow[lastTopicInRow.length - 1].focus();
index = 0;
}
break;
case "ArrowLeft":
topics[index - 1].focus();
break;
case "ArrowRight":
topics[index + 1].focus();
break;
case "Enter":
topics[index - 1]?.focus();
e.preventDefault();
if (topics[index].classList.contains("style-cta-secondary")) {
if (index < topics.length - 1) {
index++;
}
} else {
if (index > 0) {
index--;
} else {
index = 0;
}
}
break;
case "Tab":
index = 0;
break;
}
};
const mobileKeyboardEvent = (index: number, e: KeyboardEvent) => {
e.preventDefault();
topics.forEach((topic) => (topic.tabIndex = -1));
topics[index].tabIndex = 0;
topics[index].focus();
};
const mobileKeyboardEvent = (e: KeyboardEvent) => {
const topics: HTMLElement[] = Array.from(
document.querySelectorAll(".mobileTopic")
);
Expand All @@ -191,17 +163,43 @@ const mobileKeyboardEvent = (index: number, e: KeyboardEvent) => {
case "ArrowUp":
case "ArrowLeft":
e.preventDefault();
topics[index - 1].focus();
if (index > 0) {
index--;
} else {
index = topics.length - 1;
}
break;
case "ArrowDown":
case "ArrowRight":
e.preventDefault();
topics[index + 1].focus();
if (index < topics.length - 1) {
index++;
} else {
index = 0;
}
break;
case "Enter":
topics[index - 1]?.focus();
e.preventDefault();
if (topics[index].classList.contains("style-cta-secondary")) {
if (index < topics.length - 1) {
index++;
}
} else {
if (index > 0) {
index--;
} else {
index = 0;
}
}
break;
case "Tab":
index = 0;
break;
}
topics.forEach((topic) => (topic.tabIndex = -1));
topics[index].tabIndex = 0;
topics[index].focus();
};
const value = computed<Topic[]>({
Expand Down
17 changes: 1 addition & 16 deletions frontend/components/form/checkbox/FormCheckbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<input
@keydown="tabToFirstTopic($event)"
:id="uuid"
class="connect cursor-pointer mb-0 peer appearance-none w-[1.375rem] h-[1.375rem] border border-light-menu-selection rounded-sm bg-light-button dark:bg-dark-button dark:border-dark-menu-selection focus-brand"
class="cursor-pointer mb-0 peer appearance-none w-[1.375rem] h-[1.375rem] border border-light-menu-selection rounded-sm bg-light-button dark:bg-dark-button dark:border-dark-menu-selection focus-brand"
type="checkbox"
v-bind="{ ...$attrs, onChange: updateValue }"
:checked="modelValue"
Expand Down Expand Up @@ -31,21 +31,6 @@ export interface Props {
error?: string;
}
const tabToFirstTopic = (e: KeyboardEvent) => {
let firstTopic: HTMLElement | null;
if (window.matchMedia("(min-width: 640px)").matches) {
firstTopic = document.querySelector(".topic");
} else {
firstTopic = document.querySelector(".mobileTopic");
}
if (e.shiftKey && e.key == "Tab") {
e.preventDefault();
firstTopic?.focus();
return;
}
};
const props = withDefaults(defineProps<Props>(), {
label: "",
error: "",
Expand Down

0 comments on commit aa1e44c

Please sign in to comment.