@jacksonavila/phone-lib 2.0.4 → 2.0.6

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/README.md CHANGED
@@ -61,7 +61,7 @@ You can use PhoneLib directly from CDN without npm / Puedes usar PhoneLib direct
61
61
  <!DOCTYPE html>
62
62
  <html>
63
63
  <head>
64
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.css">
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.css">
65
65
  </head>
66
66
  <body>
67
67
  <div id="phone-container"></div>
@@ -69,7 +69,7 @@ You can use PhoneLib directly from CDN without npm / Puedes usar PhoneLib direct
69
69
  <script type="importmap">
70
70
  {
71
71
  "imports": {
72
- "@jacksonavila/phone-lib": "https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.js",
72
+ "@jacksonavila/phone-lib": "https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.js",
73
73
  "libphonenumber-js": "https://esm.sh/libphonenumber-js@1.11.0"
74
74
  }
75
75
  }
@@ -89,8 +89,8 @@ You can use PhoneLib directly from CDN without npm / Puedes usar PhoneLib direct
89
89
  ```
90
90
 
91
91
  **CDN URLs / URLs de CDN:**
92
- - **jsDelivr:** `https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/`
93
- - **unpkg:** `https://unpkg.com/@jacksonavila/phone-lib@2.0.4/`
92
+ - **jsDelivr:** `https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/`
93
+ - **unpkg:** `https://unpkg.com/@jacksonavila/phone-lib@2.0.6/`
94
94
 
95
95
  ### Method 2: Script Tag (All Browsers) / Método 2: Script Tag (Todos los Navegadores)
96
96
 
@@ -98,12 +98,12 @@ You can use PhoneLib directly from CDN without npm / Puedes usar PhoneLib direct
98
98
  <!DOCTYPE html>
99
99
  <html>
100
100
  <head>
101
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.css">
101
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.css">
102
102
  </head>
103
103
  <body>
104
104
  <div id="phone-container"></div>
105
105
 
106
- <script src="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.cdn.js"></script>
106
+ <script src="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.cdn.js"></script>
107
107
 
108
108
  <script>
109
109
  let phoneLib = null;
