Keyboard navigation fixes (still not perfect but better).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="disk-space-container" ref="containerRef">
|
||||
<div class="disk-space-container" ref="containerRef" tabindex="0" @keydown.enter="handleClick" @keydown.space.prevent="handleClick">
|
||||
<div
|
||||
ref="widgetRef"
|
||||
class="disk-space-widget"
|
||||
@@ -352,6 +352,11 @@ onUnmounted(() => {
|
||||
position: relative;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.disk-space-container:focus .disk-space-widget:not(.expanded) {
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
.disk-space-widget {
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useMainStore } from '@/stores/main'
|
||||
import { Doc } from '@/repositories/Document'
|
||||
import FileRenameInput from './FileRenameInput.vue'
|
||||
@@ -135,6 +135,17 @@ defineExpose({
|
||||
isCursor() {
|
||||
return store.cursor && editing.value === null
|
||||
},
|
||||
focusFirst() {
|
||||
const docs = props.documents
|
||||
if (docs.length > 0) {
|
||||
store.cursor = docs[0]!.key
|
||||
// Also focus the element directly (watchEffect won't trigger if cursor unchanged)
|
||||
nextTick(() => {
|
||||
const a = document.querySelector(`#file-${store.cursor} .name a`) as HTMLAnchorElement | null
|
||||
if (a) a.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
cursorRename() {
|
||||
editing.value = props.documents.find(doc => doc.key === store.cursor) ?? null
|
||||
},
|
||||
@@ -150,7 +161,12 @@ defineExpose({
|
||||
},
|
||||
up(ev: KeyboardEvent) { this.cursorMove(-1, ev) },
|
||||
down(ev: KeyboardEvent) { this.cursorMove(1, ev) },
|
||||
left(ev: KeyboardEvent) { router.back() },
|
||||
left(ev: KeyboardEvent) {
|
||||
// Only go back if we're in a subfolder (not at root)
|
||||
if (props.path.length > 0) {
|
||||
router.back()
|
||||
}
|
||||
},
|
||||
right(ev: KeyboardEvent) {
|
||||
const a = document.querySelector(`#file-${store.cursor} a`) as HTMLAnchorElement | null
|
||||
if (a) a.click()
|
||||
@@ -190,9 +206,17 @@ defineExpose({
|
||||
scrolltimer = null
|
||||
}, 300)
|
||||
}
|
||||
if (moveto === N) focusBreadcrumb()
|
||||
// When leaving the file list: up goes to breadcrumbs, down goes to header
|
||||
if (moveto === N) {
|
||||
if (d < 0) focusBreadcrumb()
|
||||
else focusHeader()
|
||||
}
|
||||
}
|
||||
})
|
||||
const focusHeader = () => {
|
||||
const el = document.querySelector('.headermain input[type="search"]') as HTMLElement | null
|
||||
if (el) el.focus()
|
||||
}
|
||||
const focusBreadcrumb = () => {
|
||||
const el = document.querySelector('.breadcrumb') as HTMLElement | null
|
||||
if (el) el.focus()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useMainStore } from '@/stores/main'
|
||||
import { Doc } from '@/repositories/Document'
|
||||
import { connect, controlUrl } from '@/repositories/WS'
|
||||
@@ -82,6 +82,17 @@ defineExpose({
|
||||
isCursor() {
|
||||
return store.cursor && editing.value === null
|
||||
},
|
||||
focusFirst() {
|
||||
const docs = props.documents
|
||||
if (docs.length > 0) {
|
||||
store.cursor = docs[0]!.key
|
||||
// Also focus the element directly (watchEffect won't trigger if cursor unchanged)
|
||||
nextTick(() => {
|
||||
const a = document.querySelector(`#file-${store.cursor}`) as HTMLAnchorElement | null
|
||||
if (a) a.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
cursorRename() {
|
||||
editing.value = props.documents.find(doc => doc.key === store.cursor) ?? null
|
||||
},
|
||||
@@ -144,9 +155,17 @@ defineExpose({
|
||||
scrolltimer = null
|
||||
}, 300)
|
||||
}
|
||||
if (moveto === N) focusBreadcrumb()
|
||||
// When leaving the file list: up goes to breadcrumbs, down goes to header
|
||||
if (moveto === N) {
|
||||
if (d < 0) focusBreadcrumb()
|
||||
else focusHeader()
|
||||
}
|
||||
}
|
||||
})
|
||||
const focusHeader = () => {
|
||||
const el = document.querySelector('.headermain input[type="search"]') as HTMLElement | null
|
||||
if (el) el.focus()
|
||||
}
|
||||
const focusBreadcrumb = () => {
|
||||
const el = document.querySelector('.breadcrumb') as HTMLElement | null
|
||||
if (el) el.focus()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="smallgap"></div>
|
||||
<SvgButton name="eye" @click="store.prefs.gallery = !store.prefs.gallery" tooltip="Details/Gallery" />
|
||||
<div class="search-group">
|
||||
<SvgButton name="find" @click="focusSearch" tooltip="Search" />
|
||||
<SvgButton name="find" tabindex="-1" @click="focusSearch" tooltip="Search" />
|
||||
<input
|
||||
ref="search"
|
||||
type="search"
|
||||
@@ -159,6 +159,9 @@ defineExpose({
|
||||
.search-group:focus-within {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.search-group:focus-within {
|
||||
box-shadow: 0 0 0 2px var(--accent-color, #f80);
|
||||
}
|
||||
.search-group:hover :deep(button.action-button),
|
||||
.search-group:focus-within :deep(button.action-button) {
|
||||
transform: scale(1.1);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<button
|
||||
class="action-button"
|
||||
:tabindex="tabindex"
|
||||
@mouseenter="tooltip?.startHover"
|
||||
@mousemove="tooltip?.updatePosition"
|
||||
@mouseleave="tooltip?.endHover"
|
||||
@@ -19,6 +20,7 @@ import CursorTooltip from './CursorTooltip.vue'
|
||||
const props = defineProps<{
|
||||
name: IconName
|
||||
tooltip?: string
|
||||
tabindex?: string | number
|
||||
}>()
|
||||
|
||||
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
|
||||
|
||||
Reference in New Issue
Block a user