@descope-ui/descope-combo-box 2.2.54 → 2.2.56

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [2.2.56](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.55...web-components-ui-2.2.56) (2026-02-24)
6
+
7
+ ## [2.2.55](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.54...web-components-ui-2.2.55) (2026-02-23)
8
+
9
+
10
+ ### Features
11
+
12
+ * combo-box `require-match` ([#885](https://github.com/descope/web-components-ui/issues/885)) ([3cbbb50](https://github.com/descope/web-components-ui/commit/3cbbb5089d9f4ff7c6e8b48005664fc26fdca157))
13
+
5
14
  ## [2.2.54](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.53...web-components-ui-2.2.54) (2026-02-23)
6
15
 
7
16
  ## [2.2.53](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.52...web-components-ui-2.2.53) (2026-02-23)
@@ -482,3 +482,91 @@ test.describe('custom error message', () => {
482
482
  });
483
483
  });
484
484
  });
485
+
486
+ test.describe('require-match', () => {
487
+ test('should validate pattern', async ({ page }) => {
488
+ await page.goto(
489
+ getStoryUrl(storyName, {
490
+ 'require-match': 'true',
491
+ }),
492
+ );
493
+ const componentLocator = page.locator(componentName);
494
+ const component = createComboBoxTestDriver(componentLocator);
495
+
496
+ await component.insertValue('xyz');
497
+ await component.blur();
498
+
499
+ const displayValue = await component.getDisplayValue();
500
+ expect(displayValue).toBe('xyz');
501
+
502
+ const actualValue = await componentLocator.evaluate(
503
+ (node: HTMLElement & { value: string }) => node.value,
504
+ );
505
+ expect(actualValue).toBe('');
506
+
507
+ await page.getByRole('button').getByText('Submit').click();
508
+
509
+ expect(await component.checkValidity()).toBe(false);
510
+
511
+ expect(
512
+ await component.screenshot({ delay: 1000, animations: 'disabled' }),
513
+ ).toMatchSnapshot();
514
+ });
515
+
516
+ test('should have custom error message for require match', async ({ page }) => {
517
+ await page.goto(
518
+ getStoryUrl(storyName, {
519
+ 'require-match': 'true',
520
+ 'data-errormessage-pattern-mismatch': 'Custom error message',
521
+ }),
522
+ );
523
+
524
+ const component = createComboBoxTestDriver(page.locator(componentName));
525
+
526
+ await page.getByRole('button').getByText('Submit').click();
527
+
528
+ expect(
529
+ await component.screenshot({ delay: 1000, animations: 'disabled' })
530
+ ).toMatchSnapshot();
531
+ });
532
+
533
+ test('should not clear filter on blur', async ({ page }) => {
534
+ await page.goto(
535
+ getStoryUrl(storyName, {
536
+ 'require-match': 'true',
537
+ 'data-pattern-mismatch-missing': 'Custom error message',
538
+ }),
539
+ );
540
+
541
+ const component = createComboBoxTestDriver(page.locator(componentName));
542
+
543
+ await component.insertValue('xyz');
544
+ await component.blur();
545
+
546
+ expect(
547
+ await component.screenshot({ delay: 1000, animations: 'disabled' })
548
+ ).toMatchSnapshot();
549
+ });
550
+
551
+ test('should not override allow-custom-value', async ({ page }) => {
552
+ await page.goto(
553
+ getStoryUrl(storyName, {
554
+ 'require-match': 'true',
555
+ 'allow-cuzstom-value': 'true',
556
+ 'data-pattern-mismatch-missing': 'Custom error message',
557
+ }),
558
+ );
559
+
560
+ const component = createComboBoxTestDriver(page.locator(componentName));
561
+
562
+ await component.insertValue('allowed custom value');
563
+ await component.blur();
564
+
565
+ await page.getByRole('button').getByText('Submit').click();
566
+
567
+ expect(
568
+ await component.screenshot({ delay: 1000, animations: 'disabled' })
569
+ ).toMatchSnapshot();
570
+ });
571
+ });
572
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope-ui/descope-combo-box",
3
- "version": "2.2.54",
3
+ "version": "2.2.56",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./src/component/index.js"
@@ -14,14 +14,14 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@playwright/test": "1.58.1",
17
- "e2e-utils": "2.2.54",
18
- "test-drivers": "2.2.54"
17
+ "e2e-utils": "2.2.56",
18
+ "test-drivers": "2.2.56"
19
19
  },
20
20
  "dependencies": {
21
21
  "@vaadin/combo-box": "24.3.4",
22
- "@descope-ui/common": "2.2.54",
23
- "@descope-ui/theme-globals": "2.2.54",
24
- "@descope-ui/theme-input-wrapper": "2.2.54"
22
+ "@descope-ui/common": "2.2.56",
23
+ "@descope-ui/theme-globals": "2.2.56",
24
+ "@descope-ui/theme-input-wrapper": "2.2.56"
25
25
  },
