@lion/ui 0.1.5 → 0.2.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.
Files changed (153) hide show
  1. package/components/accordion/src/LionAccordion.js +2 -2
  2. package/components/accordion/test/lion-accordion.test.js +31 -0
  3. package/components/calendar/src/LionCalendar.js +13 -3
  4. package/components/combobox/src/LionCombobox.js +107 -14
  5. package/components/combobox/src/validators.js +18 -0
  6. package/components/combobox/test/lion-combobox.test.js +286 -184
  7. package/components/combobox/test/validators.test.js +62 -0
  8. package/components/combobox/test-helpers/combobox-helpers.js +125 -0
  9. package/components/form-core/types/choice-group/ChoiceGroupMixinTypes.ts +4 -3
  10. package/components/input-datepicker/src/LionInputDatepicker.js +9 -1
  11. package/components/input-iban/translations/de.js +1 -1
  12. package/components/listbox/src/ListboxMixin.js +6 -0
  13. package/components/listbox/test-suites/ListboxMixin.suite.js +15 -8
  14. package/components/overlays/src/OverlayController.js +27 -7
  15. package/components/overlays/test/OverlayController.test.js +16 -2
  16. package/components/switch/src/LionSwitch.js +0 -3
  17. package/components/switch/src/LionSwitchButton.js +14 -1
  18. package/components/switch/test/lion-switch-button.test.js +15 -1
  19. package/components/switch/test/lion-switch.test.js +21 -6
  20. package/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js +11 -8
  21. package/components/validate-messages/translations/bg.js +4 -0
  22. package/components/validate-messages/translations/cs.js +2 -0
  23. package/components/validate-messages/translations/de.js +4 -0
  24. package/components/validate-messages/translations/en.js +2 -0
  25. package/components/validate-messages/translations/es.js +4 -0
  26. package/components/validate-messages/translations/fr.js +4 -0
  27. package/components/validate-messages/translations/hu.js +2 -0
  28. package/components/validate-messages/translations/it.js +4 -0
  29. package/components/validate-messages/translations/nl.js +4 -0
  30. package/components/validate-messages/translations/pl.js +2 -0
  31. package/components/validate-messages/translations/ro.js +4 -0
  32. package/components/validate-messages/translations/ru.js +4 -0
  33. package/components/validate-messages/translations/sk.js +2 -0
  34. package/components/validate-messages/translations/uk.js +4 -0
  35. package/components/validate-messages/translations/zh.js +2 -0
  36. package/custom-elements.json +1365 -34
  37. package/dist-types/components/calendar/src/LionCalendar.d.ts +2 -0
  38. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  39. package/dist-types/components/combobox/src/LionCombobox.d.ts +37 -13
  40. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  41. package/dist-types/components/combobox/src/validators.d.ts +13 -0
  42. package/dist-types/components/combobox/src/validators.d.ts.map +1 -0
  43. package/dist-types/components/combobox/test/lion-combobox.test.d.ts +0 -2
  44. package/dist-types/components/combobox/test/lion-combobox.test.d.ts.map +1 -1
  45. package/dist-types/components/combobox/test/validators.test.d.ts +2 -0
  46. package/dist-types/components/combobox/test/validators.test.d.ts.map +1 -0
  47. package/dist-types/components/combobox/test-helpers/combobox-helpers.d.ts +41 -0
  48. package/dist-types/components/combobox/test-helpers/combobox-helpers.d.ts.map +1 -0
  49. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts +4 -3
  50. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts.map +1 -1
  51. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +4 -0
  52. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  53. package/dist-types/components/listbox/src/ListboxMixin.d.ts.map +1 -1
  54. package/dist-types/components/listbox/test-suites/ListboxMixin.suite.d.ts.map +1 -1
  55. package/dist-types/components/overlays/src/OverlayController.d.ts +2 -0
  56. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  57. package/dist-types/components/switch/src/LionSwitch.d.ts +0 -1
  58. package/dist-types/components/switch/src/LionSwitch.d.ts.map +1 -1
  59. package/dist-types/components/switch/src/LionSwitchButton.d.ts +3 -0
  60. package/dist-types/components/switch/src/LionSwitchButton.d.ts.map +1 -1
  61. package/dist-types/components/switch/test/lion-switch.test.d.ts +1 -1
  62. package/dist-types/components/switch/test/lion-switch.test.d.ts.map +1 -1
  63. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.d.ts.map +1 -1
  64. package/dist-types/components/validate-messages/translations/bg-BG.d.ts +2 -0
  65. package/dist-types/components/validate-messages/translations/bg.d.ts +3 -0
  66. package/dist-types/components/validate-messages/translations/cs-CZ.d.ts +2 -0
  67. package/dist-types/components/validate-messages/translations/cs.d.ts +3 -0
  68. package/dist-types/components/validate-messages/translations/de-DE.d.ts +2 -0
  69. package/dist-types/components/validate-messages/translations/de.d.ts +3 -0
  70. package/dist-types/components/validate-messages/translations/en-AU.d.ts +2 -0
  71. package/dist-types/components/validate-messages/translations/en-GB.d.ts +2 -0
  72. package/dist-types/components/validate-messages/translations/en-PH.d.ts +2 -0
  73. package/dist-types/components/validate-messages/translations/en-US.d.ts +2 -0
  74. package/dist-types/components/validate-messages/translations/en.d.ts +3 -0
  75. package/dist-types/components/validate-messages/translations/es-ES.d.ts +2 -0
  76. package/dist-types/components/validate-messages/translations/es.d.ts +3 -0
  77. package/dist-types/components/validate-messages/translations/fr-BE.d.ts +2 -0
  78. package/dist-types/components/validate-messages/translations/fr-FR.d.ts +2 -0
  79. package/dist-types/components/validate-messages/translations/fr.d.ts +3 -0
  80. package/dist-types/components/validate-messages/translations/hu-HU.d.ts +2 -0
  81. package/dist-types/components/validate-messages/translations/hu.d.ts +3 -0
  82. package/dist-types/components/validate-messages/translations/it-IT.d.ts +2 -0
  83. package/dist-types/components/validate-messages/translations/it.d.ts +3 -0
  84. package/dist-types/components/validate-messages/translations/nl-BE.d.ts +2 -0
  85. package/dist-types/components/validate-messages/translations/nl-NL.d.ts +2 -0
  86. package/dist-types/components/validate-messages/translations/nl.d.ts +3 -0
  87. package/dist-types/components/validate-messages/translations/pl-PL.d.ts +2 -0
  88. package/dist-types/components/validate-messages/translations/pl.d.ts +3 -0
  89. package/dist-types/components/validate-messages/translations/ro-RO.d.ts +2 -0
  90. package/dist-types/components/validate-messages/translations/ro.d.ts +3 -0
  91. package/dist-types/components/validate-messages/translations/ru-RU.d.ts +2 -0
  92. package/dist-types/components/validate-messages/translations/ru.d.ts +3 -0
  93. package/dist-types/components/validate-messages/translations/sk-SK.d.ts +2 -0
  94. package/dist-types/components/validate-messages/translations/sk.d.ts +3 -0
  95. package/dist-types/components/validate-messages/translations/uk-UA.d.ts +2 -0
  96. package/dist-types/components/validate-messages/translations/uk.d.ts +3 -0
  97. package/dist-types/components/validate-messages/translations/zh.d.ts +3 -0
  98. package/dist-types/exports/combobox-test-helpers.d.ts +2 -0
  99. package/dist-types/exports/combobox-test-helpers.d.ts.map +1 -0
  100. package/dist-types/exports/combobox.d.ts +1 -0
  101. package/dist-types/tsconfig.tsbuildinfo +1 -1
  102. package/docs/components/checkbox-group/overview.md +2 -2
  103. package/docs/components/checkbox-group/use-cases.md +1 -0
  104. package/docs/components/collapsible/overview.md +1 -1
  105. package/docs/components/combobox/overview.md +12 -2
  106. package/docs/components/combobox/use-cases.md +36 -1
  107. package/docs/components/dialog/overview.md +1 -1
  108. package/docs/components/fieldset/overview.md +5 -5
  109. package/docs/components/form/overview.md +3 -3
  110. package/docs/components/icon/use-cases.md +1 -1
  111. package/docs/components/input/overview.md +1 -1
  112. package/docs/components/input-amount/overview.md +3 -3
  113. package/docs/components/input-date/overview.md +3 -3
  114. package/docs/components/input-datepicker/overview.md +5 -5
  115. package/docs/components/input-email/overview.md +2 -2
  116. package/docs/components/input-iban/overview.md +2 -2
  117. package/docs/components/input-range/overview.md +2 -2
  118. package/docs/components/input-stepper/overview.md +1 -1
  119. package/docs/components/input-tel/overview.md +1 -1
  120. package/docs/components/input-tel/use-cases.md +6 -6
  121. package/docs/components/input-tel-dropdown/overview.md +1 -1
  122. package/docs/components/radio-group/overview.md +3 -3
  123. package/docs/components/select-rich/overview.md +7 -10
  124. package/docs/components/select-rich/use-cases.md +54 -69
  125. package/docs/components/tabs/examples.md +1 -1
  126. package/docs/fundamentals/index.md +4 -4
  127. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  128. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  129. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  130. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  131. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  132. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  133. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  134. package/docs/fundamentals/systems/core/overview.md +1 -1
  135. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  136. package/docs/fundamentals/systems/form/model-value.md +1 -1
  137. package/docs/fundamentals/systems/form/overview.md +28 -28
  138. package/docs/fundamentals/systems/form/validate.md +1 -1
  139. package/docs/fundamentals/systems/localize/overview.md +4 -4
  140. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  141. package/docs/fundamentals/systems/overlays/overview.md +5 -10
  142. package/docs/fundamentals/systems/overlays/rationale.md +3 -6
  143. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  144. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  145. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  146. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  147. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  148. package/docs/guides/index.md +8 -8
  149. package/docs/guides/principles/index.md +5 -5
  150. package/docs/guides/principles/styling.md +1 -1
  151. package/exports/combobox-test-helpers.js +1 -0
  152. package/exports/combobox.js +1 -0
  153. package/package.json +1 -1
