feat(web): support Enter key to confirm archive and other dialogs (#1490)

This commit is contained in:
qer 2026-07-08 13:24:44 +08:00 committed by GitHub
parent 2206d21327
commit b30a45efec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 3 deletions

View file

@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---
web: Press Enter to confirm in archive and other confirmation dialogs.

View file

@ -3,11 +3,19 @@
Dialog (height auto, right-aligned footer). The single confirmation surface
for user actions driven app-wide by useConfirmDialog(). -->
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Dialog from '../ui/Dialog.vue';
import Button from '../ui/Button.vue';
withDefaults(defineProps<{
const confirmButtonRef = ref<InstanceType<typeof Button> | null>(null);
function confirmButtonElement(): HTMLElement | null {
const el = confirmButtonRef.value?.$el;
return el instanceof HTMLElement ? el : null;
}
const props = withDefaults(defineProps<{
open: boolean;
title: string;
message?: string;
@ -32,6 +40,33 @@ function onCancel(): void {
emit('update:open', false);
emit('cancel');
}
function onKeydown(event: KeyboardEvent): void {
if (event.key !== 'Enter' || !props.open || props.loading) return;
// Preserve native Enter semantics for interactive controls (buttons, links,
// form fields) so tabbing to Cancel / Close and pressing Enter does not
// accidentally confirm the dialog. Only treat Enter as confirm when focus is
// on a non-interactive part of the dialog.
const target = event.target as HTMLElement | null;
if (
target instanceof HTMLButtonElement ||
target instanceof HTMLAnchorElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement ||
target instanceof HTMLInputElement
) {
return;
}
event.preventDefault();
emit('confirm');
}
if (typeof window !== 'undefined') {
window.addEventListener('keydown', onKeydown);
}
onBeforeUnmount(() => {
if (typeof window !== 'undefined') window.removeEventListener('keydown', onKeydown);
});
</script>
<template>
@ -39,6 +74,7 @@ function onCancel(): void {
:open="open"
:title="title"
height="auto"
:initial-focus="confirmButtonElement"
@update:open="emit('update:open', $event)"
@close="onCancel"
>
@ -47,7 +83,12 @@ function onCancel(): void {
<Button variant="secondary" :disabled="loading" @click="onCancel">
{{ cancelLabel ?? t('common.cancel') }}
</Button>
<Button :variant="variant" :loading="loading" @click="emit('confirm')">
<Button
ref="confirmButtonRef"
:variant="variant"
:loading="loading"
@click="emit('confirm')"
>
{{ confirmLabel ?? t('common.confirm') }}
</Button>
</template>

View file

@ -22,6 +22,9 @@ const props = withDefaults(defineProps<{
/** When false, the body has no padding so the consumer controls layout
* (e.g. a full-bleed side-nav). */
padded?: boolean;
/** Element (or selector / resolver) to receive focus when the dialog opens.
* Falls back to the first focusable element, then the dialog panel. */
initialFocus?: HTMLElement | string | (() => HTMLElement | null | undefined);
}>(), {
closeOnOverlay: true,
closeOnEsc: true,
@ -50,6 +53,18 @@ function focusables(): HTMLElement[] {
return panel.value ? Array.from(panel.value.querySelectorAll<HTMLElement>(FOCUSABLE)) : [];
}
function resolveInitialFocus(): HTMLElement | null {
const { initialFocus } = props;
if (!initialFocus) return null;
if (typeof initialFocus === 'function') {
return initialFocus() ?? null;
}
if (typeof initialFocus === 'string') {
return panel.value?.querySelector<HTMLElement>(initialFocus) ?? null;
}
return panel.value?.contains(initialFocus) ? initialFocus : null;
}
function onKeydown(event: KeyboardEvent) {
if (!props.open) return;
if (event.key === 'Escape' && props.closeOnEsc) {
@ -87,8 +102,9 @@ watch(
openDialogCount.value += 1;
previouslyFocused = document.activeElement;
await nextTick();
const initial = resolveInitialFocus();
const list = focusables();
(list[0] ?? panel.value)?.focus();
(initial ?? list[0] ?? panel.value)?.focus();
} else {
openDialogCount.value = Math.max(0, openDialogCount.value - 1);
if (previouslyFocused instanceof HTMLElement) {