@input-kit/phone 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -388,9 +388,11 @@ function addDialCode(phone, country) {
388
388
  if (!normalized) return "";
389
389
  if (normalized === "+") return "";
390
390
  if (normalized.startsWith("+")) return normalized;
391
- const digits = cleanPhone(normalized);
392
- const matchingDialCode = country.dialCodes.find((dialCode) => digits.startsWith(dialCode.replace("+", "")));
393
- return matchingDialCode ? `+${digits}` : `${country.dialCode}${digits}`;
391
+ const parsed = parsePhoneNumberFromString(normalized, getCountryCode(country));
392
+ if (parsed) {
393
+ return parsed.number;
394
+ }
395
+ return `${country.dialCode}${cleanPhone(normalized)}`;
394
396
  }
395
397
  function removeDialCode(phone, country) {
396
398
  if (!country) return phone;
@@ -683,13 +685,21 @@ function usePhoneInput(options = {}) {
683
685
  () => validatePhoneNumber(phone, country, required, validator),
684
686
  [phone, country, required, validator]
685
687
  );
688
+ const onValidationChangeRef = useRef(onValidationChange);
689
+ useIsomorphicLayoutEffect(() => {
690
+ onValidationChangeRef.current = onValidationChange;
691
+ });
686
692
  useEffect(() => {
687
- onValidationChange?.(validation);
688
- }, [validation, onValidationChange]);
693
+ onValidationChangeRef.current?.(validation);
694
+ }, [validation]);
689
695
  const localizedCountry = useMemo(
690
696
  () => localizeCountry(country, locale),
691
697
  [country, locale]
692
698
  );
