feat(fe): Add form field navigation - Enter key moves to next field
- Create useFormFieldNavigation composable for Tab-like Enter behavior - KsTextField: Enter -> next field - KsTextArea: Ctrl+Enter for newline, Enter -> next field - KsSelect: Enter -> next field after selection - Implements standard form navigation pattern across input components
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
export function useFormFieldNavigation() {
|
||||
const inputRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function findFormElement(): HTMLFormElement | null {
|
||||
if (!inputRef.value) return null
|
||||
return inputRef.value.closest('form')
|
||||
}
|
||||
|
||||
function getFormInputElements(): HTMLElement[] {
|
||||
const form = findFormElement()
|
||||
if (!form) return []
|
||||
|
||||
const selectors = [
|
||||
'input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])',
|
||||
'textarea',
|
||||
'[role="button"][tabindex="0"]',
|
||||
'select'
|
||||
]
|
||||
|
||||
return Array.from(form.querySelectorAll(selectors.join(',')))
|
||||
.filter((el: Element): el is HTMLElement => {
|
||||
if (el instanceof HTMLInputElement) {
|
||||
return !el.disabled && !el.readonly && el.type !== 'hidden'
|
||||
}
|
||||
if (el instanceof HTMLTextAreaElement) {
|
||||
return !el.disabled && !el.readonly
|
||||
}
|
||||
if (el instanceof HTMLSelectElement) {
|
||||
return !el.disabled && !el.readonly
|
||||
}
|
||||
return !el.hasAttribute('disabled')
|
||||
})
|
||||
}
|
||||
|
||||
function findNextField(): HTMLElement | null {
|
||||
const elements = getFormInputElements()
|
||||
if (!inputRef.value || elements.length === 0) return null
|
||||
|
||||
const currentIndex = elements.indexOf(inputRef.value)
|
||||
if (currentIndex === -1) return null
|
||||
|
||||
const nextIndex = currentIndex + 1
|
||||
return nextIndex < elements.length ? elements[nextIndex] : elements[0]
|
||||
}
|
||||
|
||||
function moveToNextField(): void {
|
||||
const nextField = findNextField()
|
||||
if (nextField) {
|
||||
nextField.focus()
|
||||
// 如果是 input/textarea,select all
|
||||
if (nextField instanceof HTMLInputElement || nextField instanceof HTMLTextAreaElement) {
|
||||
nextField.select?.()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent, isTextArea: boolean = false): boolean {
|
||||
if (!isTextArea) {
|
||||
// Input/Select: Enter → 다음 필드
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
moveToNextField()
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// TextArea: Ctrl+Enter → 줄바꿈, Enter → 다음 필드
|
||||
if (event.key === 'Enter') {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// Ctrl+Enter: 줄바꿈 (기본 동작)
|
||||
return false
|
||||
} else {
|
||||
// Enter: 다음 필드
|
||||
event.preventDefault()
|
||||
moveToNextField()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
inputRef,
|
||||
handleKeyDown,
|
||||
moveToNextField,
|
||||
findNextField,
|
||||
getFormInputElements
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user