관리자 검색창 한글 IME 조합 검색 및 모바일 가로 트랙 세로 스크롤 문제 수정
- IME 조합 중에도 검색되는 공통 컴포저블 useImeSearchInput 추가 - 글/멤버/미디어/설정/태그 필터/미디어 모달 검색 IME 조합 대응 - 홈 추천글·페이지 섹션 가로 트랙 touch-action pan-x 제거로 모바일 세로 스크롤 복구 - v1.10.29 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
84
composables/useImeSearchInput.js
Normal file
84
composables/useImeSearchInput.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
/**
|
||||
* 검색어를 NFC로 정규화한다.
|
||||
* @param {string} value - 원본 입력값
|
||||
* @returns {string} 정규화된 문자열
|
||||
*/
|
||||
const normalizeSearchValue = (value) => String(value ?? '').normalize('NFC')
|
||||
|
||||
/**
|
||||
* 한글 등 IME 조합 중에도 검색어가 즉시 반영되는 검색 입력 처리기를 만든다.
|
||||
*
|
||||
* Vue의 `v-model`은 IME 조합 중에는 값을 갱신하지 않아, 조합이 끝나야(스페이스·다음 글자)
|
||||
* 검색이 시작되는 문제가 있다. 이 컴포저블은 `compositionupdate`까지 반영해 조합 도중에도
|
||||
* 검색이 되게 하고, 선택적으로 디바운스를 적용한다.
|
||||
*
|
||||
* @param {import('vue').Ref<string>} targetRef - 검색/필터에 사용할 대상 ref(정규화된 값이 기록됨)
|
||||
* @param {Object} [options] - 옵션
|
||||
* @param {number} [options.debounce=0] - 디바운스 지연(ms). 0이면 즉시 반영
|
||||
* @returns {{ displayValue: import('vue').Ref<string>, onInput: (event: Event) => void, onCompositionEnd: (event: Event) => void }}
|
||||
*/
|
||||
export const useImeSearchInput = (targetRef, options = {}) => {
|
||||
const debounce = Number(options.debounce) || 0
|
||||
const displayValue = ref(String(targetRef.value ?? ''))
|
||||
|
||||
/** @type {ReturnType<typeof setTimeout> | null} */
|
||||
let debounceTimer = null
|
||||
|
||||
/**
|
||||
* 대상 ref에 정규화된 값을 반영한다(디바운스 적용).
|
||||
* @param {string} value - 현재 입력값
|
||||
* @returns {void}
|
||||
*/
|
||||
const commit = (value) => {
|
||||
if (debounce > 0) {
|
||||
clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
targetRef.value = normalizeSearchValue(value)
|
||||
}, debounce)
|
||||
return
|
||||
}
|
||||
targetRef.value = normalizeSearchValue(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 입력 이벤트에서 표시값과 검색어를 갱신한다(IME 조합 중 포함).
|
||||
* @param {Event} event - `input`/`compositionupdate` 이벤트
|
||||
* @returns {void}
|
||||
*/
|
||||
const onInput = (event) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLInputElement)) {
|
||||
return
|
||||
}
|
||||
displayValue.value = target.value
|
||||
commit(target.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* IME 조합 종료 시 최종 검색어를 즉시 반영한다.
|
||||
* @param {Event} event - `compositionend` 이벤트
|
||||
* @returns {void}
|
||||
*/
|
||||
const onCompositionEnd = (event) => {
|
||||
const target = event.target
|
||||
if (target instanceof HTMLInputElement) {
|
||||
displayValue.value = target.value
|
||||
}
|
||||
clearTimeout(debounceTimer)
|
||||
targetRef.value = normalizeSearchValue(displayValue.value)
|
||||
}
|
||||
|
||||
watch(targetRef, (value) => {
|
||||
if (normalizeSearchValue(displayValue.value) !== value) {
|
||||
displayValue.value = String(value ?? '')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
displayValue,
|
||||
onInput,
|
||||
onCompositionEnd
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user