@input-kit/phone 0.5.0 → 0.6.0

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.d.cts CHANGED
@@ -83,6 +83,7 @@ interface PhoneInputLabels {
83
83
  searchCountriesAriaLabel: string;
84
84
  countryOptionsAriaLabel: string;
85
85
  countryButtonAriaLabel: string;
86
+ countryResultsAnnouncement: string;
86
87
  }
87
88
  interface PhoneInputOptions {
88
89
  defaultCountry?: string;
@@ -132,11 +133,32 @@ interface UsePhoneInputReturn extends PhoneInputState, PhoneInputActions {
132
133
  inputProps: InputHTMLAttributes<HTMLInputElement> & {
133
134
  ref: RefObject<HTMLInputElement | null>;
134
135
  };
136
+ highlightedIndex: number;
137
+ activeCountry: Country | undefined;
138
+ countryResultsText: string;
139
+ setHighlightedIndex: (index: number) => void;
140
+ moveHighlight: (nextIndex: number) => void;
135
141
  countryButtonProps: {
136
142
  onClick: () => void;
137
143
  'aria-expanded': boolean;
138
144
  'aria-haspopup': 'listbox';
139
145
  'aria-label': string;
146
+ 'aria-controls': string;
147
+ };
148
+ countrySearchProps: {
149
+ role: 'combobox';
150
+ value: string;
151
+ onChange: (event: {
152
+ target: {
153
+ value: string;
154
+ };
155
+ }) => void;
156
+ 'aria-expanded': boolean;
157
+ 'aria-controls': string;
158
+ 'aria-autocomplete': 'list';
159
+ 'aria-activedescendant': string | undefined;
160
+ 'aria-label': string;
161
+ placeholder: string;
140
162
  };
141
163
  countrySelectorProps: {
142
164
  onClick: () => void;
@@ -145,10 +167,12 @@ interface UsePhoneInputReturn extends PhoneInputState, PhoneInputActions {
145
167
  'aria-label': string;
146
168
  };
147
169
  dropdownProps: {
170
+ id: string;
148
171
  role: 'listbox';
149
172
  'aria-label': string;
150
- 'aria-activedescendant'?: string;
151
173
  };
174
+ listboxId: string;
175
+ activeOptionId: string | undefined;
152
176
  filteredCountries: Country[];
153
177
  countries: Country[];
154
178
  selectCountry: (country: Country) => void;
package/dist/index.d.ts CHANGED
@@ -83,6 +83,7 @@ interface PhoneInputLabels {
83
83
  searchCountriesAriaLabel: string;
84
84
  countryOptionsAriaLabel: string;
85
85
  countryButtonAriaLabel: string;
86
+ countryResultsAnnouncement: string;
86
87
  }
87
88
  interface PhoneInputOptions {
88
89
  defaultCountry?: string;
@@ -132,11 +133,32 @@ interface UsePhoneInputReturn extends PhoneInputState, PhoneInputActions {
132
133
  inputProps: InputHTMLAttributes<HTMLInputElement> & {
133
134
  ref: RefObject<HTMLInputElement | null>;
134
135
  };
136
+ highlightedIndex: number;
137
+ activeCountry: Country | undefined;
138
+ countryResultsText: string;
139
+ setHighlightedIndex: (index: number) => void;
140
+ moveHighlight: (nextIndex: number) => void;
135
141
  countryButtonProps: {
136
142
  onClick: () => void;
137
143
  'aria-expanded': boolean;
138
144
  'aria-haspopup': 'listbox';
139
145
  'aria-label': string;
146
+ 'aria-controls': string;
147
+ };
148
+ countrySearchProps: {
149
+ role: 'combobox';
150
+ value: string;
151
+ onChange: (event: {
152
+ target: {
153
+ value: string;
154
+ };
155
+ }) => void;
156
+ 'aria-expanded': boolean;
157
+ 'aria-controls': string;
158
+ 'aria-autocomplete': 'list';
159
+ 'aria-activedescendant': string | undefined;
160
+ 'aria-label': string;
161
+ placeholder: string;
140
162
  };
141
163
  countrySelectorProps: {
142
164
  onClick: () => void;
@@ -145,10 +167,12 @@ interface UsePhoneInputReturn extends PhoneInputState, PhoneInputActions {
145
167
  'aria-label': string;
146
168
  };
147
169
  dropdownProps: {
170
+ id: string;
148
171
  role: 'listbox';
149
172
  'aria-label': string;
150
- 'aria-activedescendant'?: string;
151
173
  };
174
+ listboxId: string;
175
+ activeOptionId: string | undefined;
152
176
  filteredCountries: Country[];
153
177
  countries: Country[];
154
178
  selectCountry: (country: Country) => void;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { forwardRef, useRef, useState, useId, useCallback, useEffect, useImperativeHandle, useMemo, useLayoutEffect } from 'react';
1
+ import { forwardRef, useRef, useCallback, useEffect, useImperativeHandle, useMemo, useState, useId, useLayoutEffect } from 'react';
2
2
  import { getCountries, getCountryCallingCode, getExampleNumber, parsePhoneNumberFromString, AsYouType, validatePhoneNumberLength, isPossiblePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';
3
3
  import examples from 'libphonenumber-js/mobile/examples';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
@@ -2146,7 +2146,8 @@ var DEFAULT_PHONE_INPUT_LABELS = {
2146
2146
  noCountriesFound: "No countries found",
2147
2147
  searchCountriesAriaLabel: "Search countries",
2148
2148
  countryOptionsAriaLabel: "Country options",
2149
- countryButtonAriaLabel: "Open country selector"
2149
+ countryButtonAriaLabel: "Open country selector",
2150
+ countryResultsAnnouncement: "{count} countries"
2150
2151
  };
2151
2152
  var LABELS_BY_LANGUAGE = {
2152
2153
  en: DEFAULT_PHONE_INPUT_LABELS,
@@ -2156,7 +2157,8 @@ var LABELS_BY_LANGUAGE = {
2156
2157
  noCountriesFound: "Aucun pays trouv\xE9",
2157
2158
  searchCountriesAriaLabel: "Rechercher des pays",
2158
2159
  countryOptionsAriaLabel: "Options de pays",
2159
- countryButtonAriaLabel: "Ouvrir le s\xE9lecteur de pays"
2160
+ countryButtonAriaLabel: "Ouvrir le s\xE9lecteur de pays",
2161
+ countryResultsAnnouncement: "{count} pays"
2160
2162
  },
2161
2163
  de: {
2162
2164
  selectCountry: "Land ausw\xE4hlen",
@@ -2164,7 +2166,8 @@ var LABELS_BY_LANGUAGE = {
2164
2166
  noCountriesFound: "Keine L\xE4nder gefunden",
2165
2167
  searchCountriesAriaLabel: "L\xE4nder suchen",
2166
2168
  countryOptionsAriaLabel: "L\xE4nderoptionen",
2167
- countryButtonAriaLabel: "L\xE4nderauswahl \xF6ffnen"
2169
+ countryButtonAriaLabel: "L\xE4nderauswahl \xF6ffnen",
2170
+ countryResultsAnnouncement: "{count} L\xE4nder"
2168
2171
  },
2169
2172
  es: {
2170
2173
  selectCountry: "Seleccionar pa\xEDs",
@@ -2172,7 +2175,8 @@ var LABELS_BY_LANGUAGE = {
2172
2175
  noCountriesFound: "No se encontraron pa\xEDses",
2173
2176
  searchCountriesAriaLabel: "Buscar pa\xEDses",
2174
2177
  countryOptionsAriaLabel: "Opciones de pa\xEDs",
2175
- countryButtonAriaLabel: "Abrir selector de pa\xEDs"
2178
+ countryButtonAriaLabel: "Abrir selector de pa\xEDs",
2179
+ countryResultsAnnouncement: "{count} pa\xEDses"
2176
2180
  },
2177
2181
  pt: {
2178
2182
  selectCountry: "Selecionar pa\xEDs",
@@ -2180,7 +2184,8 @@ var LABELS_BY_LANGUAGE = {
2180
2184
  noCountriesFound: "Nenhum pa\xEDs encontrado",
2181
2185
  searchCountriesAriaLabel: "Pesquisar pa\xEDses",
2182
2186
  countryOptionsAriaLabel: "Op\xE7\xF5es de pa\xEDs",
2183
- countryButtonAriaLabel: "Abrir seletor de pa\xEDs"
2187
+ countryButtonAriaLabel: "Abrir seletor de pa\xEDs",
2188
+ countryResultsAnnouncement: "{count} pa\xEDses"
2184
2189
  },
2185
2190
  it: {
2186
2191
  selectCountry: "Seleziona paese",
@@ -2188,7 +2193,8 @@ var LABELS_BY_LANGUAGE = {
2188
2193
  noCountriesFound: "Nessun paese trovato",
2189
2194
  searchCountriesAriaLabel: "Cerca paesi",
2190
2195
  countryOptionsAriaLabel: "Opzioni paese",
2191
- countryButtonAriaLabel: "Apri selettore paese"
2196
+ countryButtonAriaLabel: "Apri selettore paese",
2197
+ countryResultsAnnouncement: "{count} paesi"
2192
2198
  },
2193
2199
  ja: {
2194
2200
  selectCountry: "\u56FD\u3092\u9078\u629E",
@@ -2196,7 +2202,8 @@ var LABELS_BY_LANGUAGE = {
2196
2202
  noCountriesFound: "\u56FD\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
2197
2203
  searchCountriesAriaLabel: "\u56FD\u3092\u691C\u7D22",
2198
2204
  countryOptionsAriaLabel: "\u56FD\u306E\u5019\u88DC",
2199
- countryButtonAriaLabel: "\u56FD\u306E\u30BB\u30EC\u30AF\u30BF\u30FC\u3092\u958B\u304F"
2205
+ countryButtonAriaLabel: "\u56FD\u306E\u30BB\u30EC\u30AF\u30BF\u30FC\u3092\u958B\u304F",
2206
+ countryResultsAnnouncement: "{count} \u4EF6\u306E\u56FD"
2200
2207
  },
2201
2208
  ko: {
2202
2209
  selectCountry: "\uAD6D\uAC00 \uC120\uD0DD",
@@ -2204,7 +2211,8 @@ var LABELS_BY_LANGUAGE = {
2204
2211
  noCountriesFound: "\uAD6D\uAC00\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
2205
2212
  searchCountriesAriaLabel: "\uAD6D\uAC00 \uAC80\uC0C9",
2206
2213
  countryOptionsAriaLabel: "\uAD6D\uAC00 \uC635\uC158",
2207
- countryButtonAriaLabel: "\uAD6D\uAC00 \uC120\uD0DD\uAE30 \uC5F4\uAE30"
2214
+ countryButtonAriaLabel: "\uAD6D\uAC00 \uC120\uD0DD\uAE30 \uC5F4\uAE30",
2215
+ countryResultsAnnouncement: "\uAD6D\uAC00 {count}\uAC1C"
2208
2216
  },
2209
2217
  zh: {
2210
2218
  selectCountry: "\u9009\u62E9\u56FD\u5BB6",
@@ -2212,7 +2220,8 @@ var LABELS_BY_LANGUAGE = {
2212
2220
  noCountriesFound: "\u672A\u627E\u5230\u56FD\u5BB6",
2213
2221
  searchCountriesAriaLabel: "\u641C\u7D22\u56FD\u5BB6",
2214
2222
  countryOptionsAriaLabel: "\u56FD\u5BB6\u9009\u9879",
2215
- countryButtonAriaLabel: "\u6253\u5F00\u56FD\u5BB6\u9009\u62E9\u5668"
2223
+ countryButtonAriaLabel: "\u6253\u5F00\u56FD\u5BB6\u9009\u62E9\u5668",
2224
+ countryResultsAnnouncement: "{count} \u4E2A\u56FD\u5BB6"
2216
2225
  },
2217
2226
  ar: {
2218
2227
  selectCountry: "\u0627\u062E\u062A\u0631 \u0627\u0644\u062F\u0648\u0644\u0629",
@@ -2220,7 +2229,8 @@ var LABELS_BY_LANGUAGE = {
2220
2229
  noCountriesFound: "\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 \u062F\u0648\u0644",
2221
2230
  searchCountriesAriaLabel: "\u0627\u0628\u062D\u062B \u0639\u0646 \u0627\u0644\u062F\u0648\u0644",
2222
2231
  countryOptionsAriaLabel: "\u062E\u064A\u0627\u0631\u0627\u062A \u0627\u0644\u062F\u0648\u0644",
2223
- countryButtonAriaLabel: "\u0627\u0641\u062A\u062D \u0645\u062D\u062F\u062F \u0627\u0644\u062F\u0648\u0644\u0629"
2232
+ countryButtonAriaLabel: "\u0627\u0641\u062A\u062D \u0645\u062D\u062F\u062F \u0627\u0644\u062F\u0648\u0644\u0629",
2233
+ countryResultsAnnouncement: "{count} \u062F\u0648\u0644\u0629"
2224
2234
  },
2225
2235
  hi: {
2226
2236
  selectCountry: "\u0926\u0947\u0936 \u091A\u0941\u0928\u0947\u0902",
@@ -2228,7 +2238,8 @@ var LABELS_BY_LANGUAGE = {
2228
2238
  noCountriesFound: "\u0915\u094B\u0908 \u0926\u0947\u0936 \u0928\u0939\u0940\u0902 \u092E\u093F\u0932\u093E",
2229
2239
  searchCountriesAriaLabel: "\u0926\u0947\u0936 \u0916\u094B\u091C\u0947\u0902",
2230
2240
  countryOptionsAriaLabel: "\u0926\u0947\u0936 \u0935\u093F\u0915\u0932\u094D\u092A",
2231
- countryButtonAriaLabel: "\u0926\u0947\u0936 \u091A\u092F\u0928 \u0916\u094B\u0932\u0947\u0902"
2241
+ countryButtonAriaLabel: "\u0926\u0947\u0936 \u091A\u092F\u0928 \u0916\u094B\u0932\u0947\u0902",
2242
+ countryResultsAnnouncement: "{count} \u0926\u0947\u0936"
2232
2243
  }
2233
2244
  };
2234
2245
  function getPrimaryLanguage(locale) {
@@ -2570,6 +2581,8 @@ function usePhoneInput(options = {}) {
2570
2581
  const [country, setCountry] = useState(initialCountry);
2571
2582
  const [isOpen, setIsOpen] = useState(false);
2572
2583
  const [searchQuery, setSearchQuery] = useState("");
2584
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
2585
+ const listboxId = useId();
2573
2586
  const phone = isControlled ? controlledValue : internalPhone;
2574
2587
  const getOutputCountry = useCallback(
2575
2588
  (nextCountry) => localizeCountry(nextCountry, locale),
@@ -2640,6 +2653,26 @@ function usePhoneInput(options = {}) {
2640
2653
  () => filterCountries(localizedCountries, searchQuery, preferredCountries),
2641
2654
  [localizedCountries, searchQuery, preferredCountries]
2642
2655
  );
2656
+ useEffect(() => {
2657
+ setHighlightedIndex(0);
2658
+ }, [searchQuery]);
2659
+ useEffect(() => {
2660
+ setHighlightedIndex((current) => {
2661
+ if (filteredCountries.length === 0) {
2662
+ return 0;
2663
+ }
2664
+ return current >= filteredCountries.length ? filteredCountries.length - 1 : current;
2665
+ });
2666
+ }, [filteredCountries.length]);
2667
+ const wasOpenRef = useRef(false);
2668
+ useEffect(() => {
2669
+ if (isOpen && !wasOpenRef.current) {
2670
+ const selectedIndex = filteredCountries.findIndex((candidate) => candidate.code === country?.code);
2671
+ setHighlightedIndex(selectedIndex >= 0 ? selectedIndex : 0);
2672
+ }
2673
+ wasOpenRef.current = isOpen;
2674
+ }, [isOpen, filteredCountries, country?.code]);
2675
+ const activeCountry = filteredCountries[highlightedIndex];
2643
2676
  useIsomorphicLayoutEffect(() => {
2644
2677
  const nextDigitIndex = pendingCaretDigitIndexRef.current;
2645
2678
  const input = inputRef.current;
@@ -2752,11 +2785,22 @@ function usePhoneInput(options = {}) {
2752
2785
  (code) => `phone-input-country-${code}`,
2753
2786
  []
2754
2787
  );
2788
+ const activeOptionId = activeCountry ? getCountryOptionId(activeCountry.code) : void 0;
2789
+ const countryResultsText = filteredCountries.length === 0 ? resolvedLabels.noCountriesFound : resolvedLabels.countryResultsAnnouncement.replace("{count}", String(filteredCountries.length));
2790
+ const moveHighlight = useCallback(
2791
+ (nextIndex) => {
2792
+ const lastIndex = filteredCountries.length - 1;
2793
+ setHighlightedIndex(lastIndex < 0 ? 0 : Math.min(Math.max(nextIndex, 0), lastIndex));
2794
+ },
2795
+ [filteredCountries.length]
2796
+ );
2755
2797
  const getCountryOptionProps = useCallback(
2756
2798
  (optionCountry, index) => ({
2757
2799
  id: getCountryOptionId(optionCountry.code),
2758
2800
  role: "option",
2759
- "aria-selected": optionCountry.code === country?.code,
2801
+ // Selection follows the active option in a single-select listbox, so this
2802
+ // tracks the highlight rather than the committed country.
2803
+ "aria-selected": index === highlightedIndex,
2760
2804
  onClick: () => selectCountry(optionCountry),
2761
2805
  onKeyDown: (event) => {
2762
2806
  if (event.key === "Enter" || event.key === " ") {
@@ -2770,7 +2814,7 @@ function usePhoneInput(options = {}) {
2770
2814
  },
2771
2815
  tabIndex: index === 0 ? 0 : -1
2772
2816
  }),
2773
- [closeDropdown, country?.code, getCountryOptionId, selectCountry]
2817
+ [closeDropdown, highlightedIndex, getCountryOptionId, selectCountry]
2774
2818
  );
2775
2819
  return {
2776
2820
  // State
@@ -2785,6 +2829,9 @@ function usePhoneInput(options = {}) {
2785
2829
  searchQuery,
2786
2830
  labels: resolvedLabels,
2787
2831
  formattedValue: formattedPhone,
2832
+ highlightedIndex,
2833
+ activeCountry,
2834
+ countryResultsText,
2788
2835
  // Actions
2789
2836
  setPhone,
2790
2837
  setValue: setPhone,
@@ -2796,6 +2843,8 @@ function usePhoneInput(options = {}) {
2796
2843
  closeDropdown,
2797
2844
  close: closeDropdown,
2798
2845
  setSearchQuery,
2846
+ setHighlightedIndex,
2847
+ moveHighlight,
2799
2848
  clear,
2800
2849
  // Props
2801
2850
  inputProps: {
@@ -2814,7 +2863,21 @@ function usePhoneInput(options = {}) {
2814
2863
  onClick: toggleDropdown,
2815
2864
  "aria-expanded": isOpen,
2816
2865
  "aria-haspopup": "listbox",
2817
- "aria-label": countryButtonAriaLabel
2866
+ "aria-label": countryButtonAriaLabel,
2867
+ "aria-controls": listboxId
2868
+ },
2869
+ // The search field is the combobox: it keeps focus while the arrow keys
2870
+ // move `aria-activedescendant` through the listbox.
2871
+ countrySearchProps: {
2872
+ role: "combobox",
2873
+ value: searchQuery,
2874
+ onChange: (event) => setSearchQuery(event.target.value),
2875
+ "aria-expanded": isOpen,
2876
+ "aria-controls": listboxId,
2877
+ "aria-autocomplete": "list",
2878
+ "aria-activedescendant": activeOptionId,
2879
+ "aria-label": resolvedLabels.searchCountriesAriaLabel,
2880
+ placeholder: resolvedLabels.countrySearchPlaceholder
2818
2881
  },
2819
2882
  countrySelectorProps: {
2820
2883
  onClick: toggleDropdown,
@@ -2823,9 +2886,12 @@ function usePhoneInput(options = {}) {
2823
2886
  "aria-label": countryButtonAriaLabel
2824
2887
  },
2825
2888
  dropdownProps: {
2889
+ id: listboxId,
2826
2890
  role: "listbox",
2827
2891
  "aria-label": resolvedLabels.countryOptionsAriaLabel
2828
2892
  },
2893
+ listboxId,
2894
+ activeOptionId,
2829
2895
  getCountryOptionId,
2830
2896
  filteredCountries,
2831
2897
  countries: filteredCountries,
@@ -2860,24 +2926,25 @@ var PhoneInput = forwardRef(
2860
2926
  const inputRef = useRef(null);
2861
2927
  const optionRefs = useRef([]);
2862
2928
  const countryButtonRef = useRef(null);
2863
- const [highlightedIndex, setHighlightedIndex] = useState(0);
2864
- const listboxId = useId();
2865
2929
  const {
2866
2930
  inputProps,
2867
2931
  countryButtonProps,
2932
+ countrySearchProps,
2868
2933
  dropdownProps,
2869
2934
  country,
2870
2935
  filteredCountries,
2871
2936
  isOpen,
2872
2937
  isValid,
2873
- searchQuery,
2874
- setSearchQuery,
2875
2938
  setCountry,
2876
2939
  selectCountry,
2877
2940
  clear,
2878
2941
  openDropdown,
2879
2942
  closeDropdown,
2880
2943
  getCountryOptionId,
2944
+ highlightedIndex,
2945
+ setHighlightedIndex,
2946
+ moveHighlight,
2947
+ countryResultsText,
2881
2948
  labels
2882
2949
  } = usePhoneInput({
2883
2950
  value,
@@ -2907,7 +2974,6 @@ var PhoneInput = forwardRef(
2907
2974
  }, []);
2908
2975
  const handleCountryClick = useCallback((c) => {
2909
2976
  selectCountry(c);
2910
- setHighlightedIndex(0);
2911
2977
  returnFocusToButton();
2912
2978
  }, [selectCountry, returnFocusToButton]);
2913
2979
  const handleCountryKeyDown = useCallback((e, c, index) => {
@@ -2916,7 +2982,6 @@ var PhoneInput = forwardRef(
2916
2982
  case " ":
2917
2983
  e.preventDefault();
2918
2984
  selectCountry(c);
2919
- setHighlightedIndex(0);
2920
2985
  returnFocusToButton();
2921
2986
  break;
2922
2987
  case "ArrowDown":
@@ -2953,7 +3018,7 @@ var PhoneInput = forwardRef(
2953
3018
  returnFocusToButton();
2954
3019
  break;
2955
3020
  }
2956
- }, [selectCountry, filteredCountries.length, closeDropdown, returnFocusToButton]);
3021
+ }, [selectCountry, filteredCountries.length, closeDropdown, returnFocusToButton, setHighlightedIndex]);
2957
3022
  useEffect(() => {
2958
3023
  if (!isOpen) {
2959
3024
  return;
@@ -2978,16 +3043,14 @@ var PhoneInput = forwardRef(
2978
3043
  };
2979
3044
  }, [isOpen, closeDropdown]);
2980
3045
  useEffect(() => {
2981
- if (filteredCountries.length === 0) {
2982
- if (highlightedIndex !== 0) {
2983
- setHighlightedIndex(0);
2984
- }
3046
+ if (!isOpen || !searchable) {
2985
3047
  return;
2986
3048
  }
2987
- if (highlightedIndex >= filteredCountries.length) {
2988
- setHighlightedIndex(filteredCountries.length - 1);
3049
+ const activeOption = optionRefs.current[highlightedIndex];
3050
+ if (typeof activeOption?.scrollIntoView === "function") {
3051
+ activeOption.scrollIntoView({ block: "nearest" });
2989
3052
  }
2990
- }, [filteredCountries.length, highlightedIndex]);
3053
+ }, [isOpen, searchable, highlightedIndex]);
2991
3054
  useImperativeHandle(ref, () => ({
2992
3055
  focus,
2993
3056
  blur,
@@ -2998,16 +3061,39 @@ var PhoneInput = forwardRef(
2998
3061
  close: closeDropdown
2999
3062
  }));
3000
3063
  const handleSearchKeyDown = useCallback((e) => {
3001
- if (e.key === "ArrowDown") {
3002
- e.preventDefault();
3003
- setHighlightedIndex(0);
3004
- optionRefs.current[0]?.focus();
3005
- } else if (e.key === "Escape") {
3006
- e.preventDefault();
3007
- closeDropdown();
3008
- returnFocusToButton();
3064
+ switch (e.key) {
3065
+ case "ArrowDown":
3066
+ e.preventDefault();
3067
+ moveHighlight(highlightedIndex + 1);
3068
+ break;
3069
+ case "ArrowUp":
3070
+ e.preventDefault();
3071
+ moveHighlight(highlightedIndex - 1);
3072
+ break;
3073
+ case "Home":
3074
+ e.preventDefault();
3075
+ moveHighlight(0);
3076
+ break;
3077
+ case "End":
3078
+ e.preventDefault();
3079
+ moveHighlight(filteredCountries.length - 1);
3080
+ break;
3081
+ case "Enter": {
3082
+ const active = filteredCountries[highlightedIndex];
3083
+ if (active) {
3084
+ e.preventDefault();
3085
+ selectCountry(active);
3086
+ returnFocusToButton();
3087
+ }
3088
+ break;
3089
+ }
3090
+ case "Escape":
3091
+ e.preventDefault();
3092
+ closeDropdown();
3093
+ returnFocusToButton();
3094
+ break;
3009
3095
  }
3010
- }, [closeDropdown, returnFocusToButton]);
3096
+ }, [closeDropdown, returnFocusToButton, moveHighlight, highlightedIndex, filteredCountries, selectCountry]);
3011
3097
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: containerClassName, "data-valid": isValid, children: [
3012
3098
  /* @__PURE__ */ jsxs(
3013
3099
  "button",
@@ -3018,7 +3104,6 @@ var PhoneInput = forwardRef(
3018
3104
  className: selectorClassName,
3019
3105
  disabled,
3020
3106
  tabIndex: disabled ? -1 : 0,
3021
- "aria-controls": listboxId,
3022
3107
  children: [
3023
3108
  country ? /* @__PURE__ */ jsxs(Fragment, { children: [
3024
3109
  /* @__PURE__ */ jsx("span", { className: "phone-input-flag", children: country.flag }),
@@ -3047,22 +3132,26 @@ var PhoneInput = forwardRef(
3047
3132
  searchable && /* @__PURE__ */ jsx("div", { className: "phone-input-search", children: /* @__PURE__ */ jsx(
3048
3133
  "input",
3049
3134
  {
3135
+ ...countrySearchProps,
3050
3136
  type: "text",
3051
- value: searchQuery,
3052
- onChange: (e) => setSearchQuery(e.target.value),
3053
3137
  onKeyDown: handleSearchKeyDown,
3054
- placeholder: labels.countrySearchPlaceholder,
3055
- autoFocus: true,
3056
- "aria-controls": listboxId,
3057
- "aria-label": labels.searchCountriesAriaLabel
3138
+ autoFocus: true
3058
3139
  }
3059
3140
  ) }),
3141
+ /* @__PURE__ */ jsx(
3142
+ "div",
3143
+ {
3144
+ role: "status",
3145
+ "aria-live": "polite",
3146
+ style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" },
3147
+ children: countryResultsText
3148
+ }
3149
+ ),
3060
3150
  /* @__PURE__ */ jsxs(
3061
3151
  "ul",
3062
3152
  {
3063
3153
  ...dropdownProps,
3064
3154
  className: "phone-input-country-list",
3065
- id: listboxId,
3066
3155
  children: [
3067
3156
  filteredCountries.map((c, index) => /* @__PURE__ */ jsxs(
3068
3157
  "li",
@@ -3072,12 +3161,12 @@ var PhoneInput = forwardRef(
3072
3161
  optionRefs.current[index] = element;
3073
3162
  },
3074
3163
  role: "option",
3075
- "aria-selected": c.code === country?.code,
3164
+ "aria-selected": index === highlightedIndex,
3076
3165
  className: `phone-input-country-option ${c.code === country?.code ? "selected" : ""} ${index === highlightedIndex ? "highlighted" : ""}`,
3077
3166
  onClick: () => handleCountryClick(c),
3078
3167
  onKeyDown: (e) => handleCountryKeyDown(e, c, index),
3079
3168
  onMouseEnter: () => setHighlightedIndex(index),
3080
- tabIndex: index === highlightedIndex ? 0 : -1,
3169
+ tabIndex: searchable ? -1 : index === highlightedIndex ? 0 : -1,
3081
3170
  children: [
3082
3171
  /* @__PURE__ */ jsx("span", { className: "phone-input-flag", children: c.flag }),
3083
3172
  /* @__PURE__ */ jsx("span", { className: "phone-input-country-name", children: getCountryDisplayName(c) }),