fix: 할 일 이름 수정 시 편집 상태에 갇히는 문제 수정

편집 입력이 v-for 안에 있어 문자열 ref 가 요소가 아닌 배열이 되었고,
focus() 가 조용히 무시돼 앞선 포커스 수정이 실제로는 적용되지 않았다.
포커스가 없으니 blur 도 발화하지 않아 다른 곳을 클릭해도 편집이 닫히지 않았다.

- 문자열 ref 를 함수 ref 로 교체해 입력 요소를 직접 받는다
- 바깥 클릭으로도 편집을 확정한다(blur 에만 의존하지 않는 안전장치)
  ESC 는 입력의 취소 처리와 겹치지 않도록 훅에서 제외
- nextTick 이후 편집 대상이 바뀌었으면 포커스를 주지 않는다

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 23:21:59 +09:00
parent e1577fadc4
commit 2d5ff51708
+30 -2
View File
@@ -1,7 +1,13 @@
<script setup lang="ts">
// 라벨 카드 — 헤더(색점 + 이름 + 진행 + 편집/삭제) + 할 일 행 목록 + 인라인 추가 입력.
// 전역 .list 카드 규격을 그대로 쓰고, 행은 카드 안에서 구분선으로 나눈다.
import { nextTick, ref, watch } from 'vue'
import {
computed,
nextTick,
ref,
watch,
type ComponentPublicInstance,
} from 'vue'
import { RouterLink } from 'vue-router'
import { useClickOutside } from '@/composables/useClickOutside'
import type { ApiTodoLabel, ApiTodoItem } from '@/types/todo'
@@ -54,15 +60,37 @@ async function submitDraft(e: KeyboardEvent): Promise<void> {
const editingId = ref<string | null>(null)
const editDraft = ref('')
const editInput = ref<HTMLInputElement | null>(null)
// v-for 안에서는 문자열 ref 가 '요소 배열'이 되어 focus() 를 호출할 수 없다.
// 편집 입력은 한 번에 하나만 존재하므로 함수 ref 로 요소를 직접 받는다.
function setEditInput(el: Element | ComponentPublicInstance | null): void {
editInput.value = (el as HTMLInputElement | null) ?? null
}
async function startEdit(item: ApiTodoItem): Promise<void> {
editingId.value = item.id
editDraft.value = item.title
// 입력이 그려진 뒤 포커스를 줘야 한다. 포커스가 없으면 타이핑도 blur 확정도 되지 않는다.
await nextTick()
if (editingId.value !== item.id) return
editInput.value?.focus()
editInput.value?.select()
}
// 바깥 클릭으로도 편집을 확정한다.
// blur 만 믿으면 포커스가 걸리지 않은 상황에서 편집 상태에 갇힌다(라벨 메뉴와 동일한 훅 사용).
const isEditing = computed(() => editingId.value !== null)
useClickOutside(
isEditing,
() => {
const item = props.label.items.find((i) => i.id === editingId.value)
if (item) commitEdit(item)
else cancelEdit()
},
[editInput],
// ESC 는 입력의 @keydown.esc 가 '취소'로 처리한다.
// 여기서도 잡으면 취소해야 할 상황에 저장돼 버린다.
{ esc: false },
)
// 편집 중인 행이 목록에서 사라지면(삭제·검색 필터) 편집 상태를 정리한다
watch(
() => props.label.items,
@@ -202,7 +230,7 @@ function cancelEdit(): void {
</button>
<input
v-if="editingId === item.id"
ref="editInput"
:ref="setEditInput"
v-model="editDraft"
class="tl-edit"
maxlength="200"