@dile/ui 3.8.0 → 3.8.1

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.
@@ -183,4 +183,78 @@ describe('dile-input-mask', () => {
183
183
  expect(el.maskedValue).toBe('AB-cd-12');
184
184
  expect(el.value).toBe('ABcd12');
185
185
  });
186
+
187
+ it('supports X pattern for alphanumeric uppercase', async () => {
188
+ const el = await renderInputMask('<dile-input-mask name="test" mask="XX-0000"></dile-input-mask>');
189
+ const input = el.shadowRoot.querySelector('input');
190
+
191
+ // X accepts both letters and digits
192
+ const chars = ['a', 'B', '1', '2', '3', '4'];
193
+ chars.forEach((char) => {
194
+ input.value += char;
195
+ const inputEvent = new Event('input', { bubbles: true });
196
+ input.dispatchEvent(inputEvent);
197
+ });
198
+
199
+ await el.updateComplete;
200
+
201
+ // Letters should be uppercase, digits unchanged
202
+ expect(el.maskedValue).toBe('AB-1234');
203
+ expect(el.value).toBe('AB1234');
204
+ });
205
+
206
+ it('supports x pattern for alphanumeric lowercase', async () => {
207
+ const el = await renderInputMask('<dile-input-mask name="test" mask="xx-0000"></dile-input-mask>');
208
+ const input = el.shadowRoot.querySelector('input');
209
+
210
+ // x accepts both letters and digits
211
+ const chars = ['A', 'b', '1', '2', '3', '4'];
212
+ chars.forEach((char) => {
213
+ input.value += char;
214
+ const inputEvent = new Event('input', { bubbles: true });
215
+ input.dispatchEvent(inputEvent);
216
+ });
217
+
218
+ await el.updateComplete;
219
+
220
+ // Letters should be lowercase, digits unchanged
221
+ expect(el.maskedValue).toBe('ab-1234');
222
+ expect(el.value).toBe('ab1234');
223
+ });
224
+
225
+ it('applies lazy separators only when next block is filled', async () => {
226
+ const el = await renderInputMask('<dile-input-mask name="iban" mask="AA-XXXX-0000"></dile-input-mask>');
227
+ const input = el.shadowRoot.querySelector('input');
228
+
229
+ // Type 'ES' - should show 'ES' without trailing separator
230
+ input.value = 'ES';
231
+ input.dispatchEvent(new Event('input', { bubbles: true }));
232
+ await el.updateComplete;
233
+ expect(el.maskedValue).toBe('ES');
234
+
235
+ // Type 'ESW' - should show 'ES-W' (separator added when next block starts)
236
+ input.value = 'ESW';
237
+ input.dispatchEvent(new Event('input', { bubbles: true }));
238
+ await el.updateComplete;
239
+ expect(el.maskedValue).toBe('ES-W');
240
+
241
+ // Type 'ESWSWQ' - should show 'ES-WSWQ'
242
+ input.value = 'ESWSWQ';
243
+ input.dispatchEvent(new Event('input', { bubbles: true }));
244
+ await el.updateComplete;
245
+ expect(el.maskedValue).toBe('ES-WSWQ');
246
+
247
+ // Type 'ESWSWQ0' - should show 'ES-WSWQ-0' (separator added when digits block starts)
248
+ input.value = 'ESWSWQ0';
249
+ input.dispatchEvent(new Event('input', { bubbles: true }));
250
+ await el.updateComplete;
251
+ expect(el.maskedValue).toBe('ES-WSWQ-0');
252
+
253
+ // Type complete 'ESWSWQ0000' - should show 'ES-WSWQ-0000'
254
+ input.value = 'ESWSWQ0000';
255
+ input.dispatchEvent(new Event('input', { bubbles: true }));
256
+ await el.updateComplete;
257
+ expect(el.maskedValue).toBe('ES-WSWQ-0000');
258
+ });
186
259
  });