@@ -1,141 +1,25 @@
1
- import '@lion/ui/define/lion-combobox.js';
2
- import { LitElement } from 'lit';
1
+ import {
2
+ getComboboxMembers,
3
+ getFilteredOptionValues,
4
+ mimicKeyPress,
5
+ mimicUserTyping,
6
+ mimicUserTypingAdvanced,
7
+ } from '@lion/ui/combobox-test-helpers.js';
8
+ import { LionCombobox } from '@lion/ui/combobox.js';
3
9
  import { browserDetection } from '@lion/ui/core.js';
4
- import { Required } from '@lion/ui/form-core.js';
10
+ import '@lion/ui/define/lion-combobox.js';
5
11
  import '@lion/ui/define/lion-listbox.js';
6
12
  import '@lion/ui/define/lion-option.js';
7
- import { getListboxMembers } from '@lion/ui/listbox-test-helpers.js';
13
+ import { Required, Unparseable } from '@lion/ui/form-core.js';
8
14
  import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
15
+ import { LitElement } from 'lit';
9
16
  import sinon from 'sinon';
10
- import { LionCombobox } from '@lion/ui/combobox.js';
11
17
 
12
18
  /**
13
19
  * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay
14
- * @typedef {import('../../listbox/types/ListboxMixinTypes.js').ListboxHost} ListboxHost
15
- * @typedef {import('../../form-core/types/FormControlMixinTypes.js').FormControlHost} FormControlHost
16
20
  * @typedef {import('@lion/ui/listbox.js').LionOption} LionOption
17
21
  */
