diff --git a/src/food-market.public/src/components/SignupForm.tsx b/src/food-market.public/src/components/SignupForm.tsx index e186bb1..7356ef1 100644 --- a/src/food-market.public/src/components/SignupForm.tsx +++ b/src/food-market.public/src/components/SignupForm.tsx @@ -162,17 +162,31 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) { // onSubmit формы — срабатывает на Enter с клавиатуры. const submit = (ev: React.FormEvent) => { ev.preventDefault(); runSubmit() } - // Касание кнопки «Начать бесплатно»: отправляем на pointerdown, ДО того как - // смена фокуса (blur поля) успеет перерисовать форму и сдвинуть кнопку. - // preventDefault не даёт фокусу уйти с поля (значит blur-валидация не - // вставит ошибку и вёрстка не прыгнет) и подавляет последующий click, так что - // первое касание всегда отправляет форму — валидация всех полей + прокрутка и - // фокус к первой ошибке. Фикс потери первого касания на мобильном. - const onSubmitBtnPointerDown = (e: React.PointerEvent) => { + // Касание кнопки «Начать бесплатно». На pointerdown только preventDefault — + // фокус остаётся в поле, blur-ошибка не вставляется и вёрстка не прыгает + // (иначе кнопка уезжала и первое касание терялось). Отправляем на pointerup, + // только если палец НЕ сдвинулся больше порога и жест не стал прокруткой + // (pointercancel → tapRef сбрасывается). Так тап отправляет с первого раза, + // а свайп, начатый на кнопке, просто прокручивает страницу. + const TAP_SLOP = 10 + const tapRef = useRef<{ id: number; x: number; y: number; moved: boolean } | null>(null) + const onBtnPointerDown = (e: React.PointerEvent) => { if (e.button !== 0) return e.preventDefault() + tapRef.current = { id: e.pointerId, x: e.clientX, y: e.clientY, moved: false } + } + const onBtnPointerMove = (e: React.PointerEvent) => { + const t = tapRef.current + if (!t || e.pointerId !== t.id) return + if (Math.abs(e.clientX - t.x) > TAP_SLOP || Math.abs(e.clientY - t.y) > TAP_SLOP) t.moved = true + } + const onBtnPointerUp = (e: React.PointerEvent) => { + const t = tapRef.current + tapRef.current = null + if (!t || e.pointerId !== t.id || t.moved) return runSubmit() } + const onBtnPointerCancel = () => { tapRef.current = null } // text-base на мобильной ширине (16px) → iOS Safari не зумит страницу при // фокусе на поле; на sm и выше — привычные 14px. user-scalable=no НЕ ставим. @@ -314,7 +328,10 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) { {fieldErrors.agree &&

{fieldErrors.agree}

} -

Уже есть аккаунт? Войти

diff --git a/src/food-market.web/src/pages/LoginPage.tsx b/src/food-market.web/src/pages/LoginPage.tsx index f3cb71c..8dce620 100644 --- a/src/food-market.web/src/pages/LoginPage.tsx +++ b/src/food-market.web/src/pages/LoginPage.tsx @@ -36,14 +36,29 @@ export function LoginPage() { // onSubmit формы (Enter с клавиатуры). function handleSubmit(e: FormEvent) { e.preventDefault(); runSubmit() } - // Касание кнопки «Войти»: отправляем на pointerdown, ДО того как blur поля - // перерисует форму и сдвинёт кнопку (иначе на мобильном первое касание - // терялось). preventDefault удерживает фокус в поле и подавляет click. - function onSubmitBtnPointerDown(e: React.PointerEvent) { + // Касание кнопки «Войти». На pointerdown только preventDefault — фокус + // остаётся в поле, blur-ошибка не сдвигает вёрстку (иначе первое касание + // терялось). Отправляем на pointerup, только если палец не сдвинулся и жест + // не стал прокруткой (pointercancel сбрасывает). Свайп с кнопки — прокрутка. + const TAP_SLOP = 10 + const tapRef = useRef<{ id: number; x: number; y: number; moved: boolean } | null>(null) + function onBtnPointerDown(e: React.PointerEvent) { if (e.button !== 0) return e.preventDefault() + tapRef.current = { id: e.pointerId, x: e.clientX, y: e.clientY, moved: false } + } + function onBtnPointerMove(e: React.PointerEvent) { + const t = tapRef.current + if (!t || e.pointerId !== t.id) return + if (Math.abs(e.clientX - t.x) > TAP_SLOP || Math.abs(e.clientY - t.y) > TAP_SLOP) t.moved = true + } + function onBtnPointerUp(e: React.PointerEvent) { + const t = tapRef.current + tapRef.current = null + if (!t || e.pointerId !== t.id || t.moved) return runSubmit() } + function onBtnPointerCancel() { tapRef.current = null } async function runSubmit() { const em = validateEmail(email) @@ -154,7 +169,10 @@ export function LoginPage() {