diff --git a/src/components/Componentes/question-birthplace.tsx b/src/components/Componentes/question-birthplace.tsx index 7a38f7a..23afe36 100644 --- a/src/components/Componentes/question-birthplace.tsx +++ b/src/components/Componentes/question-birthplace.tsx @@ -161,6 +161,8 @@ export function QuestionBirthplace({ const cityInputRef = useRef(null); const listRef = useRef(null); const isMountedRef = useRef(true); + const isFocusedRef = useRef(false); + const debounceTimerRef = useRef(null); const [mode, setMode] = useState<"auto" | "manual">("auto"); const [isDetecting, setIsDetecting] = useState(false); @@ -171,6 +173,9 @@ export function QuestionBirthplace({ isMountedRef.current = true; return () => { isMountedRef.current = false; + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } }; }, []); @@ -301,6 +306,9 @@ export function QuestionBirthplace({ // Synchronize state ONLY if rawValue changes externally (e.g. draft fetch or reset) useEffect(() => { + if (isFocusedRef.current) { + return; + } if (rawValue === lastInternalAnswerRef.current) { return; } @@ -357,8 +365,26 @@ export function QuestionBirthplace({ } const newCity = e.target.value; setCityInput(newCity); - updateAnswers(selectedCountry, newCity); setDetectedLocation([selectedCountry, newCity].filter(Boolean).join(", ")); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + updateAnswers(selectedCountry, newCity); + }, 250); + }; + + const handleCityFocus = () => { + isFocusedRef.current = true; + }; + + const handleCityBlur = () => { + isFocusedRef.current = false; + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + updateAnswers(selectedCountry, cityInput); }; const isRtl = locale === "fa" || locale === "ar" || locale === "ur"; @@ -613,6 +639,8 @@ export function QuestionBirthplace({ disabled={disabled} value={cityInput} onChange={handleCityChange} + onFocus={handleCityFocus} + onBlur={handleCityBlur} placeholder={cityPlaceholder} className="h-[54px] w-full rounded-[16px] border border-[#D0D5DD] bg-white px-4.5 text-[15px] font-medium text-[#181818] placeholder:text-[#98A2B3] hover:border-[#98A2B3] focus:border-[#6F6F6F] focus:ring-1 focus:ring-[#6F6F6F] outline-none transition-all" /> diff --git a/src/components/Componentes/ui-config.test.tsx b/src/components/Componentes/ui-config.test.tsx index b804c83..4523ef2 100644 --- a/src/components/Componentes/ui-config.test.tsx +++ b/src/components/Componentes/ui-config.test.tsx @@ -346,6 +346,52 @@ describe("UI Config based behavior", () => { expect(screen.getByText("فرانسه")).toBeDefined(); expect((cityInput as HTMLInputElement).value).toBe("برلین مرکزی"); }); + + it("should not duplicate or alter city text when focused and typing continuously", async () => { + const qBirthplace = { + id: "birthplace_typing_test", + title: "محل تولد", + type: "birthplace", + order: 1, + required: true, + isVisible: true, + description: "", + tooltip: "", + extras: { placeHolder: "شهر، منطقه یا محله" }, + options: [], + ui_config: {}, + } as any; + + render( + + + + + , + ); + + const cityInput = screen.getByPlaceholderText("شهر، منطقه یا محله"); + fireEvent.focus(cityInput); + + // Simulate character-by-character typing: ت -> ته -> تهر -> تهرا -> تهران + fireEvent.change(cityInput, { target: { value: "ت" } }); + expect((cityInput as HTMLInputElement).value).toBe("ت"); + + fireEvent.change(cityInput, { target: { value: "ته" } }); + expect((cityInput as HTMLInputElement).value).toBe("ته"); + + fireEvent.change(cityInput, { target: { value: "تهر" } }); + expect((cityInput as HTMLInputElement).value).toBe("تهر"); + + fireEvent.change(cityInput, { target: { value: "تهرا" } }); + expect((cityInput as HTMLInputElement).value).toBe("تهرا"); + + fireEvent.change(cityInput, { target: { value: "تهران" } }); + expect((cityInput as HTMLInputElement).value).toBe("تهران"); + + fireEvent.blur(cityInput); + expect((cityInput as HTMLInputElement).value).toBe("تهران"); + }); });