@@ -558,6 +558,9 @@ const phoneLib = new PhoneLib('#container', {
558
558
  dialCodeWidth: '100px', // Dial code width (separated) / Ancho código (separado)
559
559
  phoneWidth: '350px', // Phone field width (separated) / Ancho campo teléfono (separado)
560
560
 
561
+ // Arrow icon / Icono de flecha
562
+ arrowIcon: null, // Custom arrow HTML (SVG, emoji, image) / HTML personalizado para flecha
563
+
561
564
  // Styles / Estilos
562
565
  customClasses: {
563
566
  wrapper: 'my-class',
@@ -782,6 +785,25 @@ const phoneLib = new PhoneLib('#container', {
782
785
  });
783
786
  ```
784
787
 
788
+ ### Custom Arrow Icon / Icono de Flecha Personalizado
789
+
790
+ ```javascript
791
+ const phoneLib = new PhoneLib('#container', {
792
+ // Opción 1: SVG personalizado
793
+ arrowIcon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
794
+ <path d="M3 4.5 L6 7.5 L9 4.5" stroke="#333" stroke-width="2" fill="none"/>
795
+ </svg>`,
796
+
797
+ // Opción 2: Emoji
798
+ arrowIcon: '▼',
799
+
800
+ // Opción 3: Imagen
801
+ arrowIcon: '<img src="arrow.png" alt="▼" style="width: 12px; height: 12px;">'
802
+ });
803
+ ```
804
+
805
+ **Nota:** Si no se especifica `arrowIcon`, se usa un chevron SVG por defecto.
806
+
785
807
  ### Customizable Elements / Elementos Personalizables
786
808
 
787
809
  - `wrapper` - Main container / Contenedor principal
@@ -789,6 +811,7 @@ const phoneLib = new PhoneLib('#container', {
789
811
  - `input` - Phone input field / Campo de entrada de teléfono
790
812
  - `dialCodeInput` - Dial code field (separated layout only) / Campo de código (solo layout separado)
791
813
  - `row` - Grid row (separated layout only) / Fila del grid (solo layout separado)
814
+ - `arrowIcon` - Dropdown arrow icon / Icono de flecha del dropdown
792
815
 
793
816
  ---
794
817
 
@@ -870,6 +893,7 @@ const phoneLib = new PhoneLib('#container', {
870
893
  | `countryLabel` | string | `'Country'` / `'País'` | Label for selector / Label para selector |
871
894
  | `dialCodeLabel` | string | `'Code'` / `'Código'` | Label for code / Label para código |
872
895
  | `phoneLabel` | string | `'Phone number'` / `'Número de teléfono'` | Label for number / Label para número |
896
+ | `arrowIcon` | string | `null` | Custom arrow HTML (SVG, emoji, image) / HTML personalizado para flecha |
873
897
  | `customClasses` | object | `{}` | Custom CSS classes / Clases CSS personalizadas |
874
898
  | `customStyles` | object | `{}` | Inline styles / Estilos inline personalizados |
875
899
  | `width` | string | `null` | Wrapper width (e.g., `'500px'`, `'100%'`) / Ancho del wrapper |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacksonavila/phone-lib",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Librería JavaScript para input de teléfono con selector de país y banderas - Compatible con Vanilla JS y React",
5
5
  "main": "phone-lib.js",
6
6
  "module": "phone-lib.js",
package/phone-lib.cdn.js CHANGED
@@ -4,8 +4,8 @@
4
4
  * Carga libphonenumber-js dinámicamente y expone PhoneLib globalmente
5
5
  *
6
6
  * Uso:
7
- * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.css">
8
- * <script src="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.4/phone-lib.cdn.js"></script>
7
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.css">
8
+ * <script src="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.6/phone-lib.cdn.js"></script>
9
9
  * <script>
10
10
  * document.addEventListener('phoneLibReady', () => {
11
11
  * const phoneLib = new PhoneLib('#container', {...});
@@ -17,7 +17,7 @@
17
17
  'use strict';
18
18
 
19
19
  // Versión del paquete (actualizar cuando se publique nueva versión)
20
- const PACKAGE_VERSION = '2.0.4';
20
+ const PACKAGE_VERSION = '2.0.6';
21
21
  const PACKAGE_NAME = '@jacksonavila/phone-lib';
22
22
 
23
23
  // URLs de CDN
package/phone-lib.css CHANGED
@@ -30,6 +30,7 @@
30
30
  font-size: 16px;
31
31
  min-width: 120px;
32
32
  height: 100%;
33
+ overflow: visible;
33
34
  }
34
35
 
35
36
  .phone-lib-dropdown-button:hover {
@@ -66,10 +67,27 @@
66
67
  }
67
68
 
68
69
  .phone-lib-arrow {
69
- font-size: 10px;
70
- color: #666;
70
+ width: 14px;
71
+ height: 14px;
71
72
  margin-left: auto;
72
73
  transition: transform 0.2s ease;
74
+ display: inline-flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ flex-shrink: 0;
78
+ color: #666;
79
+ }
80
+
81
+ .phone-lib-arrow svg {
82
+ width: 100%;
83
+ height: 100%;
84
+ display: block;
85
+ }
86
+
87
+ .phone-lib-arrow svg path,
88
+ .phone-lib-arrow svg line {
89
+ stroke: currentColor;
90
+ fill: none;
73
91
  }
74
92
 
75
93
  .phone-lib-dropdown-button.active .phone-lib-arrow {
@@ -105,6 +123,8 @@
105
123
  cursor: pointer;
106
124
  transition: background-color 0.15s ease;
107
125
  border-bottom: 1px solid #f0f0f0;
126
+ min-width: 0;
127
+ overflow: visible;
108
128
  }
109
129
 
110
130
  .phone-lib-country-item:last-child {
@@ -140,6 +160,9 @@
140
160
  .phone-lib-country-name {
141
161
  flex: 1;
142
162
  color: #333;
163
+ white-space: nowrap;
164
+ overflow: visible;
165
+ text-overflow: clip;
143
166
  }
144
167
 
145
168
  .phone-lib-country-dial-code {
@@ -271,6 +294,8 @@
271
294
  font-size: 16px;
272
295
  width: 100%;
273
296
  text-align: left;
297
+ min-width: 0;
298
+ overflow: visible;
274
299
  }
275
300
 
276
301
  .phone-lib-dropdown-button-separated:hover {
@@ -287,6 +312,10 @@
287
312
  flex: 1;
288
313
  color: #333;
289
314
  font-weight: 500;
315
+ white-space: nowrap;
316
+ overflow: visible;
317
+ text-overflow: clip;
318
+ min-width: 0;
290
319
  }
291
320
 
292
321
  .phone-lib-dial-code-input {
package/phone-lib.js CHANGED
@@ -27,6 +27,8 @@ class PhoneLib {
27
27
  showDialCode: options.showDialCode !== undefined ? options.showDialCode : true, // Mostrar código de marcación
28
28
  customClasses: options.customClasses || {}, // Clases CSS personalizadas
29
29
  customStyles: options.customStyles || {}, // Estilos inline personalizados
30
+ // Icono de flecha personalizable
31
+ arrowIcon: options.arrowIcon || null, // HTML personalizado para la flecha (ej: SVG, imagen, etc.)
30
32
  // Control de anchos
31
33
  width: options.width || null, // Ancho del wrapper (ej: '500px', '100%', '50rem')
32
34
  maxWidth: options.maxWidth || null, // Ancho máximo del wrapper
@@ -400,6 +402,27 @@ class PhoneLib {
400
402
  .join(' ');
401
403
  }
402
404
 
405
+ /**
406
+ * Obtiene el HTML del icono de flecha
407
+ */
408
+ getArrowIcon() {
409
+ // Si hay un icono personalizado, usarlo (validar que sea string)
410
+ if (this.options.arrowIcon && typeof this.options.arrowIcon === 'string') {
411
+ // Sanitización básica: remover scripts potencialmente peligrosos
412
+ // Nota: En producción, considerar usar una librería de sanitización
413
+ const sanitized = this.options.arrowIcon.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
414
+ return sanitized;
415
+ }
416
+
417
+ // Chevron SVG por defecto (simple, apuntando hacia abajo - solo líneas, sin relleno)
418
+ // Forma de chevron: dos líneas que forman una V apuntando hacia abajo
419
+ // Usa dos líneas separadas para asegurar que se vea como chevron, no triángulo
420
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
421
+ <line x1="3" y1="5" x2="6" y2="8"/>
422
+ <line x1="6" y1="8" x2="9" y2="5"/>
423
+ </svg>`;
424
+ }
425
+
403
426
  renderIntegrated() {
404
427
  const selectedCountryData = this.countries.find(c => c.iso2 === this.selectedCountry);
405
428
  const defaultFlag = this.getFlagImage('UN');
@@ -442,7 +465,7 @@ class PhoneLib {
442
465
  <button type="button" class="${dropdownButtonClass}" ${dropdownButtonStyle ? `style="${dropdownButtonStyle}"` : ''} aria-expanded="false" ${this.isDisabled ? 'disabled' : ''} aria-label="${this.options.ariaLabels.dropdownButton}">
443
466
  <span class="phone-lib-flag">${selectedCountryData?.flag || defaultFlag}</span>
444
467
  ${this.options.showDialCode ? `<span class="phone-lib-dial-code">+${selectedCountryData?.dialCode || ''}</span>` : ''}
445
- <span class="phone-lib-arrow">▼</span>
468
+ <span class="phone-lib-arrow">${this.getArrowIcon()}</span>
446
469
  </button>
447
470
  <div class="phone-lib-dropdown-menu" style="display: none;">
448
471
  <div class="phone-lib-countries-list">
@@ -545,7 +568,7 @@ class PhoneLib {
545
568
  <button type="button" class="${dropdownButtonClass}" ${dropdownButtonStyle ? `style="${dropdownButtonStyle}"` : ''} aria-expanded="false" ${this.isDisabled ? 'disabled' : ''} aria-label="${this.options.ariaLabels.dropdownButton}">
546
569
  <span class="phone-lib-flag">${selectedCountryData?.flag || defaultFlag}</span>
547
570
  <span class="phone-lib-country-name-display">${selectedCountryData?.name || ''}</span>
548
- <span class="phone-lib-arrow">▼</span>
571
+ <span class="phone-lib-arrow">${this.getArrowIcon()}</span>
549
572
  </button>
550
573
  <div class="phone-lib-dropdown-menu" style="display: none;">
551
574
  <div class="phone-lib-countries-list">
@@ -637,94 +660,183 @@ class PhoneLib {
637
660
  * Adjunta los event listeners
638
661
  */
639
662
  attachEventListeners() {
663
+ // Remover listeners anteriores si existen (para evitar duplicados)
664
+ if (this._boundHandlers) {
665
+ this.removeEventListeners();
666
+ }
667
+
668
+ // Guardar referencias a handlers para poder removerlos después
669
+ this._boundHandlers = {
670
+ dropdownClick: (e) => {
671
+ if (this.isDisabled) return;
672
+ e.stopPropagation();
673
+ this.toggleDropdown();
674
+ },
675
+ documentClick: (e) => {
676
+ if (!this.container.contains(e.target)) {
677
+ this.closeDropdown();
678
+ }
679
+ }
680
+ };
681
+
640
682
  // Toggle dropdown
641
- this.dropdownButton.addEventListener('click', (e) => {
642
- if (this.isDisabled) return;
643
- e.stopPropagation();
644
- this.toggleDropdown();
645
- });
683
+ if (this.dropdownButton) {
684
+ this.dropdownButton.addEventListener('click', this._boundHandlers.dropdownClick);
685
+ }
646
686
 
647
687
  // Cerrar dropdown al hacer click fuera
648
- document.addEventListener('click', (e) => {
649
- if (!this.container.contains(e.target)) {
650
- this.closeDropdown();
651
- }
652
- });
688
+ document.addEventListener('click', this._boundHandlers.documentClick);
653
689
 
654
690
  // Seleccionar país
655
691
  const countryItems = this.container.querySelectorAll('.phone-lib-country-item');
692
+ this._countryItemHandlers = [];
656
693
  countryItems.forEach(item => {
657
- item.addEventListener('click', () => {
694
+ const handler = () => {
658
695
  if (item.classList.contains('disabled')) {
659
696
  return; // No permitir seleccionar países deshabilitados
660
697
  }
661
698
  const iso2 = item.dataset.iso2;
662
699
  const dialCode = item.dataset.dialCode;
663
- this.selectCountry(iso2, dialCode);
664
- });
700
+ if (iso2 && dialCode) {
701
+ this.selectCountry(iso2, dialCode);
702
+ }
703
+ };
704
+ item.addEventListener('click', handler);
705
+ this._countryItemHandlers.push({ item, handler });
665
706
  });
666
707
 
667
708
  // Input de teléfono
668
- this.phoneInput.addEventListener('input', (e) => {
669
- if (this.isDisabled || this.isReadonly) return;
670
- this.handlePhoneInput(e.target.value);
709
+ if (this.phoneInput) {
710
+ this._boundHandlers.phoneInput = (e) => {
711
+ if (this.isDisabled || this.isReadonly) return;
671
712
 
672
- // Validación en tiempo real si está habilitada
673
- if (this.options.validateOnInput) {
674
- this.validatePhone();
675
- }
676
- });
713
+ // Obtener el valor actual del input
714
+ const inputValue = e.target.value;
677
715
 
678
- this.phoneInput.addEventListener('focus', () => {
679
- this.executeCallback('onFocus');
680
- this.emitEvent('focus');
681
- });
716
+ // Procesar el input
717
+ this.handlePhoneInput(inputValue);
682
718
 
683
- this.phoneInput.addEventListener('blur', () => {
684
- const isValid = this.validatePhone();
685
- this.executeCallback('onBlur', this.phoneNumber, isValid);
686
- this.emitEvent('blur', { phoneNumber: this.phoneNumber, isValid });
687
- });
719
+ // Validación en tiempo real si está habilitada
720
+ if (this.options.validateOnInput) {
721
+ this.validatePhone();
722
+ }
723
+ };
724
+
725
+ this._boundHandlers.phoneFocus = () => {
726
+ this.executeCallback('onFocus');
727
+ this.emitEvent('focus');
728
+ };
729
+
730
+ this._boundHandlers.phoneBlur = () => {
731
+ const isValid = this.validatePhone();
732
+ this.executeCallback('onBlur', this.phoneNumber, isValid);
733
+ this.emitEvent('blur', { phoneNumber: this.phoneNumber, isValid });
734
+ };
735
+
736
+ this.phoneInput.addEventListener('input', this._boundHandlers.phoneInput);
737
+ this.phoneInput.addEventListener('focus', this._boundHandlers.phoneFocus);
738
+ this.phoneInput.addEventListener('blur', this._boundHandlers.phoneBlur);
739
+ }
688
740
 
689
741
  // Navegación por teclado en dropdown
690
742
  this.setupKeyboardNavigation();
691
743
  }
692
744
 
745
+ /**
746
+ * Remueve los event listeners
747
+ */
748
+ removeEventListeners() {
749
+ // Remover listeners del botón
750
+ if (this.dropdownButton && this._boundHandlers?.dropdownClick) {
751
+ this.dropdownButton.removeEventListener('click', this._boundHandlers.dropdownClick);
752
+ }
753
+
754
+ // Remover listener del document
755
+ if (this._boundHandlers?.documentClick) {
756
+ document.removeEventListener('click', this._boundHandlers.documentClick);
757
+ }
758
+
759
+ // Remover listeners de items de países
760
+ if (this._countryItemHandlers) {
761
+ this._countryItemHandlers.forEach(({ item, handler }) => {
762
+ item.removeEventListener('click', handler);
763
+ });
764
+ this._countryItemHandlers = [];
765
+ }
766
+
767
+ // Remover listeners del input
768
+ if (this.phoneInput && this._boundHandlers) {
769
+ if (this._boundHandlers.phoneInput) {
770
+ this.phoneInput.removeEventListener('input', this._boundHandlers.phoneInput);
771
+ }
772
+ if (this._boundHandlers.phoneFocus) {
773
+ this.phoneInput.removeEventListener('focus', this._boundHandlers.phoneFocus);
774
+ }
775
+ if (this._boundHandlers.phoneBlur) {
776
+ this.phoneInput.removeEventListener('blur', this._boundHandlers.phoneBlur);
777
+ }
778
+ }
779
+
780
+ // Remover listeners de teclado
781
+ if (this._keyboardHandlers) {
782
+ if (this.dropdownButton && this._keyboardHandlers.buttonKeydown) {
783
+ this.dropdownButton.removeEventListener('keydown', this._keyboardHandlers.buttonKeydown);
784
+ }
785
+ if (this.dropdownMenu && this._keyboardHandlers.menuKeydown) {
786
+ this.dropdownMenu.removeEventListener('keydown', this._keyboardHandlers.menuKeydown);
787
+ }
788
+ }
789
+ }
790
+
693
791
  /**
694
792
  * Configura navegación por teclado
695
793
  */
696
794
  setupKeyboardNavigation() {
697
795
  if (!this.dropdownButton) return;
698
796
 
699
- this.dropdownButton.addEventListener('keydown', (e) => {
700
- if (e.key === 'Enter' || e.key === ' ') {
701
- e.preventDefault();
702
- this.toggleDropdown();
703
- } else if (e.key === 'Escape') {
704
- this.closeDropdown();
705
- }
706
- });
707
-
708
- // Navegación con flechas en el dropdown
709
- if (this.dropdownMenu) {
710
- this.dropdownMenu.addEventListener('keydown', (e) => {
797
+ // Guardar handlers para poder removerlos después
798
+ this._keyboardHandlers = {
799
+ buttonKeydown: (e) => {
800
+ if (e.key === 'Enter' || e.key === ' ') {
801
+ e.preventDefault();
802
+ this.toggleDropdown();
803
+ } else if (e.key === 'Escape') {
804
+ this.closeDropdown();
805
+ }
806
+ },
807
+ menuKeydown: (e) => {
711
808
  const items = Array.from(this.container.querySelectorAll('.phone-lib-country-item:not([style*="display: none"])'));
809
+ if (items.length === 0) return;
810
+
712
811
  const currentIndex = items.findIndex(item => item.classList.contains('selected'));
713
812
 
714
813
  if (e.key === 'ArrowDown') {
715
814
  e.preventDefault();
716
815
  const nextIndex = (currentIndex + 1) % items.length;
717
- items[nextIndex]?.click();
718
- items[nextIndex]?.scrollIntoView({ block: 'nearest' });
816
+ const nextItem = items[nextIndex];
817
+ if (nextItem && !nextItem.classList.contains('disabled')) {
818
+ nextItem.click();
819
+ nextItem.scrollIntoView({ block: 'nearest' });
820
+ }
719
821
  } else if (e.key === 'ArrowUp') {
720
822
  e.preventDefault();
721
823
  const prevIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;
722
- items[prevIndex]?.click();
723
- items[prevIndex]?.scrollIntoView({ block: 'nearest' });
824
+ const prevItem = items[prevIndex];
825
+ if (prevItem && !prevItem.classList.contains('disabled')) {
826
+ prevItem.click();
827
+ prevItem.scrollIntoView({ block: 'nearest' });
828
+ }
724
829
  } else if (e.key === 'Escape') {
725
830
  this.closeDropdown();
726
831
  }
727
- });
832
+ }
833
+ };
834
+
835
+ this.dropdownButton.addEventListener('keydown', this._keyboardHandlers.buttonKeydown);
836
+
837
+ // Navegación con flechas en el dropdown
838
+ if (this.dropdownMenu) {
839
+ this.dropdownMenu.addEventListener('keydown', this._keyboardHandlers.menuKeydown);
728
840
  }
729
841
  }
730
842
 
@@ -781,7 +893,12 @@ class PhoneLib {
781
893
  * Actualiza el número de teléfono y formatea
782
894
  */
783
895
  updatePhoneNumber() {
896
+ if (!this.phoneInput) return;
897
+
784
898
  if (!this.phoneNumber) {
899
+ if (this.phoneInput.value !== '') {
900
+ this.phoneInput.value = '';
901
+ }
785
902
  if (this.hintElement) {
786
903
  this.hintElement.textContent = '';
787
904
  }
@@ -792,10 +909,27 @@ class PhoneLib {
792
909
  const formatter = new AsYouType(this.selectedCountry);
793
910
  const formatted = formatter.input(this.phoneNumber);
794
911
 
912
+ // Solo actualizar si el valor formateado es diferente
795
913
  if (formatted && formatted !== this.phoneInput.value) {
914
+ // Guardar la posición del cursor antes de actualizar
915
+ const cursorPosition = this.phoneInput.selectionStart;
916
+ const inputLength = this.phoneInput.value.length;
917
+ const wasAtEnd = cursorPosition === inputLength;
918
+
919
+ // Actualizar el valor
796
920
  this.phoneInput.value = formatted;
797
- // Actualizar phoneNumber con el valor formateado para mantener sincronización
798
- this.phoneNumber = formatted;
921
+
922
+ // Restaurar la posición del cursor de manera inteligente
923
+ if (wasAtEnd) {
924
+ // Si estaba al final, mantenerlo al final
925
+ this.phoneInput.setSelectionRange(formatted.length, formatted.length);
926
+ } else {
927
+ // Intentar mantener la posición relativa
928
+ // Calcular la nueva posición basada en la diferencia de longitud
929
+ const lengthDiff = formatted.length - inputLength;
930
+ const newPosition = Math.max(0, Math.min(cursorPosition + lengthDiff, formatted.length));
931
+ this.phoneInput.setSelectionRange(newPosition, newPosition);
932
+ }
799
933
  }
800
934
 
801
935
  // Actualizar hint
@@ -893,12 +1027,24 @@ class PhoneLib {
893
1027
  * Selecciona un país
894
1028
  */
895
1029
  selectCountry(iso2, dialCode, silent = false) {
1030
+ // Validar que el país existe
1031
+ if (!iso2 || !dialCode) {
1032
+ console.warn('PhoneLib: selectCountry requiere iso2 y dialCode válidos');
1033
+ return;
1034
+ }
1035
+
896
1036
  const previousCountry = this.selectedCountry;
897
1037
  this.selectedCountry = iso2;
898
1038
 
899
1039
  // Actualizar botón según el layout
900
1040
  const countryData = this.countries.find(c => c.iso2 === iso2);
901
1041
 
1042
+ // Si el país no se encuentra, usar valores por defecto
1043
+ if (!countryData) {
1044
+ console.warn(`PhoneLib: País ${iso2} no encontrado en la lista`);
1045
+ return;
1046
+ }
1047
+
902
1048
  if (this.options.layout === 'separated') {
903
1049
  // Layout separado: actualizar flag, nombre del país y código de marcación (si está visible)
904
1050
  const flagElement = this.dropdownButton.querySelector('.phone-lib-flag');
@@ -935,9 +1081,11 @@ class PhoneLib {
935
1081
  // Cerrar dropdown
936
1082
  this.closeDropdown();
937
1083
 
938
- // Actualizar placeholder y formato
939
- this.phoneInput.placeholder = this.getPlaceholder();
940
- this.updatePhoneNumber();
1084
+ // Actualizar placeholder y formato (solo si el input existe)
1085
+ if (this.phoneInput) {
1086
+ this.phoneInput.placeholder = this.getPlaceholder();
1087
+ this.updatePhoneNumber();
1088
+ }
941
1089
 
942
1090
  // Ejecutar callbacks y eventos solo si no es silencioso
943
1091
  if (!silent) {
@@ -1229,11 +1377,8 @@ class PhoneLib {
1229
1377
  * Destruye la instancia y limpia recursos
1230
1378
  */
1231
1379
  destroy() {
1232
- // Remover event listeners
1233
- if (this.phoneInput) {
1234
- const newInput = this.phoneInput.cloneNode(true);
1235
- this.phoneInput.parentNode.replaceChild(newInput, this.phoneInput);
1236
- }
1380
+ // Remover todos los event listeners
1381
+ this.removeEventListeners();
1237
1382
 
1238
1383
  // Limpiar contenedor
1239
1384
  if (this.container) {
@@ -1247,6 +1392,9 @@ class PhoneLib {
1247
1392
  this.hintElement = null;
1248
1393
  this.dialCodeInput = null;
1249
1394
  this.countriesList = null;
1395
+ this._boundHandlers = null;
1396
+ this._countryItemHandlers = null;
1397
+ this._keyboardHandlers = null;
1250
1398
  }
1251
1399
 
1252
1400
  /**
@@ -1276,7 +1424,7 @@ class PhoneLib {
1276
1424
  }
1277
1425
 
1278
1426
  // Re-renderizar si cambió layout u opciones visuales importantes
1279
- if (newOptions.layout || newOptions.showDialCode !== undefined || newOptions.customClasses || newOptions.customStyles) {
1427
+ if (newOptions.layout || newOptions.showDialCode !== undefined || newOptions.customClasses || newOptions.customStyles || newOptions.arrowIcon !== undefined) {
1280
1428
  this.render();
1281
1429
  this.attachEventListeners();
1282
1430
  }