260
+
@@ -1,15 +1,17 @@
1
1
  /**
2
- * Generic mask pattern that supports letters (A/a), numbers (0), and literal characters.
2
+ * Generic mask pattern that supports letters (A/a/X/x), numbers (0), and literal characters.
3
3
  * Pattern definition:
4
- * 'A' = any letter (a-z, A-Z) - will be transformed to UPPERCASE
5
- * 'a' = any letter (a-z, A-Z) - will be transformed to lowercase
6
- * '0' = any digit (0-9)
7
- * other chars = fixed literal characters in the mask
4
+ * 'A' = uppercase letter only - will be transformed to UPPERCASE
5
+ * 'a' = lowercase letter only - will be transformed to lowercase
6
+ * 'X' = letter or digit - will be transformed to UPPERCASE if letter
7
+ * 'x' = letter or digit - will be transformed to lowercase if letter
8
+ * '0' = digit only (0-9)
9
+ * other chars = fixed literal characters in the mask (added only when next block is filled)
8
10
  *
9
11
  * Examples:
10
- * 'aa00 0000 0000 0000 0000' = IBAN format (2 lowercase letters, check digit, then numbers with spaces)
12
+ * 'aa00 0000 0000 0000 0000' = IBAN format (lowercase letters + digits with lazy spaces)
13
+ * 'AA-XXXX-0000-0000' = Mixed format with lazy separators
11
14
  * '(000) 000-0000' = US phone format
12
- * 'AA-00-AA' = Format with uppercase letters and digits
13
15
  */
