From 043d5c04177881eebb87152de5419d5eec6407ec Mon Sep 17 00:00:00 2001 From: nns Date: Mon, 12 Oct 2026 22:29:51 +0500 Subject: [PATCH] =?UTF-8?q?fix(=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F):=20=D1=82=D0=B5=D0=BB=D0=B5=D1=84?= =?UTF-8?q?=D0=BE=D0=BD=20=E2=80=94=20=D0=BD=D0=B5=D1=81=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=B0=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BF=D1=80=D0=B5=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81=20=C2=AB+7=20=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стирание больше не превращает «7» кода страны в цифру номера. Причина: при Backspace в префикс raw становился «+7», и парсер считал это номером «7» («+7 7», далее набор давал мусор «+7 010 000 00 07»). Теперь: - извлечение тела номера идёт через stripPrefix (проход по совпадающим символам префикса) — повреждённый префикс даёт пустое тело, а не номер; - onKeyDown глушит Backspace на границе префикса и Delete перед префиксом; выделение+стирание (Ctrl+A/Ctrl+X) очищает поле до «+7 ». Полностью стёртое поле = «+7 » и пустой номер. Обе копии (сайт + кабинет). Проверено настоящим вводом в Chromium (Playwright): Ctrl+A+Backspace, Backspace до упора, Delete в начале, выделение+набор, Ctrl+A+Ctrl+X — после каждого набор «7010000000» ⇒ «+7 701 000 00 00»; регрессия набора и вставки тоже зелёная. Co-Authored-By: Claude Opus 4.8 --- .../src/components/PhoneInput.tsx | 34 ++++++++++++++++--- .../src/components/PhoneInput.tsx | 34 ++++++++++++++++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/food-market.public/src/components/PhoneInput.tsx b/src/food-market.public/src/components/PhoneInput.tsx index 6031b3f..d984d8e 100644 --- a/src/food-market.public/src/components/PhoneInput.tsx +++ b/src/food-market.public/src/components/PhoneInput.tsx @@ -25,6 +25,16 @@ function valueToNational(value: string): string { return toNational(value) } +// Снимаем наш визуальный префикс «+7 », проходя по совпадающим символам. Если +// пользователь стёр ЧАСТЬ префикса (raw="+7" или "+"), совпавший кусок всё равно +// отбрасывается, а тело оказывается пустым — цифра «7» кода страны НИКОГДА не +// утекает в номер при стирании. Остаток (тело) нормализуется отдельно. +function stripPrefix(raw: string): string { + let i = 0 + while (i < PREFIX.length && i < raw.length && raw[i] === PREFIX[i]) i++ + return raw.slice(i) +} + function nationalToDisplay(d: string): string { if (d.length === 0) return PREFIX let s = PREFIX + d.slice(0, 3) @@ -88,14 +98,27 @@ export function PhoneInput({ value, onChange, className, disabled, ...rest }: Ph const raw = e.target.value const cursorPos = e.target.selectionStart ?? raw.length pendingRef.current = natDigitsBeforeCursor(raw, cursorPos) - // При посимвольном вводе значение начинается с «+7 » — берём часть после - // префикса, чтобы его «7» не попала в номер. При полной вставке (replace) - // значения без префикса нормализуем целиком (снимем код страны/«8»). - const src = raw.startsWith(PREFIX) ? raw.slice(PREFIX_LEN) : raw - const nat = toNational(src) + // Тело = всё после нашего префикса «+7 » (с защитой от частично стёртого + // префикса, см. stripPrefix). Нормализуем тело: вставка полного номера + // (с 8/кодом страны) приводится к 10 цифрам; при наборе первая «7» — это + // начало оператора 7XX и не снимается. + const nat = toNational(stripPrefix(raw)) onChange(nat.length > 0 ? '+7' + nat : '') } + // Префикс «+7 » нестираемый: Backspace на границе префикса (в номере стирать + // нечего) и Delete внутри/перед префиксом — глушим, чтобы «7» кода страны не + // превратилась в цифру номера. Выделение (Ctrl+A) + стирание не трогаем — + // браузер очистит всё, а value вернётся к «+7 ». + const handleKeyDown = (e: React.KeyboardEvent) => { + const el = e.currentTarget + const start = el.selectionStart ?? 0 + const end = el.selectionEnd ?? 0 + if (start !== end) return // есть выделение — пусть удаляет, нормализуется в handleChange + if (e.key === 'Backspace' && start <= PREFIX_LEN) { e.preventDefault(); return } + if (e.key === 'Delete' && start < PREFIX_LEN) { e.preventDefault() } + } + const guardCursor = (el: HTMLInputElement) => { const start = el.selectionStart ?? 0 const end = el.selectionEnd ?? 0 @@ -118,6 +141,7 @@ export function PhoneInput({ value, onChange, className, disabled, ...rest }: Ph value={display} onBeforeInput={handleBeforeInput} onChange={handleChange} + onKeyDown={handleKeyDown} onSelect={handleSelect} onFocus={handleFocus} disabled={disabled} diff --git a/src/food-market.web/src/components/PhoneInput.tsx b/src/food-market.web/src/components/PhoneInput.tsx index 9933041..1c06389 100644 --- a/src/food-market.web/src/components/PhoneInput.tsx +++ b/src/food-market.web/src/components/PhoneInput.tsx @@ -29,6 +29,16 @@ function valueToNational(value: string): string { return toNational(value) } +// Снимаем наш визуальный префикс «+7 », проходя по совпадающим символам. Если +// пользователь стёр ЧАСТЬ префикса (raw="+7" или "+"), совпавший кусок всё равно +// отбрасывается, тело пустеет — цифра «7» кода страны НИКОГДА не утекает в номер +// при стирании. Остаток (тело) нормализуется отдельно. +function stripPrefix(raw: string): string { + let i = 0 + while (i < PREFIX.length && i < raw.length && raw[i] === PREFIX[i]) i++ + return raw.slice(i) +} + // Display из нац. номера. Формат: «+7 7XX XXX XX XX» — после «+7 » идут // 3+3+2+2 = 10 цифр через пробелы. function nationalToDisplay(d: string): string { @@ -118,14 +128,27 @@ export function PhoneInput({ value, onChange, className, disabled, ...rest }: Ph const raw = e.target.value const cursorPos = e.target.selectionStart ?? raw.length pendingDigitCountRef.current = natDigitsBeforeCursor(raw, cursorPos) - // При посимвольном вводе значение начинается с «+7 » — берём часть после - // префикса, чтобы его «7» не попала в номер. При полной вставке значения - // без префикса нормализуем целиком (снимем код страны/«8»). - const src = raw.startsWith(PREFIX) ? raw.slice(PREFIX_LEN) : raw - const nat = toNational(src) + // Тело = всё после нашего префикса «+7 » (с защитой от частично стёртого + // префикса, см. stripPrefix). Нормализуем тело: вставка полного номера + // (с 8/кодом страны) приводится к 10 цифрам; при наборе первая «7» — это + // начало оператора 7XX и не снимается. + const nat = toNational(stripPrefix(raw)) onChange(nat.length > 0 ? '+7' + nat : '') } + // Префикс «+7 » нестираемый: Backspace на границе префикса (в номере стирать + // нечего) и Delete внутри/перед префиксом — глушим, чтобы «7» кода страны не + // превратилась в цифру номера. Выделение + стирание не трогаем — браузер + // очистит всё, value вернётся к «+7 ». + const handleKeyDown = (e: React.KeyboardEvent) => { + const el = e.currentTarget + const start = el.selectionStart ?? 0 + const end = el.selectionEnd ?? 0 + if (start !== end) return + if (e.key === 'Backspace' && start <= PREFIX_LEN) { e.preventDefault(); return } + if (e.key === 'Delete' && start < PREFIX_LEN) { e.preventDefault() } + } + // Не пускаем курсор/selection в префикс. onSelect ловит любые попытки // (клик, стрелки, Cmd+A) и мягко двигает на digit-зону. const guardCursor = (el: HTMLInputElement) => { @@ -152,6 +175,7 @@ export function PhoneInput({ value, onChange, className, disabled, ...rest }: Ph value={display} onBeforeInput={handleBeforeInput} onChange={handleChange} + onKeyDown={handleKeyDown} onSelect={handleSelect} onFocus={handleFocus} disabled={disabled}