@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 +30 -6
- package/package.json +1 -1
- package/phone-lib.cdn.js +3 -3
- package/phone-lib.css +31 -2
- package/phone-lib.js +208 -60
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.
|
|
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.
|
|
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.
|
|
93
|
-
- **unpkg:** `https://unpkg.com/@jacksonavila/phone-lib@2.0.
|
|
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.
|
|
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.
|
|
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
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.
|
|
8
|
-
* <script src="https://cdn.jsdelivr.net/npm/@jacksonavila/phone-lib@2.0.
|
|
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.
|
|
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
|
-
|
|
70
|
-
|
|
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"
|
|
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"
|
|
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
|
|
642
|
-
|
|
643
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
669
|
-
|
|
670
|
-
|
|
709
|
+
if (this.phoneInput) {
|
|
710
|
+
this._boundHandlers.phoneInput = (e) => {
|
|
711
|
+
if (this.isDisabled || this.isReadonly) return;
|
|
671
712
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
this.validatePhone();
|
|
675
|
-
}
|
|
676
|
-
});
|
|
713
|
+
// Obtener el valor actual del input
|
|
714
|
+
const inputValue = e.target.value;
|
|
677
715
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
this.emitEvent('focus');
|
|
681
|
-
});
|
|
716
|
+
// Procesar el input
|
|
717
|
+
this.handlePhoneInput(inputValue);
|
|
682
718
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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]
|
|
718
|
-
|
|
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]
|
|
723
|
-
|
|
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
|
-
|
|
798
|
-
|
|
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
|
|
940
|
-
|
|
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
|
-
|
|
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
|
}
|