mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 08:44:06 +00:00
## Summary This PR improves keyboard event handling consistency and fixes an issue where pressing Escape in nested input components would unintentionally close parent modals/dialogs. ## Changes ### Keyboard Event Fixes **TagsInput Escape Key Handling** - Added `@keydown.escape.stop` handler to `TagsInputInput.vue` to prevent Escape from bubbling up and closing parent modals - The handler blurs the input and exits editing mode without propagating the event **EditableText keyup → keydown Migration** - Changed `@keyup.enter` to `@keydown.enter` and `@keyup.escape` to `@keydown.escape` - Using `keydown` is more consistent with how other UI frameworks handle these events and provides more responsive feedback - Updated corresponding unit tests to use `keydown` triggers ### Why keydown over keyup? - `keydown` fires immediately when the key is pressed, providing faster perceived response - Better consistency with browser/OS conventions for action triggers - Prevents default behaviors (like form submission) more reliably when needed - Aligns with other keyboard handlers in the codebase ## Testing - Updated `EditableText.test.ts` to use `keydown` events - Updated `NodeHeader.test.ts` to use `keydown.enter` - Manual testing: Escape in TagsInput no longer closes parent modal ## Checklist - [x] Unit tests updated - [x] Keyboard event handlers consistent - [x] No breaking changes to component API ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8204-fix-Consistent-keydown-handling-for-EditableText-and-TagsInput-escape-key-2ef6d73d365081f0ac6bed8bcae57657) by [Unito](https://www.unito.io)
105 lines
2.5 KiB
Vue
105 lines
2.5 KiB
Vue
<template>
|
|
<div class="editable-text">
|
|
<span v-if="!isEditing">
|
|
{{ modelValue }}
|
|
</span>
|
|
<!-- Avoid double triggering finishEditing event when keydown.enter is triggered -->
|
|
<InputText
|
|
v-else
|
|
ref="inputRef"
|
|
v-model:model-value="inputValue"
|
|
v-focus
|
|
type="text"
|
|
size="small"
|
|
fluid
|
|
:pt="{
|
|
root: {
|
|
onBlur: finishEditing,
|
|
...inputAttrs
|
|
}
|
|
}"
|
|
@keydown.enter.capture.stop="blurInputElement"
|
|
@keydown.escape.capture.stop="cancelEditing"
|
|
@click.stop
|
|
@contextmenu.stop
|
|
@pointerdown.stop.capture
|
|
@pointermove.stop.capture
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import InputText from 'primevue/inputtext'
|
|
import { nextTick, ref, watch } from 'vue'
|
|
|
|
const {
|
|
modelValue,
|
|
isEditing = false,
|
|
inputAttrs = {}
|
|
} = defineProps<{
|
|
modelValue: string
|
|
isEditing?: boolean
|
|
inputAttrs?: Record<string, string>
|
|
}>()
|
|
|
|
const emit = defineEmits(['edit', 'cancel'])
|
|
const inputValue = ref<string>(modelValue)
|
|
const inputRef = ref<InstanceType<typeof InputText> | undefined>()
|
|
const isCanceling = ref(false)
|
|
|
|
const blurInputElement = () => {
|
|
// @ts-expect-error - $el is an internal property of the InputText component
|
|
inputRef.value?.$el.blur()
|
|
}
|
|
const finishEditing = () => {
|
|
// Don't save if we're canceling
|
|
if (!isCanceling.value) {
|
|
emit('edit', inputValue.value)
|
|
}
|
|
isCanceling.value = false
|
|
}
|
|
const cancelEditing = () => {
|
|
// Set canceling flag to prevent blur from saving
|
|
isCanceling.value = true
|
|
// Reset to original value
|
|
inputValue.value = modelValue
|
|
// Emit cancel event
|
|
emit('cancel')
|
|
// Blur the input to exit edit mode
|
|
blurInputElement()
|
|
}
|
|
watch(
|
|
() => isEditing,
|
|
async (newVal) => {
|
|
if (newVal) {
|
|
inputValue.value = modelValue
|
|
await nextTick(() => {
|
|
if (!inputRef.value) return
|
|
const fileName = inputValue.value.includes('.')
|
|
? inputValue.value.split('.').slice(0, -1).join('.')
|
|
: inputValue.value
|
|
const start = 0
|
|
const end = fileName.length
|
|
// @ts-expect-error - $el is an internal property of the InputText component
|
|
const inputElement = inputRef.value.$el
|
|
inputElement.setSelectionRange?.(start, end)
|
|
})
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
const vFocus = {
|
|
mounted: (el: HTMLElement) => el.focus()
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.editable-text {
|
|
display: inline;
|
|
}
|
|
.editable-text input {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
</style>
|