18
22
 
19
- /**
20
- * @param { LionCombobox } el
21
- */
22
- function getComboboxMembers(el) {
23
- const obj = getListboxMembers(el);
24
- return {
25
- ...obj,
26
- ...{
27
- // @ts-ignore [allow-protected] in test
28
- _invokerNode: el._invokerNode,
29
- // @ts-ignore [allow-protected] in test
30
- _overlayCtrl: el._overlayCtrl,
31
- // @ts-ignore [allow-protected] in test
32
- _comboboxNode: el._comboboxNode,
33
- // @ts-ignore [allow-protected] in test
34
- _inputNode: el._inputNode,
35
- // @ts-ignore [allow-protected] in test
36
- _listboxNode: el._listboxNode,
37
- // @ts-ignore [allow-protected] in test
38
- _selectionDisplayNode: el._selectionDisplayNode,
39
- // @ts-ignore [allow-protected] in test
40
- _activeDescendantOwnerNode: el._activeDescendantOwnerNode,
41
- // @ts-ignore [allow-protected] in test
42
- _ariaVersion: el._ariaVersion,
43
- },
44
- };
45
- }
46
-
47
- /**
48
- * @param {LionCombobox} el
49
- * @param {string} value
50
- */
51
- // TODO: add keys that actually make sense...
52
- function mimicUserTyping(el, value) {
53
- const { _inputNode } = getComboboxMembers(el);
54
- _inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
55
- // eslint-disable-next-line no-param-reassign
56
- _inputNode.value = value;
57
- _inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
58
- _inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value }));
59
- _inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value }));
60
- }
61
-
62
- /**
63
- * @param {HTMLElement} el
64
- * @param {string} key
65
- */
66
- function mimicKeyPress(el, key) {
67
- el.dispatchEvent(new KeyboardEvent('keydown', { key }));
68
- el.dispatchEvent(new KeyboardEvent('keyup', { key }));
69
- }
70
-
71
- /**
72
- * @param {LionCombobox} el
73
- * @param {string[]} values
74
- */
75
- async function mimicUserTypingAdvanced(el, values) {
76
- const { _inputNode } = getComboboxMembers(el);
77
- _inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
78
-
79
- for (const key of values) {
80
- // eslint-disable-next-line no-await-in-loop, no-loop-func
81
- await new Promise(resolve => {
82
- const hasSelection = _inputNode.selectionStart !== _inputNode.selectionEnd;
83
-
84
- if (key === 'Backspace') {
85
- if (hasSelection) {
86
- _inputNode.value =
87
- _inputNode.value.slice(
88
- 0,
89
- _inputNode.selectionStart ? _inputNode.selectionStart : undefined,
90
- ) +
91
- _inputNode.value.slice(
92
- _inputNode.selectionEnd ? _inputNode.selectionEnd : undefined,
93
- _inputNode.value.length,
94
- );
95
- } else {
96
- _inputNode.value = _inputNode.value.slice(0, -1);
97
- }
98
- } else if (hasSelection) {
99
- _inputNode.value =
100
- _inputNode.value.slice(
101
- 0,
102
- _inputNode.selectionStart ? _inputNode.selectionStart : undefined,
103
- ) +
104
- key +
105
- _inputNode.value.slice(
106
- _inputNode.selectionEnd ? _inputNode.selectionEnd : undefined,
107
- _inputNode.value.length,
108
- );
109
- } else {
110
- _inputNode.value += key;
111
- }
112
-
113
- mimicKeyPress(_inputNode, key);
114
- _inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
115
-
116
- el.updateComplete.then(() => {
117
- // @ts-ignore
118
- resolve();
119
- });
120
- });
121
- }
122
- }
123
-
124
- /**
125
- * @param {LionCombobox} el
126
- */
127
- function getFilteredOptionValues(el) {
128
- const options = el.formElements;
129
- /**
130
- * @param {{ style: { display: string; }; }} option
131
- */
132
- const filtered = options.filter(option => option.getAttribute('aria-hidden') !== 'true');
133
- /**
134
- * @param {{ value: any; }} option
135
- */
136
- return filtered.map(option => option.value);
137
- }
138
-
139
23
  /**
140
24
  * @param {{ autocomplete?:'none'|'list'|'both', matchMode?:'begin'|'all' }} config
141
25
  */
