Skip to content

Commit

Permalink
fix focus state for dropdowns, fix taborder for timerange select in t…
Browse files Browse the repository at this point in the history
…imetracker and timeentryrows
  • Loading branch information
Onatcer committed Jan 29, 2025
1 parent cd207c6 commit 1a03637
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 50 deletions.
7 changes: 2 additions & 5 deletions e2e/time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ test('test that updating a the start of an existing time entry in the overview w
'time_entry_range_selector'
);
await timeEntryRangeElement.click();
await page.getByTestId('time_picker_input').first().fill('1');
await page.getByTestId('time_entry_range_start').first().fill('1');
await Promise.all([
page.waitForResponse(async (response) => {
return (
Expand All @@ -204,10 +204,7 @@ test('test that updating a the start of an existing time entry in the overview w
(await response.json()).data.end !== null
);
}),
page
.getByTestId('time_entry_range_end')
.getByTestId('time_picker_input')
.press('Enter'),
page.getByTestId('time_entry_range_end').press('Enter'),
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const activeClass = computed(() => {
<template>
<Badge
size="large"
tag="button"
:class="
twMerge(
'cursor-pointer hover:bg-card-background transition flex',
Expand Down
8 changes: 6 additions & 2 deletions resources/js/packages/ui/src/Input/Dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
flip,
limitShift,
type Placement,
type ReferenceElement,
shift,
useFloating,
} from '@floating-ui/vue';
Expand Down Expand Up @@ -45,6 +44,11 @@ watch(open, (value) => {
layers.value.push(id);
} else {
layers.value = layers.value.filter((layer) => layer !== id);
reference.value
?.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
?.focus();
}
});
Expand All @@ -69,7 +73,7 @@ function onBackgroundClick() {
open.value = false;
}
const reference = ref<null | ReferenceElement>(null);
const reference = ref<null | HTMLElement>(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
placement: props.align,
Expand Down
2 changes: 2 additions & 0 deletions resources/js/packages/ui/src/Input/TimeRangeSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ watch(focused, (newValue, oldValue) => {
<TimePickerSimple
data-testid="time_entry_range_start"
tabindex="0"
@keydown.exact.tab.shift.stop.prevent="emit('close')"
:focus
@changed="updateTimeEntry"
v-model="tempStart"></TimePickerSimple>
Expand All @@ -84,6 +85,7 @@ watch(focused, (newValue, oldValue) => {
v-model="tempEnd"></DatePicker>
</div>
<div class="text-muted" v-else>-- : --</div>
<div tabindex="0" @focusin="emit('close')"></div>
</div>
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function onSelectChange(event: Event) {
<div class="flex-1">
<button
@click="expanded = !expanded"
class="hidden lg:block text-muted w-[105px] px-1 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary">
class="hidden lg:block text-muted w-[110px] px-1 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-medium focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary">
{{ formatStartEnd(timeEntry.start, timeEntry.end) }}
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const emit = defineEmits<{
}>();
const open = ref(false);
const triggerElement = ref<HTMLButtonElement | null>(null);
function closeAndFocusButton() {
triggerElement.value?.focus();
open.value = false;
}
</script>

<template>
Expand All @@ -31,9 +36,10 @@ const open = ref(false);
<template #trigger>
<button
data-testid="time_entry_range_selector"
ref="triggerElement"
:class="
twMerge(
'text-muted w-[105px] px-2 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:text-text-primary focus-visible:ring-ring focus-visible:bg-tertiary',
'text-muted w-[110px] px-2 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:text-text-primary focus-visible:ring-ring focus-visible:bg-tertiary',
showDate
? 'text-xs py-1.5 font-semibold'
: 'text-sm py-1.5 font-medium',
Expand All @@ -53,6 +59,7 @@ const open = ref(false);
emit('changed', newStart, newEnd)
"
focus
@close="closeAndFocusButton"
:start="start"
:end="end">
</TimeRangeSelector>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {
import { computed, defineProps, ref } from 'vue';
import parse from 'parse-duration';
import dayjs from 'dayjs';
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
import TimeRangeSelector from '@/packages/ui/src/Input/TimeRangeSelector.vue';
const props = defineProps<{
start: string;
Expand Down Expand Up @@ -63,32 +61,14 @@ function selectInput(event: Event) {
</script>

<template>
<Dropdown
v-model="open"
@submit="open = false"
align="bottom"
:close-on-content-click="false">
<template #trigger>
<input
data-testid="time_entry_duration_input"
class="text-white w-[90px] px-2 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring"
@focus="selectInput"
@keydown.tab="open = false"
@blur="updateTimerAndStartLiveTimerUpdate"
@keydown.enter="updateTimerAndStartLiveTimerUpdate"
v-model="currentTime" />
</template>
<template #content>
<TimeRangeSelector
@changed="
(newStart: string, newEnd: string) =>
emit('changed', newStart, newEnd)
"
:start="start"
:end="end">
</TimeRangeSelector>
</template>
</Dropdown>
<input
data-testid="time_entry_duration_input"
class="text-white w-[90px] px-2 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring"
@focus="selectInput"
@keydown.tab="open = false"
@blur="updateTimerAndStartLiveTimerUpdate"
@keydown.enter="updateTimerAndStartLiveTimerUpdate"
v-model="currentTime" />
</template>

<style scoped></style>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
import { computed, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import TimeRangeSelector from '@/packages/ui/src/Input/TimeRangeSelector.vue';
import dayjs, { Dayjs } from 'dayjs';
import parse from 'parse-duration';
Expand Down Expand Up @@ -28,8 +28,8 @@ function pauseLiveTimerUpdate(event: FocusEvent) {
function onTimeEntryEnterPress() {
updateTimerAndStartLiveTimerUpdate();
const activeElement = document.activeElement as HTMLElement;
activeElement?.blur();
//const activeElement = document.activeElement as HTMLElement;
// activeElement?.blur();
}
const currentTime = computed({
Expand Down Expand Up @@ -111,6 +111,7 @@ function isHHMM(value: string): boolean {
function parseHHMM(value: string): string[] | null {
return value.match(HHMMtimeRegex);
}
const temporaryCustomTimerEntry = ref<string>('');
async function updateTimeRange(newStart: string) {
Expand All @@ -132,11 +133,31 @@ const startTime = computed(() => {
return dayjs().utc().format();
});
const inputField = ref<HTMLInputElement | null>(null);
watch(open, (isOpen) => {
if (!isOpen) {
inputField.value?.focus();
const timeRangeSelector = ref<HTMLElement | null>(null);
function openModalOnTab(e: FocusEvent) {
// check if the source is inside the dropdown
const source = e.relatedTarget as HTMLElement;
if (source && window.document.body.contains(source)) {
open.value = true;
}
});
}
function focusNextElement(e: KeyboardEvent) {
if (open.value) {
e.preventDefault();
const focusableElement = timeRangeSelector.value?.querySelector<HTMLElement>(

Check warning on line 150 in resources/js/packages/ui/src/TimeTracker/TimeTrackerRangeSelector.vue

View workflow job for this annotation

GitHub Actions / build

Insert `⏎···········`
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'

Check warning on line 151 in resources/js/packages/ui/src/TimeTracker/TimeTrackerRangeSelector.vue

View workflow job for this annotation

GitHub Actions / build

Insert `····`
);

Check warning on line 152 in resources/js/packages/ui/src/TimeTracker/TimeTrackerRangeSelector.vue

View workflow job for this annotation

GitHub Actions / build

Insert `····`
focusableElement?.focus();
}
}
function closeAndFocusInput() {
inputField.value?.focus();
open.value = false;
}
</script>

<template>
Expand All @@ -150,20 +171,26 @@ watch(open, (isOpen) => {
<input
placeholder="00:00:00"
@focus="pauseLiveTimerUpdate"
@focusin="openModalOnTab"
@keydown.exact.tab="focusNextElement"
@keydown.exact.shift.tab="open = false"
ref="inputField"
data-testid="time_entry_time"
@blur="updateTimerAndStartLiveTimerUpdate"
@keydown.enter="onTimeEntryEnterPress"
v-model="currentTime"
class="w-[110px] lg:w-[130px] h-full text-white py-2.5 rounded-r-lg text-center px-4 text-base lg:text-lg font-bold bg-card-background border-none placeholder-muted focus:ring-0 transition"
class="w-[110px] lg:w-[130px] h-full text-white py-2.5 rounded-lg border-border-secondary border text-center px-4 text-base lg:text-lg font-bold bg-card-background border-none placeholder-muted focus:ring-0 transition"
type="text" />
</template>
<template #content>
<TimeRangeSelector
@changed="updateTimeRange"
:start="startTime"
:end="null">
</TimeRangeSelector>
<div ref="timeRangeSelector">
<TimeRangeSelector
@changed="updateTimeRange"
@close="closeAndFocusInput"
:start="startTime"
:end="null">
</TimeRangeSelector>
</div>
</template>
</Dropdown>
</div>
Expand Down

0 comments on commit 1a03637

Please sign in to comment.