14
16
  export class MaskPattern {
15
17
  constructor(pattern, language = 'en') {
@@ -27,6 +29,10 @@ export class MaskPattern {
27
29
  this.patternMap.push({ type: 'letterUpper', index: i });
28
30
  } else if (char === 'a') {
29
31
  this.patternMap.push({ type: 'letterLower', index: i });
32
+ } else if (char === 'X') {
33
+ this.patternMap.push({ type: 'alphanumUpper', index: i });
34
+ } else if (char === 'x') {
35
+ this.patternMap.push({ type: 'alphanumLower', index: i });
30
36
  } else if (char === '0') {
31
37
  this.patternMap.push({ type: 'digit', index: i });
32
38
  } else {
@@ -49,24 +55,29 @@ export class MaskPattern {
49
55
  }
50
56
 
51
57
  maskIt(value) {
52
- let valueIndex = 0;
53
58
  let masked = '';
54
-
59
+ let slotIndex = 0; // Position in input slots (non-literals only)
60
+
55
61
  for (let i = 0; i < this.patternMap.length; i++) {
56
62
  const patternInfo = this.patternMap[i];
57
-
63
+
58
64
  if (patternInfo.type === 'literal') {
59
- masked += patternInfo.char;
65
+ // Lazy literals: only add if there are more input characters to fill slots after this literal
66
+ if (slotIndex < value.length) {
67
+ masked += patternInfo.char;
68
+ }
60
69
  } else {
61
- if (valueIndex < value.length) {
62
- masked += value[valueIndex];
63
- valueIndex++;
70
+ // This is an input slot position
71
+ if (slotIndex < value.length) {
72
+ masked += value[slotIndex];
73
+ slotIndex++;
64
74
  } else {
75
+ // No more input characters, stop
65
76
  break;
66
77
  }
67
78
  }
68
79
  }
69
-
80
+
70
81
  return masked;
71
82
  }
72
83
 
@@ -91,6 +102,15 @@ export class MaskPattern {
91
102
  };
92
103
  }
93
104
  valueIndex++;
105
+ } else if (patternInfo.type === 'alphanumUpper' || patternInfo.type === 'alphanumLower') {
106
+ const char = value[valueIndex];
107
+ if (!/[a-zA-Z0-9]/.test(char)) {
108
+ return {
109
+ valid: false,
110
+ error: this.getErrorMessage('invalidAlphanumeric', valueIndex + 1, char),
111
+ };
112
+ }
113
+ valueIndex++;
94
114
  } else if (patternInfo.type === 'digit') {
95
115
  const char = value[valueIndex];
96
116
  if (!/[0-9]/.test(char)) {
@@ -113,11 +133,15 @@ export class MaskPattern {
113
133
  getFormatDescription() {
114
134
  const lettersUpper = this.patternMap.filter(p => p.type === 'letterUpper').length;
115
135
  const lettersLower = this.patternMap.filter(p => p.type === 'letterLower').length;
136
+ const alphanumUpper = this.patternMap.filter(p => p.type === 'alphanumUpper').length;
137
+ const alphanumLower = this.patternMap.filter(p => p.type === 'alphanumLower').length;
116
138
  const digits = this.patternMap.filter(p => p.type === 'digit').length;
117
139
 
118
140
  const parts = [];
119
141
  if (lettersUpper > 0) parts.push(`${lettersUpper} uppercase letter${lettersUpper !== 1 ? 's' : ''}`);
120
142
  if (lettersLower > 0) parts.push(`${lettersLower} lowercase letter${lettersLower !== 1 ? 's' : ''}`);
143
+ if (alphanumUpper > 0) parts.push(`${alphanumUpper} alphanumeric (uppercase if letter)${alphanumUpper !== 1 ? 's' : ''}`);
144
+ if (alphanumLower > 0) parts.push(`${alphanumLower} alphanumeric (lowercase if letter)${alphanumLower !== 1 ? 's' : ''}`);
121
145
  if (digits > 0) parts.push(`${digits} digit${digits !== 1 ? 's' : ''}`);
122
146
 
123
147
  return parts.join(' + ');
@@ -130,6 +154,8 @@ export class MaskPattern {
130
154
  /**
131
155
  * Transforms a clean value according to the pattern's case requirements
132
156
  * 'A' positions will be uppercase, 'a' positions will be lowercase
157
+ * 'X' positions: if letter, uppercase; if digit, unchanged
158
+ * 'x' positions: if letter, lowercase; if digit, unchanged
133
159
  * Returns only the transformed value (letters and digits), without literals
134
160
  * @param {string} cleanValue - The clean value (letters and digits only)
135
161
  * @returns {string} The transformed value with case applied (no literals)
@@ -151,6 +177,18 @@ export class MaskPattern {
151
177
  result += cleanValue[valueIndex].toLowerCase();
152
178
  valueIndex++;
153
179
  }
180
+ } else if (patternInfo.type === 'alphanumUpper') {
181
+ if (valueIndex < cleanValue.length) {
182
+ const char = cleanValue[valueIndex];
183
+ result += /[a-zA-Z]/.test(char) ? char.toUpperCase() : char;
184
+ valueIndex++;
185
+ }
186
+ } else if (patternInfo.type === 'alphanumLower') {
187
+ if (valueIndex < cleanValue.length) {
188
+ const char = cleanValue[valueIndex];
189
+ result += /[a-zA-Z]/.test(char) ? char.toLowerCase() : char;
190
+ valueIndex++;
191
+ }
154
192
  } else if (patternInfo.type === 'digit') {
155
193
  if (valueIndex < cleanValue.length) {
156
194
  result += cleanValue[valueIndex];
@@ -168,22 +206,27 @@ export class MaskPattern {
168
206
  en: {
169
207
  invalidLetter: `Position ${position}: expected a letter but got "${char}"`,
170
208
  invalidDigit: `Position ${position}: expected a digit but got "${char}"`,
209
+ invalidAlphanumeric: `Position ${position}: expected a letter or digit but got "${char}"`,
171
210
  },
172
211
  es: {
173
212
  invalidLetter: `Posición ${position}: se esperaba una letra pero recibió "${char}"`,
174
213
  invalidDigit: `Posición ${position}: se esperaba un dígito pero recibió "${char}"`,
214
+ invalidAlphanumeric: `Posición ${position}: se esperaba una letra o dígito pero recibió "${char}"`,
175
215
  },
176
216
  fr: {
177
217
  invalidLetter: `Position ${position} : lettre attendue mais reçu "${char}"`,
178
218
  invalidDigit: `Position ${position} : chiffre attendu mais reçu "${char}"`,
219
+ invalidAlphanumeric: `Position ${position} : lettre ou chiffre attendu mais reçu "${char}"`,
179
220
  },
180
221
  de: {
181
222
  invalidLetter: `Position ${position}: Buchstabe erwartet, aber "${char}" erhalten`,
182
223
  invalidDigit: `Position ${position}: Ziffer erwartet, aber "${char}" erhalten`,
224
+ invalidAlphanumeric: `Position ${position}: Buchstabe oder Ziffer erwartet, aber "${char}" erhalten`,
183
225
  },
184
226
  cat: {
185
227
  invalidLetter: `Posició ${position}: s'esperava una lletra però va rebre "${char}"`,
186
228
  invalidDigit: `Posició ${position}: s'esperava un dígit però va rebre "${char}"`,
229
+ invalidAlphanumeric: `Posició ${position}: s'esperava una lletra o dígit però va rebre "${char}"`,
187
230
  },
188
231
  };
189
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,5 +28,5 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "dd0e035ddb5e6e051ce36dbd2a365fec53371cef"
31
+ "gitHead": "b1a8398739fe3faf4c4c3100b068fefcfd9d9b3a"
32
32
  }