@dile/utils 3.2.3 → 3.3.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.
Files changed (17) hide show
  1. package/components/country-select/src/countriesData.js +14 -2
  2. package/components/input-phone/__screenshots__/input-phone.spec.js/dile-input-phone-Rendering-preselects-the-prefix-of-the-country-attribute-1.png +0 -0
  3. package/components/input-phone/__screenshots__/input-phone.spec.js/dile-input-phone-Value-set-from-outside-keeps-the-country-when-it-shares-the-prefix-of-the-value-1.png +0 -0
  4. package/components/input-phone/__screenshots__/input-phone.spec.js/dile-input-phone-Value-with-prefix-does-not-let-the-prefix-selector-events-escape-as-the-field-own-events-1.png +0 -0
  5. package/components/input-phone/__screenshots__/input-phone.spec.js/dile-input-phone-Value-with-prefix-updates-the-value-when-the-user-chooses-a-prefix-1.png +0 -0
  6. package/components/input-phone/index.js +1 -0
  7. package/components/input-phone/input-phone.js +7 -0
  8. package/components/input-phone/input-phone.spec.js +345 -0
  9. package/components/input-phone/src/DileInputPhone.js +294 -0
  10. package/components/phone-prefix-select/__screenshots__/phone-prefix-select.spec.js/dile-phone-prefix-select-Initial-value-and-country-ignores-a-country-that-does-not-match-the-prefix-1.png +0 -0
  11. package/components/phone-prefix-select/__screenshots__/phone-prefix-select.spec.js/dile-phone-prefix-select-Searching-compact-gives-the-selected-field-an-accessible-name-with-the-country-1.png +0 -0
  12. package/components/phone-prefix-select/index.js +10 -0
  13. package/components/phone-prefix-select/phone-prefix-select.js +7 -0
  14. package/components/phone-prefix-select/phone-prefix-select.spec.js +257 -0
  15. package/components/phone-prefix-select/src/DilePhonePrefixSelect.js +213 -0
  16. package/components/phone-prefix-select/src/phonePrefixesData.js +128 -0
  17. package/package.json +3 -3
