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}