26
26
  "publishConfig": {
27
27
  "link-workspace-packages": false
@@ -17,6 +17,7 @@ import {
17
17
  draggableMixin,
18
18
  createProxy,
19
19
  componentNameValidationMixin,
20
+ inputOverrideValidConstraintsMixin,
20
21
  portalMixin,
21
22
  proxyInputMixin,
22
23
  } from '@descope-ui/common/components-mixins';
@@ -26,7 +27,7 @@ export const componentName = getComponentName('combo-box');
26
27
  const ComboBoxMixin = (superclass) =>
27
28
  class ComboBoxMixinClass extends superclass {
28
29
  static get observedAttributes() {
29
- return ['label-type'];
30
+ return ['label-type', 'require-match'];
30
31
  }
31
32
 
32
33
  // eslint-disable-next-line class-methods-use-this
@@ -98,6 +99,10 @@ const ComboBoxMixin = (superclass) =>
98
99
  }
99
100
  }
100
101
 
102
+ get requireMatch() {
103
+ return this.getAttribute('require-match') === 'true' && !this.allowCustomValue;
104
+ }
105
+
101
106
  // eslint-disable-next-line class-methods-use-this
102
107
  isValidDataType(data) {
103
108
  const isValid = Array.isArray(data);
@@ -250,16 +255,27 @@ const ComboBoxMixin = (superclass) =>
250
255
  }
251
256
  }
252
257
 
258
+ isValueMatch() {
259
+ return this.baseElement.items.some(val => val.getAttribute('data-id') === this.baseElement.querySelector('input').value);
260
+ }
261
+
253
262
  init() {
254
263
  super.init?.();
255
264
 
256
265
  // eslint-disable-next-line func-names
257
266
  this.getValidity = function () {
267
+ if (this.requireMatch && !this.isValueMatch()) {
268
+ return {
269
+ patternMismatch: true,
270
+ };
271
+ }
272
+
258
273
  if (!this.value && this.isRequired) {
259
274
  return {
260
275
  valueMissing: true,
261
276
  };
262
277
  }
278
+
263
279
  return {};
264
280
  };
265
281
 
@@ -311,6 +327,10 @@ const ComboBoxMixin = (superclass) =>
311
327
  }
312
328
  }
313
329
 
330
+ handleRequireMatchChange(shouldValidate) {
331
+ this.baseElement.allowCustomValue = shouldValidate || this.allowCustomValue;
332
+ }
333
+
314
334
  attributeChangedCallback(attrName, oldValue, newValue) {
315
335
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
316
336
 
@@ -321,6 +341,8 @@ const ComboBoxMixin = (superclass) =>
321
341
  } else {
322
342
  this.removeEventListener('click', this.onLabelClick);
323
343
  }
344
+ } else if (attrName === 'require-match') {
345
+ this.handleRequireMatchChange(newValue === 'true');
324
346
  }
325
347
  }
326
348
  }
@@ -618,6 +640,7 @@ export const ComboBoxClass = compose(
618
640
  proxyProps: ['selectionStart'],
619
641
  inputEvent: 'value-changed',
620
642
  }),
643
+ inputOverrideValidConstraintsMixin,
621
644
  componentNameValidationMixin,
622
645
  ComboBoxMixin,
623
646
  )(
@@ -41,9 +41,11 @@ const Template = ({
41
41
  loading,
42
42
  'default-value': defaultValue,
43
43
  'data-errormessage-value-missing': customErrorMissingValue,
44
+ 'data-errormessage-pattern-mismatch': customErrorPatternMismatch,
44
45
  'label-type': labelType,
45
46
  'hide-toggle-button': hideToggleButton,
46
47
  'allow-custom-value': allowCustomValue,
48
+ 'require-match': patternValidation,
47
49
  errorMsgIcon,
48
50
  }) => {
49
51
  let serializedData;
@@ -71,10 +73,12 @@ const Template = ({
71
73
  disabled="${disabled || false}"
72
74
  loading="${loading || false}"
73
75
  data-errormessage-value-missing="${customErrorMissingValue || ''}"
76
+ data-errormessage-pattern-mismatch="${customErrorPatternMismatch || ''}"
74
77
  st-host-direction="${direction ?? ''}"
75
78
  label-type="${labelType || ''}"
76
79
  hide-toggle-button="${hideToggleButton || false}"
77
80
  allow-custom-value="${allowCustomValue || false}"
81
+ require-match="${patternValidation || false}"
78
82
  ${errorMsgIcon ? errorMessageIconAttrs : ''}
79
83
  >
80
84
  <span data-name="ItemName1" data-id="itemId1">Trojan War Heroes' Valor</span>
@@ -163,6 +167,10 @@ export default {
163
167
  name: 'Override the default renderer function',
164
168
  control: { type: 'boolean' },
165
169
  },
170
+ 'require-match': {
171
+ name: 'Require Match',
172
+ control: { type: 'boolean' },
173
+ },
166
174
  },
167
175
  };
168
176
 
@@ -180,4 +188,5 @@ Default.args = {
180
188
  ],
181
189
  itemsSource: 'children',
182
190
  'data-errormessage-value-missing': 'Please fill out this field.',
191
+ 'data-errormessage-pattern-mismatch': ''
183
192
  };