@@ -38,7 +38,6 @@ export const COUNTRIES_ES = [
38
38
  { name: 'Camerún', iso: 'CM' },
39
39
  { name: 'Canadá', iso: 'CA' },
40
40
  { name: 'Catar', iso: 'QA' },
41
- { name: 'Cazajstán', iso: 'KZ' },
42
41
  { name: 'Chad', iso: 'TD' },
43
42
  { name: 'Chile', iso: 'CL' },
44
43
  { name: 'China', iso: 'CN' },
@@ -61,7 +60,9 @@ export const COUNTRIES_ES = [
61
60
  { name: 'República Dominicana', iso: 'DO' },
62
61
  { name: 'Ecuador', iso: 'EC' },
63
62
  { name: 'Egipto', iso: 'EG' },
63
+ { name: 'El Salvador', iso: 'SV' },
64
64
  { name: 'Emiratos Árabes Unidos', iso: 'AE' },
65
+ { name: 'Eritrea', iso: 'ER' },
65
66
  { name: 'Eslovakia', iso: 'SK' },
66
67
  { name: 'Eslovenia', iso: 'SI' },
67
68
  { name: 'España', iso: 'ES' },
@@ -77,7 +78,9 @@ export const COUNTRIES_ES = [
77
78
  { name: 'Gambia', iso: 'GM' },
78
79
  { name: 'Georgia', iso: 'GE' },
79
80
  { name: 'Georgia del Sur y las Islas Sandwich del Sur', iso: 'GS' },
81
+ { name: 'Ghana', iso: 'GH' },
80
82
  { name: 'Gibraltar', iso: 'GI' },
83
+ { name: 'Granada', iso: 'GD' },
81
84
  { name: 'Grecia', iso: 'GR' },
82
85
  { name: 'Groenlandia', iso: 'GL' },
83
86
  { name: 'Guadalupe', iso: 'GP' },
@@ -93,6 +96,11 @@ export const COUNTRIES_ES = [
93
96
  { name: 'Honduras', iso: 'HN' },
94
97
  { name: 'Hong Kong', iso: 'HK' },
95
98
  { name: 'Hungría', iso: 'HU' },
99
+ { name: 'India', iso: 'IN' },
100
+ { name: 'Indonesia', iso: 'ID' },
101
+ { name: 'Irak', iso: 'IQ' },
102
+ { name: 'Irán', iso: 'IR' },
103
+ { name: 'Irlanda', iso: 'IE' },
96
104
  { name: 'Isla Bouvet', iso: 'BV' },
97
105
  { name: 'Isla Christmas', iso: 'CX' },
98
106
  { name: 'Isla de Man', iso: 'IM' },
@@ -112,6 +120,7 @@ export const COUNTRIES_ES = [
112
120
  { name: 'Islas Pitcairn', iso: 'PN' },
113
121
  { name: 'Islas Salomón', iso: 'SB' },
114
122
  { name: 'Islas Turcas y Caicos', iso: 'TC' },
123
+ { name: 'Islas Ultramarinas Menores de Estados Unidos', iso: 'UM' },
115
124
  { name: 'Islas Vírgenes Británicas', iso: 'VG' },
116
125
  { name: 'Islas Vírgenes de Estados Unidos', iso: 'VI' },
117
126
  { name: 'Islas Wallis y Futuna', iso: 'WF' },
@@ -137,6 +146,7 @@ export const COUNTRIES_ES = [
137
146
  { name: 'Liechtenstein', iso: 'LI' },
138
147
  { name: 'Lituania', iso: 'LT' },
139
148
  { name: 'Luxemburgo', iso: 'LU' },
149
+ { name: 'Macao', iso: 'MO' },
140
150
  { name: 'Madagascar', iso: 'MG' },
141
151
  { name: 'Malasia', iso: 'MY' },
142
152
  { name: 'Malawi', iso: 'MW' },
@@ -161,6 +171,7 @@ export const COUNTRIES_ES = [
161
171
  { name: 'Nauru', iso: 'NR' },
162
172
  { name: 'Nepal', iso: 'NP' },
163
173
  { name: 'Nicaragua', iso: 'NI' },
174
+ { name: 'Níger', iso: 'NE' },
164
175
  { name: 'Nigeria', iso: 'NG' },
165
176
  { name: 'Niue', iso: 'NU' },
166
177
  { name: 'Noruega', iso: 'NO' },
@@ -249,6 +260,7 @@ export const COUNTRIES_EN = [
249
260
  { name: 'Afghanistan', iso: 'AF' },
250
261
  { name: 'Albania', iso: 'AL' },
251
262
  { name: 'Algeria', iso: 'DZ' },
263
+ { name: 'American Samoa', iso: 'AS' },
252
264
  { name: 'Andorra', iso: 'AD' },
253
265
  { name: 'Angola', iso: 'AO' },
254
266
  { name: 'Anguilla', iso: 'AI' },
@@ -301,6 +313,7 @@ export const COUNTRIES_EN = [
301
313
  { name: 'Czech Republic', iso: 'CZ' },
302
314
  { name: 'Côte d\'Ivoire', iso: 'CI' },
303
315
  { name: 'Curaçao', iso: 'CW' },
316
+ { name: 'Democratic Republic of the Congo', iso: 'CD' },
304
317
  { name: 'Denmark', iso: 'DK' },
305
318
  { name: 'Djibouti', iso: 'DJ' },
306
319
  { name: 'Dominica', iso: 'DM' },
@@ -421,7 +434,6 @@ export const COUNTRIES_EN = [
421
434
  { name: 'Portugal', iso: 'PT' },
422
435
  { name: 'Puerto Rico', iso: 'PR' },
423
436
  { name: 'Qatar', iso: 'QA' },
424
- { name: 'Republic of the Congo', iso: 'CD' },
425
437
  { name: 'Réunion', iso: 'RE' },
426
438
  { name: 'Romania', iso: 'RO' },
427
439
  { name: 'Russia', iso: 'RU' },
@@ -0,0 +1 @@
1
+ export { DileInputPhone } from './src/DileInputPhone.js';
@@ -0,0 +1,7 @@
1
+ import { DileInputPhone } from './src/DileInputPhone.js';
2
+
3
+ if (!customElements.get('dile-input-phone')) {
4
+ customElements.define('dile-input-phone', DileInputPhone);
5
+ }
6
+
7
+ export { DileInputPhone };
@@ -0,0 +1,345 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './input-phone.js';
3
+
4
+ describe('dile-input-phone', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ function wait(ms) {
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+
13
+ async function render(attributes = '') {
14
+ document.body.innerHTML = `<dile-input-phone ${attributes}></dile-input-phone>`;
15
+ const el = document.body.querySelector('dile-input-phone');
16
+ await el.updateComplete;
17
+ await el.prefixSelect?.updateComplete;
18
+ return el;
19
+ }
20
+
21
+ function numberInput(el) {
22
+ return el.shadowRoot.getElementById('textField');
23
+ }
24
+
25
+ async function typeNumber(el, text) {
26
+ const input = numberInput(el);
27
+ input.value = text;
28
+ input.dispatchEvent(new Event('input', { bubbles: true }));
29
+ await el.updateComplete;
30
+ }
31
+
32
+ async function blurNumber(el) {
33
+ numberInput(el).dispatchEvent(new Event('blur'));
34
+ await el.updateComplete;
35
+ await el.prefixSelect?.updateComplete;
36
+ }
37
+
38
+ async function choosePrefix(el, keyword, optionText) {
39
+ const prefixSelect = el.prefixSelect;
40
+ const search = prefixSelect.shadowRoot.getElementById('search');
41
+ search.value = keyword;
42
+ search.dispatchEvent(new Event('input', { bubbles: true }));
43
+ await wait(20);
44
+ await prefixSelect.updateComplete;
45
+ const option = Array.from(prefixSelect.select.shadowRoot.querySelectorAll('[role="option"]'))
46
+ .find(o => o.textContent.trim() === optionText);
47
+ option.click();
48
+ await prefixSelect.select.updateComplete;
49
+ await prefixSelect.updateComplete;
50
+ await el.updateComplete;
51
+ }
52
+
53
+ describe('Rendering', () => {
54
+ it('renders the prefix selector and a tel input', async () => {
55
+ const el = await render('label="Phone"');
56
+
57
+ expect(el.prefixSelect).toBeTruthy();
58
+ const input = numberInput(el);
59
+ expect(input.type).toBe('tel');
60
+ expect(input.getAttribute('inputmode')).toBe('tel');
61
+ expect(input.getAttribute('autocomplete')).toBe('tel-national');
62
+ expect(el.shadowRoot.querySelector('label').textContent).toBe('Phone');
63
+ });
64
+
65
+ it('does not render the prefix selector with noPrefix', async () => {
66
+ const el = await render('noPrefix');
67
+
68
+ expect(el.prefixSelect).toBeNull();
69
+ expect(numberInput(el).getAttribute('autocomplete')).toBe('tel');
70
+ });
71
+
72
+ it('preselects the prefix of the country attribute', async () => {
73
+ const el = await render('country="ES"');
74
+
75
+ expect(el.prefix).toBe('+34');
76
+ expect(el.prefixSelect.value).toBe('+34');
77
+ // Compact: only the prefix once selected, to leave room for the number
78
+ expect(el.prefixSelect.selectedText).toBe('+34');
79
+ });
80
+
81
+ it('screen readers get the country of the compact prefix', async () => {
82
+ const el = await render('country="ES"');
83
+ const input = el.prefixSelect.shadowRoot.getElementById('result').shadowRoot.querySelector('input');
84
+
85
+ expect(input.getAttribute('aria-label')).toBe('+34 Spain');
86
+ });
87
+
88
+ it('lets the prefix list be wider than the narrow prefix box', async () => {
89
+ const el = await render();
90
+ const overlay = el.prefixSelect.select.shadowRoot.getElementById('overlay');
91
+
92
+ expect(getComputedStyle(overlay).minWidth).toBe('256px');
93
+ });
94
+
95
+ it('passes language to the prefix selector, with a short placeholder', async () => {
96
+ const el = await render('language="es"');
97
+
98
+ expect(el.prefixSelect.language).toBe('es');
99
+ expect(el.prefixSelect.shadowRoot.getElementById('search').placeholder).toBe('Prefijo');
100
+ });
101
+ });
102
+
103
+ describe('Value with prefix', () => {
104
+ it('combines the selected prefix and the typed number in E.164 format', async () => {
105
+ const el = await render('country="ES" name="phone"');
106
+ let detail = null;
107
+ el.addEventListener('element-changed', (e) => { detail = e.detail; });
108
+
109
+ await typeNumber(el, '600 12 34 56');
110
+
111
+ expect(el.value).toBe('+34600123456');
112
+ expect(el.number).toBe('600123456');
113
+ expect(detail).toEqual({ name: 'phone', value: '+34600123456' });
114
+ // The field keeps what the user typed
115
+ expect(numberInput(el).value).toBe('600 12 34 56');
116
+ });
117
+
118
+ it('updates the value when the user chooses a prefix', async () => {
119
+ const el = await render();
120
+ await typeNumber(el, '612345678');
121
+ expect(el.value).toBe('612345678');
122
+
123
+ await choosePrefix(el, 'portugal', '+351 Portugal');
124
+
125
+ expect(el.country).toBe('PT');
126
+ expect(el.value).toBe('+351612345678');
127
+ });
128
+
129
+ it('does not let the prefix selector events escape as the field own events', async () => {
130
+ const el = await render();
131
+ const names = [];
132
+ el.addEventListener('element-changed', (e) => names.push(e.detail.name));
133
+ document.body.addEventListener('dile-phone-prefix-changed', () => names.push('leaked'));
134
+
135
+ await choosePrefix(el, 'spain', '+34 Spain');
136
+
137
+ expect(names.includes('leaked')).toBe(false);
138
+ expect(names.every(name => name === el.name)).toBe(true);
139
+ });
140
+
141
+ it('accepts a full international number typed in the field and moves the prefix to the selector on blur', async () => {
142
+ const el = await render();
143
+
144
+ await typeNumber(el, '+44 7700 900123');
145
+ expect(el.value).toBe('+447700900123');
146
+
147
+ await blurNumber(el);
148
+
149
+ expect(el.country).toBe('GB');
150
+ expect(numberInput(el).value).toBe('7700900123');
151
+ expect(el.value).toBe('+447700900123');
152
+ expect(el.prefixSelect.value).toBe('+44');
153
+ });
154
+
155
+ it('understands the 00 international prefix', async () => {
156
+ const el = await render();
157
+
158
+ await typeNumber(el, '0034600123456');
159
+
160
+ expect(el.value).toBe('+34600123456');
161
+ });
162
+
163
+ it('removes characters that are not used in phone numbers', async () => {
164
+ const el = await render('country="ES"');
165
+
166
+ await typeNumber(el, '600abc123');
167
+
168
+ expect(numberInput(el).value).toBe('600123');
169
+ expect(el.value).toBe('+34600123');
170
+ });
171
+
172
+ it('a country set from outside changes the prefix of the typed number', async () => {
173
+ const el = await render('country="ES"');
174
+ await typeNumber(el, '600123456');
175
+
176
+ el.country = 'FR';
177
+ await el.updateComplete;
178
+
179
+ expect(el.value).toBe('+33600123456');
180
+ });
181
+ });
182
+
183
+ describe('Value set from outside', () => {
184
+ it('splits an initial international value into prefix and number', async () => {
185
+ const el = await render('value="+34600123456"');
186
+
187
+ expect(el.country).toBe('ES');
188
+ expect(el.prefix).toBe('+34');
189
+ expect(numberInput(el).value).toBe('600123456');
190
+ expect(el.prefixSelect.value).toBe('+34');
191
+ expect(el.value).toBe('+34600123456');
192
+ });
193
+
194
+ it('keeps the country when it shares the prefix of the value', async () => {
195
+ const el = await render('value="+15145550199" country="CA"');
196
+
197
+ expect(el.country).toBe('CA');
198
+ expect(el.prefixSelect.selectedText).toBe('+1');
199
+ });
200
+
201
+ it('uses the main country of a shared prefix', async () => {
202
+ const el = await render('value="+15555550199"');
203
+
204
+ expect(el.country).toBe('US');
205
+ });
206
+
207
+ it('keeps an unknown or national value as typed, without rewriting it', async () => {
208
+ const el = await render('country="ES" value="600123456"');
209
+
210
+ expect(numberInput(el).value).toBe('600123456');
211
+ expect(el.value).toBe('600123456');
212
+ });
213
+
214
+ it('parses values set later, as DileForm does', async () => {
215
+ const el = await render();
216
+
217
+ el.value = '+351912345678';
218
+ await el.updateComplete;
219
+
220
+ expect(el.country).toBe('PT');
221
+ expect(numberInput(el).value).toBe('912345678');
222
+ });
223
+
224
+ it('clear() empties the number and restores the initial country', async () => {
225
+ const el = await render('country="ES"');
226
+ el.value = '+447700900123';
227
+ await el.updateComplete;
228
+ expect(el.country).toBe('GB');
229
+
230
+ el.clear();
231
+ await el.updateComplete;
232
+
233
+ expect(el.value).toBe('');
234
+ expect(numberInput(el).value).toBe('');
235
+ expect(el.country).toBe('ES');
236
+
237
+ // A value set after clear() is still parsed
238
+ el.value = '+33612345678';
239
+ await el.updateComplete;
240
+ expect(el.country).toBe('FR');
241
+ });
242
+
243
+ it('clearing the value removes the prefix when there was no initial country', async () => {
244
+ const el = await render('value="+34600123456"');
245
+
246
+ el.clear();
247
+ await el.updateComplete;
248
+ await el.prefixSelect.updateComplete;
249
+
250
+ expect(el.country).toBeUndefined();
251
+ expect(el.prefixSelect.value).toBeUndefined();
252
+ });
253
+ });
254
+
255
+ describe('noPrefix', () => {
256
+ it('the value is the typed number without separators', async () => {
257
+ const el = await render('noPrefix');
258
+
259
+ await typeNumber(el, '(600) 12-34-56');
260
+
261
+ expect(el.value).toBe('600123456');
262
+ expect(el.prefix).toBeUndefined();
263
+ });
264
+
265
+ it('keeps an international number typed by the user', async () => {
266
+ const el = await render('noPrefix');
267
+
268
+ await typeNumber(el, '+34 600 123 456');
269
+
270
+ expect(el.value).toBe('+34600123456');
271
+ });
272
+
273
+ it('shows a value set from outside as is', async () => {
274
+ const el = await render('noPrefix value="+34600123456"');
275
+
276
+ expect(numberInput(el).value).toBe('+34600123456');
277
+ });
278
+ });
279
+
280
+ describe('isValid()', () => {
281
+ it('an empty value is valid', async () => {
282
+ const el = await render();
283
+ expect(el.isValid()).toBe(true);
284
+ });
285
+
286
+ it('with prefix requires an international number', async () => {
287
+ const el = await render();
288
+
289
+ await typeNumber(el, '600123456');
290
+ expect(el.isValid()).toBe(false);
291
+
292
+ el.country = 'ES';
293
+ await el.updateComplete;
294
+ expect(el.isValid()).toBe(true);
295
+ });
296
+
297
+ it('rejects too short or too long numbers', async () => {
298
+ const el = await render('country="ES"');
299
+
300
+ await typeNumber(el, '12');
301
+ expect(el.isValid()).toBe(false);
302
+
303
+ await typeNumber(el, '1234567890123456');
304
+ expect(el.isValid()).toBe(false);
305
+ });
306
+
307
+ it('without prefix checks only the length', async () => {
308
+ const el = await render('noPrefix');
309
+
310
+ await typeNumber(el, '600123456');
311
+ expect(el.isValid()).toBe(true);
312
+
313
+ await typeNumber(el, '12');
314
+ expect(el.isValid()).toBe(false);
315
+ });
316
+ });
317
+
318
+ describe('States and forms', () => {
319
+ it('applies errored and disabled to the prefix selector and the input', async () => {
320
+ const el = await render('errored disabled message="Wrong phone"');
321
+
322
+ expect(el.prefixSelect.errored).toBe(true);
323
+ expect(el.prefixSelect.disabled).toBe(true);
324
+ expect(numberInput(el).disabled).toBe(true);
325
+ expect(el.shadowRoot.querySelector('.message').textContent.trim()).toBe('Wrong phone');
326
+ });
327
+
328
+ it('hideErrorOnInput clears the error when typing', async () => {
329
+ const el = await render('errored message="Wrong phone" hideErrorOnInput');
330
+
331
+ await typeNumber(el, '6');
332
+
333
+ expect(el.errored).toBe(false);
334
+ expect(el.message).toBe('');
335
+ });
336
+
337
+ it('is form-associated with the full number as form value', async () => {
338
+ document.body.innerHTML = '<form><dile-input-phone name="phone" value="+34600123456"></dile-input-phone></form>';
339
+ const el = document.body.querySelector('dile-input-phone');
340
+ await el.updateComplete;
341
+
342
+ expect(new FormData(document.body.querySelector('form')).get('phone')).toBe('+34600123456');
343
+ });
344
+ });
345
+ });