@descope/web-components-ui 1.44.0 → 1.46.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 (43) hide show
  1. package/dist/cjs/index.cjs.js +58 -24
  2. package/dist/cjs/index.cjs.js.map +1 -1
  3. package/dist/index.esm.js +469 -232
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/umd/3620.js +1 -1
  6. package/dist/umd/3620.js.map +1 -1
  7. package/dist/umd/5348.js +2 -0
  8. package/dist/umd/5348.js.map +1 -0
  9. package/dist/umd/6477.js +149 -0
  10. package/dist/umd/6477.js.map +1 -0
  11. package/dist/umd/9365.js +1 -1
  12. package/dist/umd/9365.js.map +1 -1
  13. package/dist/umd/DescopeDev.js +1 -1
  14. package/dist/umd/DescopeDev.js.map +1 -1
  15. package/dist/umd/descope-hybrid-field-index-js.js +3 -3
  16. package/dist/umd/descope-hybrid-field-index-js.js.map +1 -1
  17. package/dist/umd/descope-passcode-index-js.js +1 -1
  18. package/dist/umd/descope-passcode-index-js.js.map +1 -1
  19. package/dist/umd/index.js +1 -1
  20. package/dist/umd/index.js.map +1 -1
  21. package/dist/umd/phone-fields-descope-phone-field-descope-phone-field-internal-index-js.js +1 -1
  22. package/dist/umd/phone-fields-descope-phone-field-descope-phone-field-internal-index-js.js.map +1 -1
  23. package/dist/umd/phone-fields-descope-phone-field-index-js.js +1 -1
  24. package/dist/umd/phone-fields-descope-phone-field-index-js.js.map +1 -1
  25. package/dist/umd/phone-fields-descope-phone-input-box-field-descope-phone-input-box-internal-index-js.js +2 -2
  26. package/dist/umd/phone-fields-descope-phone-input-box-field-descope-phone-input-box-internal-index-js.js.map +1 -1
  27. package/dist/umd/phone-fields-descope-phone-input-box-field-index-js.js +2 -113
  28. package/dist/umd/phone-fields-descope-phone-input-box-field-index-js.js.LICENSE.txt +0 -6
  29. package/dist/umd/phone-fields-descope-phone-input-box-field-index-js.js.map +1 -1
  30. package/package.json +7 -6
  31. package/src/components/descope-hybrid-field/HybridFieldClass.js +6 -0
  32. package/src/components/descope-passcode/PasscodeClass.js +2 -0
  33. package/src/components/phone-fields/descope-phone-field/PhoneFieldClass.js +10 -2
  34. package/src/components/phone-fields/descope-phone-field/descope-phone-field-internal/PhoneFieldInternal.js +229 -126
  35. package/src/components/phone-fields/descope-phone-input-box-field/PhoneFieldInputBoxClass.js +42 -24
  36. package/src/components/phone-fields/descope-phone-input-box-field/descope-phone-input-box-internal/PhoneFieldInternalInputBox.js +179 -83
  37. package/src/components/phone-fields/descope-phone-input-box-field/index.js +0 -1
  38. package/src/components/phone-fields/helpers.js +7 -0
  39. package/src/mixins/index.js +1 -0
  40. package/src/mixins/inputOverrideValidConstraints.js +12 -0
  41. package/dist/umd/6424.js +0 -149
  42. package/dist/umd/6424.js.map +0 -1
  43. /package/dist/umd/{6424.js.LICENSE.txt → 6477.js.LICENSE.txt} +0 -0
@@ -1,20 +1,19 @@
1
1
  import { createBaseInputClass } from '../../../../baseClasses/createBaseInputClass';
2
2
  import { getComponentName } from '../../../../helpers/componentHelpers';
3
- import { getCountryByCodeId } from '../../helpers';
3
+ import { getCountryByCodeId, matchingParenthesis } from '../../helpers';
4
+ import parsePhoneNumberFromString, { AsYouType } from 'libphonenumber-js/min';
4
5
 
5
6
  export const componentName = getComponentName('phone-field-internal-input-box');
6
7
 
7
8
  const observedAttributes = [
8
9
  'disabled',
9
10
  'size',
10
- 'bordered',
11
- 'invalid',
12
11
  'readonly',
13
12
  'phone-input-placeholder',
14
13
  'name',
14
+ 'maxlength',
15
15
  'autocomplete',
16
16
  'label-type',
17
- 'allow-alphanumeric-input',
18
17
  ];
