From 491e3791c0442b9dc6b0ec00a5abad239ac57948 Mon Sep 17 00:00:00 2001 From: hextion <100ishundred@gmail.com> Date: Wed, 15 Jan 2025 14:08:44 +0300 Subject: [PATCH] fix(international-phone-input): correct preserve country code --- .changeset/mighty-eggs-sing.md | 6 ++ .../Component.tsx | 18 +++++- .../international-phone-input/src/types.ts | 13 +---- .../src/utils/index.ts | 22 +++++-- .../international-phone-input/tsconfig.json | 2 +- packages/shared/src/maskUtils.ts | 57 +++++++++++-------- packages/types/index.ts | 11 ++++ 7 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 .changeset/mighty-eggs-sing.md diff --git a/.changeset/mighty-eggs-sing.md b/.changeset/mighty-eggs-sing.md new file mode 100644 index 0000000000..89c7725aa2 --- /dev/null +++ b/.changeset/mighty-eggs-sing.md @@ -0,0 +1,6 @@ +--- +'@alfalab/core-components-shared': minor +'@alfalab/core-components-international-phone-input': patch +--- + +- Исправлено автозаполнение номера в Safari 18 diff --git a/packages/international-phone-input/src/components/base-international-phone-input/Component.tsx b/packages/international-phone-input/src/components/base-international-phone-input/Component.tsx index dd99dbe343..d8932ee1e8 100644 --- a/packages/international-phone-input/src/components/base-international-phone-input/Component.tsx +++ b/packages/international-phone-input/src/components/base-international-phone-input/Component.tsx @@ -14,6 +14,7 @@ import { useMaskito } from '@maskito/react'; import type { InputAutocompleteProps } from '@alfalab/core-components-input-autocomplete'; import { AnyObject, BaseOption } from '@alfalab/core-components-select/shared'; import type { BaseSelectChangePayload } from '@alfalab/core-components-select/typings'; +import { isNullable } from '@alfalab/core-components-shared'; import type { BaseInternationalPhoneInputProps, Country } from '../../types'; import { @@ -58,6 +59,7 @@ export const BaseInternationalPhoneInput = forwardRef< }, ref, ) => { + const lastCountryRef = useRef(null); const countriesData = useMemo(() => initCountries(countries), [countries]); const inputRef = useRef(null); const inputWrapperRef = useRef(null); @@ -80,7 +82,13 @@ export const BaseInternationalPhoneInput = forwardRef< const preserveCountryCode = clearableCountryCodeFromProps === 'preserve'; const clearableCountryCode = preserveCountryCode || clearableCountryCodeFromProps; const maskOptions = useMemo( - () => createMaskOptions(country, clearableCountryCode, preserveCountryCode), + () => + createMaskOptions( + country, + clearableCountryCode, + preserveCountryCode, + lastCountryRef, + ), [country, clearableCountryCode, preserveCountryCode], ); @@ -202,6 +210,14 @@ export const BaseInternationalPhoneInput = forwardRef< ...restProps.inputProps, } as const; + useEffect(() => { + if (!preserveCountryCode || isNullable(country)) { + return; + } + + lastCountryRef.current = country; + }, [country, preserveCountryCode]); + return Array.isArray(options) ? ( , ): MaskitoOptions { const countryCode = country?.countryCode; const prefix = countryCode ? getInitialValueFromCountry(countryCode) : ''; const prefixLen = !clearableCountryCode && prefix ? prefix.length : 0; const mask = createPhoneMaskExpression(country, clearableCountryCode); + const preprocessors = [ + maskUtils.insertionPhonePreprocessor(mask, countryCode, clearableCountryCode), + ]; + + if (preserveCountryCode) { + const createMask = (lastCountry: Country) => + createPhoneMaskExpression(lastCountry, clearableCountryCode); + + preprocessors.push(maskUtils.preserveCountryCodePreprocessor(lastCountryRef, createMask)); + } + return { mask, - preprocessors: [ - maskUtils.insertionPhonePreprocessor(mask, countryCode, clearableCountryCode), - maskUtils.preserveCountryCodePreprocessor(countryCode, preserveCountryCode), - ], + preprocessors, postprocessors: [maskUtils.prefixPostprocessor(prefixLen > 0 ? prefix : '')], plugins: [ maskUtils.caretGuard((value, [from, to]) => [ diff --git a/packages/international-phone-input/tsconfig.json b/packages/international-phone-input/tsconfig.json index 131687460d..d89a145dbe 100644 --- a/packages/international-phone-input/tsconfig.json +++ b/packages/international-phone-input/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "../../typings"], + "include": ["src", "../../typings", "../types"], "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", diff --git a/packages/shared/src/maskUtils.ts b/packages/shared/src/maskUtils.ts index b8ba1a0754..1f3ec1a2ee 100644 --- a/packages/shared/src/maskUtils.ts +++ b/packages/shared/src/maskUtils.ts @@ -1,8 +1,11 @@ /* eslint-disable no-plusplus, no-param-reassign */ +import { RefObject } from 'react'; import type { MaskitoPlugin } from '@maskito/core'; import { MaskitoPostprocessor, MaskitoPreprocessor } from '@maskito/core'; -import { fnUtils } from './fnUtils'; +import { Country } from '../../types'; + +import { fnUtils, isNonNullable } from './fnUtils'; /** * Запрещает каретке становиться за указанные границы @@ -88,7 +91,12 @@ function prefixPostprocessor(prefix: string): MaskitoPostprocessor { : (state) => state; } -const countDigits = (value: string): number => value.replace(/\D/g, '').length; +const clearMask = (value: string) => value.replace(/\D/g, ''); + +const countDigits = (value: string): number => clearMask(value).length; + +const getCompletePhoneLength = (mask: Array) => + mask.filter((item) => `${item}` === `${/\d/}` || /[0-9]/.test(`${item}`)).length; /** * Препроцессор необходим для правильной вставки/автокомплита телефонного номера @@ -98,9 +106,7 @@ function insertionPhonePreprocessor( countryCode: string | undefined, clearableCountryCode: boolean | undefined, ): MaskitoPreprocessor { - const completePhoneLength = mask.filter( - (item) => `${item}` === `${/\d/}` || /[0-9]/.test(`${item}`), - ).length; + const completePhoneLength = getCompletePhoneLength(mask); const trimCountryPrefix = (value: string): string => { if (countryCode === '7') { @@ -142,25 +148,30 @@ function insertionPhonePreprocessor( * Препроцессор необходим для сохранения кода страны при автозаполнении */ function preserveCountryCodePreprocessor( - countryCode: string | undefined, - preserveCountryCode: boolean, + countryRef: RefObject, + createMask: (country: Country) => Array, ): MaskitoPreprocessor { - return ({ elementState, data }) => { - if (!preserveCountryCode || fnUtils.isNil(countryCode)) { - return { elementState, data }; - } - - const { value, selection } = elementState; - - if (value.length > 0 && !value.startsWith('+')) { - const nextValue = countryCode.concat(value); - - return { - elementState: { - value: nextValue, - selection, - }, - }; + return ({ elementState }) => { + const country = countryRef.current; + + if (isNonNullable(country)) { + const { value, selection } = elementState; + const { countryCode } = country; + const numbersValue = clearMask(value); + + if (!numbersValue.startsWith(countryCode)) { + const nextValue = countryCode.concat(numbersValue); + const mask = createMask(country); + + if (countDigits(nextValue) === getCompletePhoneLength(mask)) { + return { + elementState: { + selection, + value: nextValue, + }, + }; + } + } } return { elementState }; diff --git a/packages/types/index.ts b/packages/types/index.ts index ea10af6a84..e67565f506 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -186,3 +186,14 @@ export type TypographyType = | 'accent-component' | 'action-component' | 'paragraph-component'; + +export type Country = { + name: string; + regions?: string[]; + iso2: string; + countryCode: string; + dialCode: string; + format?: string; + priority: number; + mainCode?: boolean; +};