@@ -387,6 +271,22 @@ describe('lion-combobox', () => {
387
271
  expect(_comboboxNode).to.exist;
388
272
  expect(el.querySelector('[role=combobox]')).to.equal(_comboboxNode);
389
273
  });
274
+
275
+ it('has validator "MatchesOption" applied by default', async () => {
276
+ const el = /** @type {LionCombobox} */ (
277
+ await fixture(html`
278
+ <lion-combobox name="foo">
279
+ <lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
280
+ <lion-option .choiceValue="${'20'}">Item 2</lion-option>
281
+ </lion-combobox>
282
+ `)
283
+ );
284
+ mimicUserTyping(el, '30');
285
+ await el.updateComplete;
286
+ expect(el.hasFeedbackFor).to.include('error');
287
+ expect(el.validationStates).to.have.property('error');
288
+ expect(el.validationStates.error).to.have.property('MatchesOption');
289
+ });
390
290
  });
391
291
 
392
292
  describe('Values', () => {
@@ -408,7 +308,7 @@ describe('lion-combobox', () => {
408
308
  expect(_inputNode.value).to.equal('20');
409
309
  });
410
310
 
411
- it('sets modelValue to empty string if no option is selected', async () => {
311
+ it('sets modelValue to Unparseable if no option is selected', async () => {
412
312
  const el = /** @type {LionCombobox} */ (
413
313
  await fixture(html`
414
314
  <lion-combobox name="foo" .modelValue="${'Artichoke'}">
@@ -419,12 +319,59 @@ describe('lion-combobox', () => {
419
319
  </lion-combobox>
420
320
  `)
421
321
  );
322
+ const { _inputNode } = getComboboxMembers(el);
422
323
 
423
324
  expect(el.modelValue).to.equal('Artichoke');
424
325
  expect(el.formElements[0].checked).to.be.true;
425
- el.checkedIndex = -1;
326
+ el.setCheckedIndex(-1);
327
+ el.__shouldAutocompleteNextUpdate = true;
426
328
  await el.updateComplete;
427
- expect(el.modelValue).to.equal('');
329
+ expect(el.modelValue instanceof Unparseable).to.be.true;
330
+ expect(el.modelValue.viewValue).to.equal('Artichoke');
331
+ expect(el.formElements[0].checked).to.be.false;
332
+
333
+ el.setCheckedIndex(-1);
334
+ _inputNode.value = 'Foo';
335
+ el.__shouldAutocompleteNextUpdate = true;
336
+ await el.updateComplete;
337
+ expect(el.modelValue instanceof Unparseable).to.be.true;
338
+ expect(el.modelValue.viewValue).to.equal('Foo');
339
+ expect(el.formElements[0].checked).to.be.false;
340
+
341
+ el.setCheckedIndex(0);
342
+ el.__shouldAutocompleteNextUpdate = true;
343
+ await el.updateComplete;
344
+ expect(el.modelValue instanceof Unparseable).to.be.false;
345
+ expect(el.modelValue).to.equal('Artichoke');
346
+ expect(el.formElements[0].checked).to.be.true;
347
+ });
348
+
349
+ it('sets modelValue to _inputNode.value if no option is selected when requireOptionMatch is false', async () => {
350
+ const el = /** @type {LionCombobox} */ (
351
+ await fixture(html`
352
+ <lion-combobox name="foo" .modelValue="${'Artichoke'}" .requireOptionMatch="${false}">
353
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
354
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
355
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
356
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
357
+ </lion-combobox>
358
+ `)
359
+ );
360
+ const { _inputNode } = getComboboxMembers(el);
361
+
362
+ expect(el.modelValue).to.equal('Artichoke');
363
+ expect(el.formElements[0].checked).to.be.true;
364
+ el.setCheckedIndex(-1);
365
+ el.__shouldAutocompleteNextUpdate = true;
366
+ await el.updateComplete;
367
+ expect(el.modelValue).to.equal('Artichoke');
368
+ expect(el.formElements[0].checked).to.be.true;
369
+
370
+ el.setCheckedIndex(-1);
371
+ _inputNode.value = 'Foo';
372
+ el.__shouldAutocompleteNextUpdate = true;
373
+ await el.updateComplete;
374
+ expect(el.modelValue).to.equal('Foo');
428
375
  expect(el.formElements[0].checked).to.be.false;
429
376
  });
430
377
 
@@ -439,15 +386,37 @@ describe('lion-combobox', () => {
439
386
  </lion-combobox>
440
387
  `)
441
388
  );
442
-
443
- expect(el.modelValue).to.eql(['Artichoke']);
389
+ expect(el.modelValue).to.deep.equal(['Artichoke']);
444
390
  expect(el.formElements[0].checked).to.be.true;
445
- el.checkedIndex = [];
391
+ el.setCheckedIndex([]);
446
392
  await el.updateComplete;
447
- expect(el.modelValue).to.eql([]);
393
+ expect(el.modelValue).to.deep.equal([]);
448
394
  expect(el.formElements[0].checked).to.be.false;
449
395
  });
450
396
 
397
+ it('multiple choice and requireOptionMatch is false do not work together yet', async () => {
398
+ const errorMessage = `multipleChoice and requireOptionMatch=false can't be used at the same time (yet).`;
399
+ let error;
400
+ const el = /** @type {LionCombobox} */ (
401
+ await fixture(html`
402
+ <lion-combobox name="foo" multiple-choice>
403
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
404
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
405
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
406
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
407
+ </lion-combobox>
408
+ `)
409
+ );
410
+ try {
411
+ el.requireOptionMatch = false;
412
+ await el.updateComplete;
413
+ } catch (err) {
414
+ error = err;
415
+ }
416
+ expect(error).to.be.instanceOf(Error);
417
+ expect(/** @type {Error} */ (error).message).to.equal(errorMessage);
418
+ });
419
+
451
420
  it('clears modelValue and textbox value on clear()', async () => {
452
421
  const el = /** @type {LionCombobox} */ (
453
422
  await fixture(html`
@@ -479,7 +448,67 @@ describe('lion-combobox', () => {
479
448
 
480
449
  el2.clear();
481
450
  expect(el2.modelValue).to.eql([]);
482
- expect(_inputNode.value).to.equal('');
451
+ // @ts-ignore [allow-protected] in test
452
+ expect(el2._inputNode.value).to.equal('');
453
+ });
454
+
455
+ it('updates option list after clear()', async () => {
456
+ const el = /** @type {LionCombobox} */ (
457
+ await fixture(html`
458
+ <lion-combobox name="foo" multiple-choice>
459
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
460
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
461
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
462
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
463
+ </lion-combobox>
464
+ `)
465
+ );
466
+ const options = el.formElements;
467
+ mimicUserTyping(el, 'a');
468
+ await el.updateComplete;
469
+
470
+ const visibleOptions = options.filter(o => o.style.display !== 'none');
471
+ expect(visibleOptions.length).to.equal(3, 'after input');
472
+
473
+ el.clear();
474
+ await el.updateComplete;
475
+ const visibleOptions2 = options.filter(o => o.style.display !== 'none');
476
+ expect(visibleOptions2.length).to.equal(0, 'after clear');
477
+ });
478
+
479
+ it('resets modelValue and textbox value on reset()', async () => {
480
+ const el = /** @type {LionCombobox} */ (
481
+ await fixture(html`
482
+ <lion-combobox name="foo" .modelValue="${'Artichoke'}">
483
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
484
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
485
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
486
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
487
+ </lion-combobox>
488
+ `)
489
+ );
490
+
491
+ const { _inputNode } = getComboboxMembers(el);
492
+ el.modelValue = 'Chard';
493
+ el.reset();
494
+ expect(el.modelValue).to.equal('Artichoke');
495
+ expect(_inputNode.value).to.equal('Artichoke');
496
+
497
+ const el2 = /** @type {LionCombobox} */ (
498
+ await fixture(html`
499
+ <lion-combobox name="foo" multiple-choice .modelValue="${['Artichoke']}">
500
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
501
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
502
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
503
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
504
+ </lion-combobox>
505
+ `)
506
+ );
507
+ el2.modelValue = ['Artichoke', 'Chard'];
508
+ el2.reset();
509
+ expect(el2.modelValue).to.deep.equal(['Artichoke']);
510
+ // @ts-ignore [allow-protected] in test
511
+ expect(el2._inputNode.value).to.equal('Artichoke');
483
512
  });
484
513
 
485
514
  it('syncs textbox to modelValue', async () => {
@@ -493,27 +522,123 @@ describe('lion-combobox', () => {
493
522
  );
494
523
  const { _inputNode } = getComboboxMembers(el);
495
524
 
496
- async function performChecks() {
525
+ /** @param {string} autocompleteMode */
526
+ async function performChecks(autocompleteMode) {
497
527
  el.formElements[0].click();
498
528
  await el.updateComplete;
499
529
 
500
530
  // FIXME: fix properly for Webkit
501
- // expect(_inputNode.value).to.equal('Aha');
502
- expect(el.checkedIndex).to.equal(0);
531
+ // expect(_inputNode.value).to.equal('Aha', `autocomplete mode ${autocompleteMode}`);
532
+ expect(el.checkedIndex).to.equal(0, `autocomplete mode ${autocompleteMode}`);
503
533
 
504
534
  mimicUserTyping(el, 'Ah');
505
535
  await el.updateComplete;
506
- expect(_inputNode.value).to.equal('Ah');
536
+ expect(_inputNode.value).to.equal('Ah', `autocomplete mode ${autocompleteMode}`);
507
537
 
508
538
  await el.updateComplete;
509
- expect(el.checkedIndex).to.equal(-1);
539
+ expect(el.checkedIndex).to.equal(-1, `autocomplete mode ${autocompleteMode}`);
510
540
  }
511
541
 
512
542
  el.autocomplete = 'none';
513
- await performChecks();
543
+ await performChecks('none');
514
544
 
515
545
  el.autocomplete = 'list';
516
- await performChecks();
546
+ await performChecks('list');
547
+ });
548
+
549
+ it('works with Required validator', async () => {
550
+ const el = /** @type {LionCombobox} */ (
551
+ await fixture(html`
552
+ <lion-combobox name="foo" .validators=${[new Required()]}>
553
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
554
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
555
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
556
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
557
+ </lion-combobox>
558
+ `)
559
+ );
560
+ el.submitted = true;
561
+ await el.feedbackComplete;
562
+ expect(el.hasFeedbackFor).to.include('error', 'hasFeedbackFor');
563
+ await el.feedbackComplete;
564
+ expect(el.showsFeedbackFor).to.include('error', 'showsFeedbackFor');
565
+ });
566
+
567
+ it('allows a value outside of the option list when requireOptionMatch is false', async () => {
568
+ const el = /** @type {LionCombobox} */ (
569
+ await fixture(html`
570
+ <lion-combobox name="foo" .validators=${[new Required()]}>
571
+ <lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
572
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
573
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
574
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
575
+ </lion-combobox>
576
+ `)
577
+ );
578
+ el.requireOptionMatch = false;
579
+ const { _inputNode } = getComboboxMembers(el);
580
+ expect(el.checkedIndex).to.equal(0);
581
+
582
+ mimicUserTyping(el, 'Foo');
583
+ _inputNode.dispatchEvent(new Event('input'));
584
+ await el.updateComplete;
585
+
586
+ expect(el.checkedIndex).to.equal(-1);
587
+ expect(el.modelValue).to.equal('Foo');
588
+ expect(_inputNode.value).to.equal('Foo');
589
+ });
590
+
591
+ it("when removing a letter it won't select the option", async () => {
592
+ // We don't autocomplete when characters are removed
593
+ const el = /** @type {LionCombobox} */ (
594
+ await fixture(html`
595
+ <lion-combobox name="foo">
596
+ <lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
597
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
598
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
599
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
600
+ </lion-combobox>
601
+ `)
602
+ );
603
+ const { _inputNode } = getComboboxMembers(el);
604
+ expect(el.checkedIndex).to.equal(0);
605
+
606
+ // Simulate backspace deleting the char at the end of the string
607
+ mimicKeyPress(_inputNode, 'Backspace');
608
+ _inputNode.dispatchEvent(new Event('input'));
609
+ const arr = _inputNode.value.split('');
610
+ arr.splice(_inputNode.value.length - 1, 1);
611
+ _inputNode.value = arr.join('');
612
+ await el.updateComplete;
613
+ el.dispatchEvent(new Event('blur'));
614
+
615
+ expect(el.checkedIndex).to.equal(-1);
616
+ expect(el.modelValue instanceof Unparseable).to.be.true;
617
+ expect(el.modelValue.viewValue).to.equal('Artichok');
618
+ });
619
+
620
+ it('allows the user to override the parser', async () => {
621
+ const el = /** @type {LionCombobox} */ (
622
+ await fixture(html`
623
+ <lion-combobox
624
+ name="foo"
625
+ .parser="${/** @param {string} value */ value => value.replace(/[0-9]/g, '')}"
626
+ >
627
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
628
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
629
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
630
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
631
+ </lion-combobox>
632
+ `)
633
+ );
634
+ const { _inputNode } = getComboboxMembers(el);
635
+
636
+ el.setCheckedIndex(-1);
637
+ _inputNode.value = 'Foo123';
638
+ el.__shouldAutocompleteNextUpdate = true;
639
+ await el.updateComplete;
640
+ expect(el.modelValue).to.equal('Foo');
641
+ expect(el.formElements[0].checked).to.be.false;
517
642
  });
518
643
  });
519
644
 
@@ -822,6 +947,7 @@ describe('lion-combobox', () => {
822
947
  expect(el.opened).to.equal(true);
823
948
 
824
949
  const visibleOptions = options.filter(o => o.style.display !== 'none');
950
+ expect(visibleOptions.length).to.not.equal(0);
825
951
  visibleOptions.forEach((o, i) => {
826
952
  expect(o.getAttribute('aria-posinset')).to.equal(`${i + 1}`);
827
953
  expect(o.getAttribute('aria-setsize')).to.equal(`${visibleOptions.length}`);
@@ -874,36 +1000,6 @@ describe('lion-combobox', () => {
874
1000
  });
875
1001
  });
876
1002
 
877
- it('works with validation', async () => {
878
- const el = /** @type {LionCombobox} */ (
879
- await fixture(html`
880
- <lion-combobox name="foo" .validators=${[new Required()]}>
881
- <lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
882
- <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
883
- <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
884
- <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
885
- </lion-combobox>
886
- `)
887
- );
888
- const { _inputNode } = getComboboxMembers(el);
889
- expect(el.checkedIndex).to.equal(0);
890
-
891
- // Simulate backspace deleting the char at the end of the string
892
- mimicKeyPress(_inputNode, 'Backspace');
893
- _inputNode.dispatchEvent(new Event('input'));
894
- const arr = _inputNode.value.split('');
895
- arr.splice(_inputNode.value.length - 1, 1);
896
- _inputNode.value = arr.join('');
897
- await el.updateComplete;
898
- el.dispatchEvent(new Event('blur'));
899
-
900
- expect(el.checkedIndex).to.equal(-1);
901
- await el.feedbackComplete;
902
- expect(el.hasFeedbackFor).to.include('error', 'hasFeedbackFor');
903
- await el.feedbackComplete;
904
- expect(el.showsFeedbackFor).to.include('error', 'showsFeedbackFor');
905
- });
906
-
907
1003
  it('dropdown has a label', async () => {
908
1004
  const el = /** @type {LionCombobox} */ (
909
1005
  await fixture(html`
@@ -1656,7 +1752,7 @@ describe('lion-combobox', () => {
1656
1752
 
1657
1753
  const el = /** @type {LionCombobox} */ (
1658
1754
  await fixture(html`
1659
- <${tag} name="foo" autocomplete="list" opened>
1755
+ <${tag} name="foo" opened>
1660
1756
  <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
1661
1757
  <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
1662
1758
  <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
@@ -1948,7 +2044,10 @@ describe('lion-combobox', () => {
1948
2044
  */
1949
2045
  // eslint-disable-next-line class-methods-use-this
1950
2046
  _getTextboxValueFromOption(option) {
1951
- return option.label;
2047
+ if (option && option.label) {
2048
+ return option.label;
2049
+ }
2050
+ return this.modelValue;
1952
2051
  }
1953
2052
  }
1954
2053
  const tagName = defineCE(X);
@@ -1995,7 +2094,10 @@ describe('lion-combobox', () => {
1995
2094
  */
1996
2095
  // eslint-disable-next-line class-methods-use-this
1997
2096
  _getTextboxValueFromOption(option) {
1998
- return option.label;
2097
+ if (option && option.label) {
2098
+ return option.label;
2099
+ }
2100
+ return this.modelValue;
1999
2101
  }
2000
2102
  }
2001
2103
  const tagName = defineCE(X);
@@ -2068,29 +2170,29 @@ describe('lion-combobox', () => {
2068
2170
  );
2069
2171
  const { _inputNode } = getComboboxMembers(el);
2070
2172
 
2071
- async function performChecks() {
2173
+ /** @param {string} autocompleteMode */
2174
+ async function performChecks(autocompleteMode) {
2072
2175
  await el.updateComplete;
2073
2176
 
2074
2177
  el.formElements[0].click();
2075
- await el.updateComplete;
2076
2178
 
2077
2179
  // FIXME: fix properly for Webkit
2078
- // expect(_inputNode.value).to.equal('Aha');
2079
- expect(el.checkedIndex).to.equal(0);
2180
+ // expect(_inputNode.value).to.equal('Aha', autocompleteMode);
2181
+ expect(el.checkedIndex).to.equal(0, autocompleteMode);
2080
2182
 
2081
2183
  mimicUserTyping(el, 'Arti');
2082
2184
  await el.updateComplete;
2083
- expect(_inputNode.value).to.equal('Arti');
2185
+ expect(_inputNode.value).to.equal('Arti', autocompleteMode);
2084
2186
 
2085
2187
  await el.updateComplete;
2086
- expect(el.checkedIndex).to.equal(-1);
2188
+ expect(el.checkedIndex).to.equal(-1, autocompleteMode);
2087
2189
  }
2088
2190
 
2089
2191
  el.autocomplete = 'none';
2090
- await performChecks();
2192
+ await performChecks('none');
2091
2193
 
2092
2194
  el.autocomplete = 'list';
2093
- await performChecks();
2195
+ await performChecks('list');
2094
2196
  });
2095
2197
  });
2096
2198
 
@@ -2233,7 +2335,7 @@ describe('lion-combobox', () => {
2233
2335
  // eslint-disable-next-line no-param-reassign
2234
2336
  elm.activeIndex = -1;
2235
2337
  // eslint-disable-next-line no-param-reassign
2236
- elm.checkedIndex = -1;
2338
+ elm.setCheckedIndex(-1);
2237
2339
  // eslint-disable-next-line no-param-reassign
2238
2340
  elm.opened = true;
2239
2341
  await elm.updateComplete;
@@ -0,0 +1,62 @@
1
+ import { mimicUserTyping } from '@lion/ui/combobox-test-helpers.js';
2
+ import { MatchesOption } from '@lion/ui/combobox.js';
3
+ import '@lion/ui/define/lion-combobox.js';
4
+ import '@lion/ui/define/lion-option.js';
5
+ import { expect, fixture, html } from '@open-wc/testing';
6
+
7
+ /**
8
+ * @typedef {import('@lion/ui/combobox.js').LionCombobox} LionCombobox
9
+ */
10
+
11
+ describe('MatchesOption validation', () => {
12
+ it('is enabled when an invalid value is set', async () => {
13
+ let isEnabled;
14
+ const el = /** @type {LionCombobox} */ (
15
+ await fixture(html`
16
+ <lion-combobox name="foo">
17
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
18
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
19
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
20
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
21
+ </lion-combobox>
22
+ `)
23
+ );
24
+ const config = {};
25
+ config.node = el;
26
+ const validator = new MatchesOption();
27
+
28
+ mimicUserTyping(el, 'Artichoke');
29
+ await el.updateComplete;
30
+
31
+ isEnabled = validator.execute('Artichoke', undefined, config);
32
+ expect(isEnabled).to.be.false;
33
+
34
+ mimicUserTyping(el, 'Foo');
35
+ await el.updateComplete;
36
+
37
+ isEnabled = validator.execute('Foo', undefined, config);
38
+ expect(isEnabled).to.be.true;
39
+ });
40
+
41
+ it('is not enabled when empty is set', async () => {
42
+ const el = /** @type {LionCombobox} */ (
43
+ await fixture(html`
44
+ <lion-combobox name="foo">
45
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
46
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
47
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
48
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
49
+ </lion-combobox>
50
+ `)
51
+ );
52
+ const config = {};
53
+ config.node = el;
54
+ const validator = new MatchesOption();
55
+
56
+ el.modelValue = '';
57
+ await el.updateComplete;
58
+
59
+ const isEnabled = validator.execute('', undefined, config);
60
+ expect(isEnabled).to.be.false;
61
+ });
62
+ });