699
+ const countryButtonAriaLabel = useMemo(
700
+ () => localizedCountry ? `${resolvedLabels.countryButtonAriaLabel}, ${localizedCountry.name} ${localizedCountry.dialCode}` : resolvedLabels.countryButtonAriaLabel,
701
+ [localizedCountry, resolvedLabels.countryButtonAriaLabel]
702
+ );
693
703
  const filteredCountries = useMemo(
694
704
  () => filterCountries(localizedCountries, searchQuery, preferredCountries),
695
705
  [localizedCountries, searchQuery, preferredCountries]
@@ -739,16 +749,20 @@ function usePhoneInput(options = {}) {
739
749
  (selectedCountry) => {
740
750
  const rawCountry = getCountryByCode(selectedCountry.code) ?? selectedCountry;
741
751
  manualCountrySelectionRef.current = true;
752
+ const rewritten = phone.trim().startsWith("+") ? addDialCode(removeDialCode(phone, country), rawCountry) : phone;
753
+ if (rewritten !== phone && !isControlled) {
754
+ setInternalPhone(rewritten);
755
+ }
742
756
  setCountry(rawCountry);
743
757
  setIsOpen(false);
744
758
  setSearchQuery("");
745
759
  onCountryChange?.(getOutputCountry(rawCountry));
746
760
  onChange?.(
747
- includeDialCode ? addDialCode(phone, rawCountry) : phone,
761
+ includeDialCode ? addDialCode(rewritten, rawCountry) : rewritten,
748
762
  getOutputCountry(rawCountry)
749
763
  );
750
764
  },
751
- [phone, onChange, onCountryChange, includeDialCode, getOutputCountry]
765
+ [phone, country, isControlled, onChange, onCountryChange, includeDialCode, getOutputCountry]
752
766
  );
753
767
  const toggleDropdown = useCallback(() => {
754
768
  setIsOpen((prev) => !prev);
@@ -774,18 +788,23 @@ function usePhoneInput(options = {}) {
774
788
  const rawValue = event.target.value;
775
789
  const caretPosition = event.target.selectionStart ?? rawValue.length;
776
790
  const unformatted = rawValue.trim().startsWith("+") ? `+${rawValue.replace(/\D/g, "")}` : unformatPhone(rawValue);
777
- if (cleanPhone(unformatted).length > cleanPhone(phone).length && isPhoneTooLong(unformatted, country)) {
778
- event.target.value = formattedPhone;
779
- const restoredCaret = Math.min(Math.max(caretPosition - 1, 0), formattedPhone.length);
791
+ const isInternationalEntry = unformatted.startsWith("+") || unformatted.startsWith("00");
792
+ if (!isInternationalEntry && cleanPhone(unformatted).length > cleanPhone(phone).length && isPhoneTooLong(unformatted, country)) {
793
+ const truncated = limitInputLength(unformatted, country);
794
+ const truncatedDisplay = formatPhone(truncated, country);
795
+ event.target.value = truncatedDisplay;
796
+ const restoredCaret = Math.min(Math.max(caretPosition - 1, 0), truncatedDisplay.length);
780
797
  if (typeof event.target.setSelectionRange === "function") {
781
798
  event.target.setSelectionRange(restoredCaret, restoredCaret);
782
799
  }
800
+ pendingCaretDigitIndexRef.current = countDigitsBeforePosition(truncatedDisplay, restoredCaret);
801
+ setPhone(truncated);
783
802
  return;
784
803
  }
785
804
  pendingCaretDigitIndexRef.current = countDigitsBeforePosition(rawValue, caretPosition);
786
805
  setPhone(unformatted);
787
806
  },
788
- [setPhone, phone, country, formattedPhone]
807
+ [setPhone, phone, country]
789
808
  );
790
809
  const handleFocus = useCallback(() => {
791
810
  onFocus?.();
@@ -859,13 +878,13 @@ function usePhoneInput(options = {}) {
859
878
  onClick: toggleDropdown,
860
879
  "aria-expanded": isOpen,
861
880
  "aria-haspopup": "listbox",
862
- "aria-label": resolvedLabels.countryButtonAriaLabel
881
+ "aria-label": countryButtonAriaLabel
863
882
  },
864
883
  countrySelectorProps: {
865
884
  onClick: toggleDropdown,
866
885
  "aria-expanded": isOpen,
867
886
  "aria-haspopup": "listbox",
868
- "aria-label": resolvedLabels.countryButtonAriaLabel
887
+ "aria-label": countryButtonAriaLabel
869
888
  },
870
889
  dropdownProps: {
871
890
  role: "listbox",
@@ -904,6 +923,7 @@ var PhoneInput = forwardRef(
904
923
  const containerRef = useRef(null);
905
924
  const inputRef = useRef(null);
906
925
  const optionRefs = useRef([]);
926
+ const countryButtonRef = useRef(null);
907
927
  const [highlightedIndex, setHighlightedIndex] = useState(0);
908
928
  const listboxId = useId();
909
929
  const {
@@ -946,10 +966,14 @@ var PhoneInput = forwardRef(
946
966
  const blur = useCallback(() => {
947
967
  inputRef.current?.blur();
948
968
  }, []);
969
+ const returnFocusToButton = useCallback(() => {
970
+ countryButtonRef.current?.focus();
971
+ }, []);
949
972
  const handleCountryClick = useCallback((c) => {
950
973
  selectCountry(c);
951
974
  setHighlightedIndex(0);
952
- }, [selectCountry]);
975
+ returnFocusToButton();
976
+ }, [selectCountry, returnFocusToButton]);
953
977
  const handleCountryKeyDown = useCallback((e, c, index) => {
954
978
  switch (e.key) {
955
979
  case "Enter":
@@ -957,6 +981,7 @@ var PhoneInput = forwardRef(
957
981
  e.preventDefault();
958
982
  selectCountry(c);
959
983
  setHighlightedIndex(0);
984
+ returnFocusToButton();
960
985
  break;
961
986
  case "ArrowDown":
962
987
  e.preventDefault();
@@ -989,9 +1014,10 @@ var PhoneInput = forwardRef(
989
1014
  case "Escape":
990
1015
  e.preventDefault();
991
1016
  closeDropdown();
1017
+ returnFocusToButton();
992
1018
  break;
993
1019
  }
994
- }, [selectCountry, filteredCountries.length, closeDropdown]);
1020
+ }, [selectCountry, filteredCountries.length, closeDropdown, returnFocusToButton]);
995
1021
  useEffect(() => {
996
1022
  if (!isOpen) {
997
1023
  return;
@@ -1043,13 +1069,15 @@ var PhoneInput = forwardRef(
1043
1069
  } else if (e.key === "Escape") {
1044
1070
  e.preventDefault();
1045
1071
  closeDropdown();
1072
+ returnFocusToButton();
1046
1073
  }
1047
- }, [closeDropdown]);
1074
+ }, [closeDropdown, returnFocusToButton]);
1048
1075
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: containerClassName, "data-valid": isValid, children: [
1049
1076
  /* @__PURE__ */ jsxs(
1050
1077
  "button",
1051
1078
  {
1052
1079
  ...countryButtonProps,
1080
+ ref: countryButtonRef,
1053
1081
  type: "button",
1054
1082
  className: selectorClassName,
1055
1083
  disabled,