19
18
  const mapAttrs = {
20
19
  'phone-input-placeholder': 'placeholder',
@@ -27,145 +26,242 @@ class PhoneFieldInternal extends BaseInputClass {
27
26
  return [].concat(BaseInputClass.observedAttributes || [], observedAttributes);
28
27
  }
29
28
 
29
+ #ayt;
30
+
30
31
  constructor() {
31
32
  super();
32
33
 
33
34
  this.innerHTML = `
34
35
  <div>
35
- <descope-text-field tabindex="1"></descope-text-field>
36
+ <descope-text-field tabindex="1" type="tel" bordered="false"></descope-text-field>
36
37
  </div>
37
38
  `;
38
39
 
39
- this.phoneNumberInput = this.querySelector('descope-text-field');
40
+ this.textField = this.querySelector('descope-text-field');
41
+ }
42
+
43
+ // notice: this function is exposed in parent component
44
+ get phoneNumberInputEle() {
45
+ return this.textField.shadowRoot.querySelector('input');
40
46
  }
41
47
 
42
- get defaultCountryCode() {
48
+ get defaultDialCode() {
43
49
  return getCountryByCodeId(this.getAttribute('default-code'));
44
50
  }
45
51
 
46
- get hasDefaultCode() {
47
- return !!this.getAttribute('default-code');
52
+ get defaultCode() {
53
+ return this.getAttribute('default-code');
48
54
  }
49
55
 
50
56
  get allowAlphanumericInput() {
51
57
  return this.getAttribute('allow-alphanumeric-input') === 'true';
52
58
  }
53
59
 
54
- get value() {
55
- if (!this.phoneNumberValue) {
56
- return '';
57
- }
60
+ get minLength() {
61
+ return parseInt(this.getAttribute('minlength'), 10) || 0;
62
+ }
58
63
 
59
- if (this.hasDefaultCode) {
60
- // we want to transform phone numbers to a valid {dialCode}-{phoneNumber} format
61
- // e.g.:
62
- // +972-12345 => +972-12345
63
- // 972-12345 => +972-12345
64
- // 12345 => +972-12345
65
- //
66
- // we also want to handle any extra dash if added in the start of the phone number
67
- // e.g.:
68
- // +972--12345 => +972-12345
69
- const pattern = new RegExp(`\\+?${parseInt(this.defaultCountryCode, 10)}--?`);
70
- return `${this.defaultCountryCode}-${this.phoneNumberInput.value.replace(pattern, '')}`;
71
- }
64
+ get maxLength() {
65
+ return parseInt(this.getAttribute('maxlength'), 10) || 50;
66
+ }
72
67
 
73
- return this.phoneNumberInput.value;
68
+ get restrictCountries() {
69
+ return this.getAttribute('restrict-countries')?.split(',').filter(Boolean) || [];
74
70
  }
75
71
 
76
- set value(val) {
77
- this.phoneNumberInput.value = val;
72
+ get isFormatValue() {
73
+ return this.getAttribute('format-value') === 'true';
78
74
  }
79
75
 
80
- get phoneNumberValue() {
81
- return this.phoneNumberInput.value;
76
+ get isStrictValidation() {
77
+ return this.getAttribute('strict-validation') === 'true';
82
78
  }
83
79
 
84
- get phoneNumberInputEle() {
85
- return this.phoneNumberInput.shadowRoot.querySelector('input');
80
+ get value() {
81
+ if (!this.textField.value) return '';
82
+
83
+ if (!this.isStrictValidation) {
84
+ return this.#nonParsedValue();
85
+ }
86
+
87
+ const parsedVal = this.#parseWithCountryCode();
88
+
89
+ if (parsedVal?.country && parsedVal?.countryCallingCode && parsedVal?.nationalNumber) {
90
+ return `+${[parsedVal?.countryCallingCode, parsedVal?.nationalNumber].join('-')}`;
91
+ }
92
+
93
+ // if failed to parse or to find country code return text field value
94
+ return this.textField.value;
86
95
  }
87
96
 
88
- get minLength() {
89
- return parseInt(this.getAttribute('minlength'), 10) || 0;
97
+ set value(val) {
98
+ this.textField.value = val;
90
99
  }
91
100
 
92
- get maxLength() {
93
- return parseInt(this.getAttribute('maxlength'), 10) || 50;
101
+ init() {
102
+ this.addEventListener('focus', (e) => {
103
+ // We want to ignore focus events we are dispatching
104
+ if (e.isTrusted) this.textField.focus();
105
+ });
106
+
107
+ super.init?.();
108
+
109
+ this.textField.addEventListener('input', this.#onInput.bind(this));
110
+ this.handleFocusEventsDispatching([this.textField]);
94
111
  }
95
112
 
96
113
  getValidity() {
97
114
  const validPhonePattern = /^\+?\d{1,4}-?(?:\d-?){1,15}$/;
98
- const stripValue = this.value.replace(/\D/g, '');
115
+ const stripValue = this.#sanitizeVal(this.textField.value);
99
116
 
100
- if (this.isRequired && !this.value) {
117
+ if (this.isRequired && !this.textField.value) {
101
118
  return { valueMissing: true };
102
119
  }
103
120
 
104
- if (stripValue.length < this.minLength) {
105
- return { tooShort: true };
121
+ if (this.textField.value) {
122
+ if (stripValue.length < this.minLength) {
123
+ return { tooShort: true };
124
+ }
125
+
126
+ if (
127
+ // has `strict-validation` and not properly parsed
128
+ (this.isStrictValidation && this.textField.value && !this.#isValidParsedValue()) ||
129
+ // if no `strict-validation` then conform with naive pattern
130
+ (!this.isStrictValidation && this.textField.value && !validPhonePattern.test(this.value))
131
+ ) {
132
+ return { patternMismatch: true };
133
+ }
106
134
  }
107
135
 
108
- if (stripValue.length > this.maxLength) {
109
- return { tooLong: true };
136
+ return {};
137
+ }
138
+
139
+ setSelectionRange(...args) {
140
+ this.textField.setSelectionRange(...args);
141
+ }
142
+
143
+ attributeChangedCallback(attrName, oldValue, newValue) {
144
+ super.attributeChangedCallback(attrName, oldValue, newValue);
145
+
146
+ if (oldValue !== newValue && observedAttributes.includes(attrName)) {
147
+ const attr = mapAttrs[attrName] || attrName;
148
+ this.textField.setAttribute(attr, newValue);
110
149
  }
150
+ }
151
+
152
+ #onInput(e) {
153
+ let sanitizedInput = this.#sanitizeInput(e.target.value);
111
154
 
112
- if (this.value && !validPhonePattern.test(this.value)) {
113
- return { patternMismatch: true };
155
+ if (this.isFormatValue && this.#canFormat(sanitizedInput)) {
156
+ sanitizedInput = this.#formatPhoneNumber(sanitizedInput);
114
157
  }
115
158
 
116
- return {};
159
+ e.target.value = sanitizedInput;
117
160
  }
118
161
 
119
- init() {
120
- this.addEventListener('focus', (e) => {
121
- // we want to ignore focus events we are dispatching
122
- if (e.isTrusted) this.phoneNumberInput.focus();
123
- });
162
+ #nonParsedValue() {
163
+ if (!this.defaultDialCode) {
164
+ return this.textField.value;
165
+ }
124
166
 
125
- super.init?.();
126
- this.initInputs();
167
+ const nationalNumber = this.#trimDuplicateCountryCode(this.textField.value);
168
+ const sanitizedVal = this.#sanitizeVal(nationalNumber);
169
+
170
+ return [this.defaultDialCode, sanitizedVal].join('-');
171
+ }
172
+
173
+ #parseWithCountryCode() {
174
+ if (this.defaultDialCode) {
175
+ return parsePhoneNumberFromString(
176
+ [this.defaultDialCode, this.#sanitizeVal(this.textField.value)].filter(Boolean).join('')
177
+ );
178
+ }
179
+
180
+ // if default-code or not parsed - try to extract country code from value
181
+ return parsePhoneNumberFromString(this.textField.value);
182
+ }
183
+
184
+ #sanitizeVal(val) {
185
+ return val.replace(/\D/g, '');
127
186
  }
128
187
 
129
- getCountryByDialCode(countryDialCode) {
130
- return this.countryCodeInput.items?.find(
131
- (c) => c.getAttribute('data-country-code') === countryDialCode
188
+ #trimDuplicateCountryCode(val) {
189
+ if (this.textField.value?.[0] === '+') {
190
+ const dialCodePrefixPattern = new RegExp(`^\\${this.defaultDialCode}`);
191
+ const trimmed = val.replace(dialCodePrefixPattern, '');
192
+ return trimmed;
193
+ }
194
+ return val;
195
+ }
196
+
197
+ #isValidParsedValue() {
198
+ const parsed = parsePhoneNumberFromString(this.value);
199
+ return (
200
+ parsed && // parsed successfully (not undefined)
201
+ parsed.isValid?.() && // Parsed object is valid
202
+ parsed.country && // Parsed object with a country code
203
+ this.#isAllowedCountry(parsed.country) && // Parsed with allowed country code
204
+ (this.defaultCode ? this.defaultCode === parsed.country : true) // In case default country code is set validate parsed country matches it
132
205
  );
133
206
  }
134
207
 
135
- initInputs() {
136
- // Sanitize phone input value to filter everything but digits
137
- this.phoneNumberInput.addEventListener('input', (e) => {
138
- if (e.target.value.length === 1 && e.target.value === '-') {
139
- e.target.value = '';
140
- }
208
+ #isAllowedCountry(countryCode) {
209
+ if (!this.restrictCountries) {
210
+ return true;
211
+ }
141
212
 
142
- e.target.value = e.target.value
143
- .replace(/(?!^)\+/g, '')
144
- .replace('--', '-')
145
- .replace('+-', '+');
146
-
147
- let sanitizedInput = e.target.value;
148
- if (!this.allowAlphanumericInput) {
149
- const telDigitsRegExp = /^[+\d-]+$/;
150
- sanitizedInput = e.target.value
151
- .split('')
152
- .filter((char) => telDigitsRegExp.test(char))
153
- .join('');
154
- }
213
+ return this.restrictCountries.includes(countryCode);
214
+ }
155
215
 
156
- e.target.value = sanitizedInput;
157
- });
216
+ #sanitizeInput(val) {
217
+ val = val
218
+ .replace(/^-+/, '') // dash as first char
219
+ .replace(/(?!^)\+/g, '') // multiple plus symbols
220
+ .replace('--', '-') // consecutive dashes
221
+ .replace('+-', '+'); // dash following plus symbol
222
+
223
+ if (!this.allowAlphanumericInput) {
224
+ const telDigitsRegExp = /^[+\d-\(\)]+$/;
225
+ val = val
226
+ .split('')
227
+ .filter((char) => telDigitsRegExp.test(char))
228
+ .join('');
229
+ }
158
230
 
159
- this.handleFocusEventsDispatching([this.phoneNumberInput]);
231
+ return val;
160
232
  }
161
233
 
162
- attributeChangedCallback(attrName, oldValue, newValue) {
163
- super.attributeChangedCallback(attrName, oldValue, newValue);
234
+ #formatPhoneNumber(phoneNumber = '') {
235
+ // Get country code from `default-code or` from phone number
236
+ const countryCode = this.defaultCode || this.#getCountryCodeFromValue(phoneNumber);
164
237
 
165
- if (oldValue !== newValue && observedAttributes.includes(attrName)) {
166
- const attr = mapAttrs[attrName] || attrName;
167
- this.phoneNumberInput.setAttribute(attr, newValue);
238
+ // Skip formatting if no country code is available
239
+ if (!countryCode) {
240
+ return phoneNumber;
241
+ }
242
+
243
+ // Update AsYouType country code if needed
244
+ if (!this.#ayt || this.#ayt.country !== countryCode) {
245
+ this.#ayt = new AsYouType(countryCode);
168
246
  }
247
+
248
+ // We need to reset AsYouType instance before setting new input
249
+ this.#ayt.reset();
250
+
251
+ // Set AsYouType input
252
+ const formattedVal = this.#ayt.input(phoneNumber) || phoneNumber;
253
+
254
+ return formattedVal;
255
+ }
256
+
257
+ #getCountryCodeFromValue(val) {
258
+ const parsed = parsePhoneNumberFromString(val);
259
+ return parsed?.country || '';
260
+ }
261
+
262
+ #canFormat(val) {
263
+ if (!matchingParenthesis(val)) return false;
264
+ return true;
169
265
  }
170
266
  }
171
267
 
@@ -1,5 +1,4 @@
1
1
  import './descope-phone-input-box-internal';
2
- import '@descope-ui/descope-combo-box';
3
2
  import '../../descope-text-field';
4
3
 
5
4
  import { componentName, PhoneFieldInputBoxClass } from './PhoneFieldInputBoxClass';
@@ -1,5 +1,12 @@
1
+ import parsePhoneNumberFromString from 'libphonenumber-js/min';
1
2
  import CountryCodes from './CountryCodes';
2
3
 
3
4
  export const getCountryByCodeId = (countryCode) => {
4
5
  return CountryCodes.find((c) => c.code === countryCode)?.dialCode;
5
6
  };
7
+
8
+ export const matchingParenthesis = (val) => {
9
+ const openParenMatches = val.match(/\(/g);
10
+ const closeParenMatches = val.match(/\)/g);
11
+ return openParenMatches?.length === closeParenMatches?.length;
12
+ };
@@ -11,3 +11,4 @@ export { normalizeBooleanAttributesMixin } from './normalizeBooleanAttributesMix
11
11
  export { lifecycleEventsMixin } from './lifecycleEventsMixin';
12
12
  export { inputEventsDispatchingMixin } from './inputEventsDispatchingMixin';
13
13
  export { externalInputMixin } from './externalInputMixin';
14
+ export { inputOverrideValidConstraintsMixin } from './inputOverrideValidConstraints';
@@ -0,0 +1,12 @@
1
+ export const inputOverrideValidConstraintsMixin = (superclass) =>
2
+ class InputOverrideValidConstraintsMixinClass extends superclass {
3
+ init() {
4
+ super.init?.();
5
+
6
+ // vaadin uses `validConstraints` (required, pattern, minlength, maxlength) to determine if it should validate
7
+ // the input or not. We want to override this behavior, so we can enforce validation even if these attributes are not present.
8
+ if (this.baseElement._hasValidConstraints) {
9
+ this.baseElement._hasValidConstraints = () => true;
10
+ }
11
+ }
12
+ };