@lion/ui 0.7.6 → 0.7.8

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 (135) hide show
  1. package/README.md +1 -2
  2. package/components/button/test-suites/LionButton.suite.js +3 -1
  3. package/components/calendar/src/LionCalendar.js +9 -5
  4. package/components/calendar/test/lion-calendar.test.js +33 -6
  5. package/components/combobox/src/LionCombobox.js +33 -9
  6. package/components/combobox/test/lion-combobox.test.js +134 -7
  7. package/components/dialog/test/lion-dialog.test.js +59 -1
  8. package/components/input-datepicker/src/LionInputDatepicker.js +1 -21
  9. package/components/input-datepicker/test/lion-input-datepicker.test.js +30 -3
  10. package/components/input-file/src/FileHandle.js +1 -1
  11. package/components/input-file/src/LionInputFile.js +1 -3
  12. package/components/input-file/test/lion-input-file.test.js +11 -14
  13. package/components/overlays/src/OverlayController.js +13 -7
  14. package/components/overlays/src/OverlayMixin.js +5 -1
  15. package/components/overlays/test/OverlayController.test.js +46 -0
  16. package/components/select-rich/src/LionSelectRich.js +12 -0
  17. package/components/select-rich/test/lion-select-rich.test.js +26 -0
  18. package/custom-elements.json +60 -73
  19. package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
  20. package/dist-types/components/calendar/src/LionCalendar.d.ts +1 -0
  21. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  22. package/dist-types/components/combobox/src/LionCombobox.d.ts +9 -0
  23. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  24. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +0 -5
  25. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  26. package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
  27. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  28. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  29. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  30. package/dist-types/tsconfig.tsbuildinfo +1 -1
  31. package/docs/components/button/use-cases.md +2 -2
  32. package/docs/components/calendar/use-cases.md +4 -4
  33. package/docs/components/checkbox-group/api-table.md +49 -55
  34. package/docs/components/checkbox-group/overview.md +5 -5
  35. package/docs/components/checkbox-group/use-cases.md +33 -33
  36. package/docs/components/collapsible/overview.md +1 -1
  37. package/docs/components/collapsible/use-cases.md +3 -3
  38. package/docs/components/combobox/api-table.md +84 -89
  39. package/docs/components/combobox/extensions.md +20 -20
  40. package/docs/components/combobox/overview.md +1 -1
  41. package/docs/components/combobox/use-cases.md +6 -6
  42. package/docs/components/dialog/api-table.md +3 -3
  43. package/docs/components/dialog/overview.md +2 -2
  44. package/docs/components/dialog/src/external-dialog.js +32 -0
  45. package/docs/components/dialog/src/slots-dialog-content.js +1 -1
  46. package/docs/components/dialog/src/styled-dialog-content.js +1 -1
  47. package/docs/components/dialog/use-cases.md +49 -3
  48. package/docs/components/drawer/use-cases.md +3 -3
  49. package/docs/components/fieldset/api-table.md +45 -51
  50. package/docs/components/fieldset/overview.md +7 -7
  51. package/docs/components/fieldset/use-cases.md +15 -15
  52. package/docs/components/form/overview.md +7 -7
  53. package/docs/components/form/use-cases.md +4 -4
  54. package/docs/components/icon/use-cases.md +1 -1
  55. package/docs/components/input/overview.md +1 -1
  56. package/docs/components/input/use-cases.md +3 -3
  57. package/docs/components/input-amount/overview.md +3 -3
  58. package/docs/components/input-amount/use-cases.md +8 -8
  59. package/docs/components/input-date/overview.md +3 -3
  60. package/docs/components/input-date/use-cases.md +8 -8
  61. package/docs/components/input-datepicker/api-table.md +1 -3
  62. package/docs/components/input-datepicker/overview.md +5 -5
  63. package/docs/components/input-datepicker/use-cases.md +5 -5
  64. package/docs/components/input-email/overview.md +2 -2
  65. package/docs/components/input-email/use-cases.md +3 -3
  66. package/docs/components/input-file/overview.md +1 -1
  67. package/docs/components/input-iban/overview.md +2 -2
  68. package/docs/components/input-iban/use-cases.md +8 -8
  69. package/docs/components/input-range/overview.md +2 -2
  70. package/docs/components/input-stepper/api-table.md +65 -69
  71. package/docs/components/input-stepper/overview.md +1 -1
  72. package/docs/components/input-tel/overview.md +1 -1
  73. package/docs/components/input-tel/use-cases.md +9 -9
  74. package/docs/components/input-tel-dropdown/api-table.md +70 -73
  75. package/docs/components/input-tel-dropdown/extensions.md +1 -1
  76. package/docs/components/input-tel-dropdown/overview.md +1 -1
  77. package/docs/components/input-tel-dropdown/use-cases.md +5 -5
  78. package/docs/components/listbox/api-table.md +64 -70
  79. package/docs/components/listbox/overview.md +5 -5
  80. package/docs/components/listbox/use-cases.md +61 -61
  81. package/docs/components/pagination/use-cases.md +5 -5
  82. package/docs/components/radio-group/api-table.md +49 -55
  83. package/docs/components/radio-group/overview.md +6 -6
  84. package/docs/components/radio-group/use-cases.md +18 -18
  85. package/docs/components/select/use-cases.md +2 -2
  86. package/docs/components/select-rich/api-table.md +79 -85
  87. package/docs/components/select-rich/overview.md +3 -3
  88. package/docs/components/select-rich/use-cases.md +33 -33
  89. package/docs/components/steps/overview.md +4 -4
  90. package/docs/components/steps/use-cases.md +7 -7
  91. package/docs/components/tabs/examples.md +1 -1
  92. package/docs/components/tabs/use-cases.md +1 -1
  93. package/docs/components/textarea/use-cases.md +3 -3
  94. package/docs/components/tooltip/api-table.md +3 -3
  95. package/docs/components/tooltip/use-cases.md +6 -6
  96. package/docs/fundamentals/index.md +4 -4
  97. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  98. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  99. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  100. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  101. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  102. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  103. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  104. package/docs/fundamentals/rationales/accessibility.md +1 -1
  105. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  106. package/docs/fundamentals/systems/core/overview.md +1 -1
  107. package/docs/fundamentals/systems/form/formatting-and-parsing.md +8 -8
  108. package/docs/fundamentals/systems/form/model-value.md +1 -1
  109. package/docs/fundamentals/systems/form/overview.md +28 -28
  110. package/docs/fundamentals/systems/form/use-cases.md +19 -19
  111. package/docs/fundamentals/systems/form/validate.md +50 -50
  112. package/docs/fundamentals/systems/localize/api-table.md +10 -17
  113. package/docs/fundamentals/systems/localize/overview.md +4 -4
  114. package/docs/fundamentals/systems/localize/use-cases.md +4 -4
  115. package/docs/fundamentals/systems/overlays/_configuration-positioning.md +4 -4
  116. package/docs/fundamentals/systems/overlays/api-table.md +34 -90
  117. package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -9
  118. package/docs/fundamentals/systems/overlays/configuration.md +30 -30
  119. package/docs/fundamentals/systems/overlays/overview.md +6 -6
  120. package/docs/fundamentals/systems/overlays/use-cases.md +30 -30
  121. package/docs/fundamentals/tools/ajax/use-cases.md +20 -20
  122. package/docs/fundamentals/tools/helpers/action-logger.md +3 -3
  123. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  124. package/docs/fundamentals/tools/singleton-manager/example-complex/demo-app.js +2 -2
  125. package/docs/fundamentals/tools/singleton-manager/example-fail/demo-app.js +2 -2
  126. package/docs/fundamentals/tools/singleton-manager/example-success/demo-app.js +2 -2
  127. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  128. package/docs/guides/how-to/create-a-custom-field.md +7 -7
  129. package/docs/guides/how-to/create-a-form.md +4 -4
  130. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  131. package/docs/guides/index.md +8 -8
  132. package/docs/guides/principles/index.md +5 -5
  133. package/docs/guides/principles/scoped-elements.md +1 -1
  134. package/docs/guides/principles/styling.md +1 -1
  135. package/package.json +1 -1
package/README.md CHANGED
@@ -47,8 +47,7 @@ They provide an unopinionated, white-label layer that can be extended to your ow
47
47
  - **Modern Code:** Lion is distributes as pure es modules.
48
48
  - **Exposes functions/classes and Web Components:** Ships a functionality in it's most appropriate form.
49
49
 
50
- > Note: Our demos may look a little bland but that is on purpose. They only come with functional stylings.
51
- > This makes sense as the main use case is to extend those components and if you do you do not want to override existing stylings.
50
+ > Note: Our demo examples might look simple and not very stylish. This is on purpose. They are designed to be basic so you can easily add your own styles to them to match your intended design, without dealing with styles that are already there.
52
51
 
53
52
  <p align="center">
54
53
  <a href="https://lion-web.netlify.app/guides/"><strong>Explore the Lion Guides&nbsp;&nbsp;▶</strong></a>
@@ -52,7 +52,9 @@ export function LionButtonSuite({ klass = LionButton } = {}) {
52
52
 
53
53
  describe('Active', () => {
54
54
  it('updates "active" attribute on host when mousedown/mouseup on button', async () => {
55
- const el = /** @type {LionButton} */ (await fixture(html`<${tagButton}>foo</${tagButton}`));
55
+ const el = /** @type {LionButton} */ (
56
+ await fixture(html`<${tagButton}>foo</${tagButton}>`)
57
+ );
56
58
  el.dispatchEvent(new Event('mousedown'));
57
59
 
58
60
  expect(el.active).to.be.true;
@@ -270,11 +270,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
270
270
 
271
271
  this.__connectedCallbackDone = true;
272
272
 
273
- this.__calculateInitialCentralDate();
274
-
275
- // setup data for initial render
276
- this.__data = this.__createData();
277
-
278
273
  /**
279
274
  * This logic needs to happen on firstUpdated, but every time the DOM node is moved as well
280
275
  * since firstUpdated only runs once, this logic is moved here, but after updateComplete
@@ -305,6 +300,13 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
305
300
  }
306
301
  }
307
302
 
303
+ firstUpdated() {
304
+ this.__calculateInitialCentralDate();
305
+
306
+ // setup data for initial render
307
+ this.__data = this.__createData();
308
+ }
309
+
308
310
  disconnectedCallback() {
309
311
  super.disconnectedCallback();
310
312
  if (this.__contentWrapperElement) {
@@ -372,6 +374,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
372
374
  if (this.centralDate === this.__today && this.selectedDate) {
373
375
  // initialized with selectedDate only if user didn't provide another one
374
376
  this.centralDate = this.selectedDate;
377
+ } else if (!this.__isEnabledDate(this.centralDate)) {
378
+ this.centralDate = this.findNearestEnabledDate(this.centralDate);
375
379
  }
376
380
  /** @type {Date} */
377
381
  this.__initialCentralDate = this.centralDate;
@@ -465,9 +465,8 @@ describe('<lion-calendar>', () => {
465
465
  `);
466
466
 
467
467
  clock.restore();
468
- expect(isSameDate(el.centralDate, new Date('2019/06/03')), 'central date').to.be.true;
469
- expect(isSameDate(elSetting.centralDate, new Date('2019/07/03')), 'central date').to.be
470
- .true;
468
+ expect(isSameDate(el.centralDate, new Date('2019/07/03'))).to.be.true;
469
+ expect(isSameDate(elSetting.centralDate, new Date('2019/07/03'))).to.be.true;
471
470
  });
472
471
 
473
472
  describe('Normalization', () => {
@@ -573,7 +572,7 @@ describe('<lion-calendar>', () => {
573
572
  .disableDates="${/** @param {Date} d */ d => d.getDate() === 15}"
574
573
  ></lion-calendar>
575
574
  `);
576
- el.focusDate(el.findNearestEnabledDate());
575
+ el.focusDate(el.findNearestEnabledDate(new Date()));
577
576
  await el.updateComplete;
578
577
 
579
578
  const elObj = new CalendarObject(el);
@@ -590,7 +589,7 @@ describe('<lion-calendar>', () => {
590
589
  .disableDates="${/** @param {Date} d */ d => d.getFullYear() > 1998}"
591
590
  ></lion-calendar>
592
591
  `);
593
- el.focusDate(el.findNearestEnabledDate());
592
+ el.focusDate(el.findNearestEnabledDate(new Date()));
594
593
  await el.updateComplete;
595
594
 
596
595
  expect(el.centralDate.getFullYear()).to.equal(1998);
@@ -609,7 +608,7 @@ describe('<lion-calendar>', () => {
609
608
  ></lion-calendar>
610
609
  `);
611
610
 
612
- el.focusDate(el.findNearestEnabledDate());
611
+ el.focusDate(el.findNearestEnabledDate(new Date()));
613
612
  await el.updateComplete;
614
613
 
615
614
  expect(el.centralDate.getFullYear()).to.equal(2002);
@@ -1262,6 +1261,34 @@ describe('<lion-calendar>', () => {
1262
1261
 
1263
1262
  clock.restore();
1264
1263
  });
1264
+
1265
+ it('is nearest to today if no selected date is available and today is disabled', async () => {
1266
+ const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
1267
+
1268
+ const calWithMin = await fixture(
1269
+ html`<lion-calendar .minDate=${new Date('2000/12/25')}></lion-calendar>`,
1270
+ );
1271
+ const calObjWithMin = new CalendarObject(calWithMin);
1272
+ expect(calObjWithMin.centralDayObj?.monthday).to.equal(25);
1273
+
1274
+ const calWithMax = await fixture(
1275
+ html`<lion-calendar .maxDate=${new Date('2000/12/05')}></lion-calendar>`,
1276
+ );
1277
+ const calObjWithMax = new CalendarObject(calWithMax);
1278
+ expect(calObjWithMax.centralDayObj?.monthday).to.equal(5);
1279
+
1280
+ const calWithDisabled = await fixture(
1281
+ html`<lion-calendar
1282
+ .disableDates=${/** @param {Date} date */ date => date.getDate() === 15}
1283
+ ></lion-calendar>`,
1284
+ );
1285
+ await calWithDisabled.updateComplete;
1286
+
1287
+ const calObjWithDisabled = new CalendarObject(calWithDisabled);
1288
+ expect(calObjWithDisabled.centralDayObj?.monthday).to.equal(16);
1289
+
1290
+ clock.restore();
1291
+ });
1265
1292
  });
1266
1293
 
1267
1294
  /**
@@ -22,6 +22,7 @@ const matchA11ySpanReverseFns = new WeakMap();
22
22
  * @typedef {import('@lion/ui/types/form-core.js').ChoiceInputHost} ChoiceInputHost
23
23
  * @typedef {import('@lion/ui/types/form-core.js').FormControlHost} FormControlHost
24
24
  * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay
25
+ * @typedef {LionOption & { onFilterUnmatch?:function; onFilterMatch?:function }} OptionWithFilterFn
25
26
  */
26
27
 
27
28
  /**
@@ -201,8 +202,30 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
201
202
 
202
203
  reset() {
203
204
  super.reset();
204
- // @ts-ignore _initialModelValue comes from ListboxMixin
205
- this.value = this._initialModelValue;
205
+ if (!this.multipleChoice) {
206
+ // @ts-ignore _initialModelValue comes from ListboxMixin
207
+ this.value = this._initialModelValue;
208
+ }
209
+ this._resetListboxOptions();
210
+ }
211
+
212
+ /**
213
+ * @protected
214
+ */
215
+ _resetListboxOptions() {
216
+ this.formElements.forEach((/** @type {OptionWithFilterFn} */ option, idx) => {
217
+ this._unhighlightMatchedOption(option);
218
+ if (!this.showAllOnEmpty || !this.opened) {
219
+ // eslint-disable-next-line no-param-reassign
220
+ option.style.display = 'none';
221
+ } else {
222
+ // eslint-disable-next-line no-param-reassign
223
+ option.style.display = '';
224
+ option.setAttribute('aria-posinset', `${idx + 1}`);
225
+ option.setAttribute('aria-setsize', `${this.formElements.length}`);
226
+ option.removeAttribute('aria-hidden');
227
+ }
228
+ });
206
229
  }
207
230
 
208
231
  /**
@@ -704,11 +727,13 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
704
727
  */
705
728
  _listboxOnClick(ev) {
706
729
  super._listboxOnClick(ev);
707
-
708
730
  this._inputNode.focus();
709
731
  if (!this.multipleChoice) {
710
732
  this.activeIndex = -1;
711
733
  this.opened = false;
734
+ } else {
735
+ this._inputNode.value = '';
736
+ this._resetListboxOptions();
712
737
  }
713
738
  }
714
739
 
@@ -897,7 +922,6 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
897
922
  const autoselect = this._autoSelectCondition();
898
923
  const noFilter = this.autocomplete === 'inline' || this.autocomplete === 'none';
899
924
 
900
- /** @typedef {LionOption & { onFilterUnmatch?:function, onFilterMatch?:function }} OptionWithFilterFn */
901
925
  this.formElements.forEach((/** @type {OptionWithFilterFn} */ option, i) => {
902
926
  // [1]. Decide whether option should be shown
903
927
  const matches = this.matchCondition(option, curValue);
@@ -983,7 +1007,7 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
983
1007
 
984
1008
  // [8]. These values will help computing autofill intentions next autocomplete cycle
985
1009
  this.__prevCboxValueNonSelected = curValue;
986
- // See test 'computation of "user intends autofill" works correctly afer autofill'
1010
+ // See test 'computation of "user intends autofill" works correctly after autofill'
987
1011
  this.__prevCboxValue = this._inputNode.value;
988
1012
  this.__hadSelectionLastAutofill =
989
1013
  this._inputNode.value.length !== this._inputNode.selectionStart;
@@ -1120,16 +1144,16 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
1120
1144
  ev.preventDefault();
1121
1145
 
1122
1146
  this.modelValue = this.parser([...this.modelValue, this._inputNode.value]);
1123
-
1124
1147
  this._inputNode.value = '';
1125
1148
  this.opened = false;
1126
1149
  } else {
1127
1150
  super._listboxOnKeyDown(ev);
1128
- // TODO: should we clear the input value here when allowCustomChoice is false?
1129
- // For now, we don't...
1151
+ this._resetListboxOptions();
1130
1152
  }
1131
1153
  if (!this.multipleChoice) {
1132
1154
  this.opened = false;
1155
+ } else {
1156
+ this._inputNode.value = '';
1133
1157
  }
1134
1158
  break;
1135
1159
  default: {
@@ -1168,7 +1192,7 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
1168
1192
  .filter(option => diff.includes(option.choiceValue))
1169
1193
  .map(option => this._getTextboxValueFromOption(option))
1170
1194
  .join(' ');
1171
- this._setTextboxValue(newValue); // or last selected value?
1195
+ this._setTextboxValue(newValue);
1172
1196
  }
1173
1197
  }
1174
1198
 
@@ -81,6 +81,54 @@ describe('lion-combobox', () => {
81
81
  await performChecks();
82
82
  });
83
83
 
84
+ it('hides all options on reset()', async () => {
85
+ const el = /** @type {LionCombobox} */ (
86
+ await fixture(html`
87
+ <lion-combobox name="foo">
88
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
89
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
90
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
91
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
92
+ </lion-combobox>
93
+ `)
94
+ );
95
+
96
+ const options = el.formElements;
97
+ const visibleOptions = () => options.filter(o => o.style.display !== 'none');
98
+
99
+ mimicUserTyping(el, 'cha');
100
+ await el.updateComplete;
101
+ expect(visibleOptions().length).to.equal(1);
102
+ el.reset();
103
+ await el.updateComplete;
104
+ expect(visibleOptions().length).to.equal(0);
105
+ });
106
+
107
+ it('shows all options on reset() when showAllOnEmpty is set to true and overlay was open', async () => {
108
+ const el = /** @type {LionCombobox} */ (
109
+ await fixture(html`
110
+ <lion-combobox name="foo" show-all-on-empty>
111
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
112
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
113
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
114
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
115
+ </lion-combobox>
116
+ `)
117
+ );
118
+
119
+ const options = el.formElements;
120
+ const visibleOptions = () => options.filter(o => o.getAttribute('aria-hidden') !== 'true');
121
+
122
+ mimicUserTyping(el, 'cha');
123
+ await el.updateComplete;
124
+ expect(visibleOptions().length).to.equal(1);
125
+ expect(el.opened).to.be.true;
126
+
127
+ el.reset();
128
+ await el.updateComplete;
129
+ expect(visibleOptions().length).to.equal(4);
130
+ });
131
+
84
132
  it('hides listbox on click/enter (when multiple-choice is false)', async () => {
85
133
  const el = /** @type {LionCombobox} */ (
86
134
  await fixture(html`
@@ -467,13 +515,12 @@ describe('lion-combobox', () => {
467
515
  mimicUserTyping(el, 'a');
468
516
  await el.updateComplete;
469
517
 
470
- const visibleOptions = options.filter(o => o.style.display !== 'none');
471
- expect(visibleOptions.length).to.equal(3, 'after input');
518
+ const visibleOptions = () => options.filter(o => o.style.display !== 'none');
519
+ expect(visibleOptions().length).to.equal(3, 'after input');
472
520
 
473
521
  el.clear();
474
522
  await el.updateComplete;
475
- const visibleOptions2 = options.filter(o => o.style.display !== 'none');
476
- expect(visibleOptions2.length).to.equal(0, 'after clear');
523
+ expect(visibleOptions().length).to.equal(0, 'after clear');
477
524
  });
478
525
 
479
526
  it('resets modelValue and textbox value on reset()', async () => {
@@ -508,7 +555,7 @@ describe('lion-combobox', () => {
508
555
  el2.reset();
509
556
  expect(el2.modelValue).to.deep.equal(['Artichoke']);
510
557
  // @ts-ignore [allow-protected] in test
511
- expect(el2._inputNode.value).to.equal('Artichoke');
558
+ expect(el2._inputNode.value).to.equal('');
512
559
  });
513
560
 
514
561
  it('syncs textbox to modelValue', async () => {
@@ -735,7 +782,7 @@ describe('lion-combobox', () => {
735
782
  await el.updateComplete;
736
783
  });
737
784
 
738
- it('allows manyu custom selections when multi-choice when requireOptionMatch is false', async () => {
785
+ it('allows many custom selections when multi-choice when requireOptionMatch is false', async () => {
739
786
  const el = /** @type {LionCombobox} */ (
740
787
  await fixture(html`
741
788
  <lion-combobox
@@ -2088,7 +2135,7 @@ describe('lion-combobox', () => {
2088
2135
  });
2089
2136
  });
2090
2137
 
2091
- it('highlights first occcurence of matching option', async () => {
2138
+ it('highlights first occurrence of matching option', async () => {
2092
2139
  const el = /** @type {LionCombobox} */ (
2093
2140
  await fixture(html`
2094
2141
  <lion-combobox name="foo" match-mode="all">
@@ -2170,6 +2217,28 @@ describe('lion-combobox', () => {
2170
2217
  );
2171
2218
  });
2172
2219
 
2220
+ it('resets removes highlights on reset()', async () => {
2221
+ const el = /** @type {LionCombobox} */ (
2222
+ await fixture(html`
2223
+ <lion-combobox name="foo" match-mode="all">
2224
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
2225
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
2226
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
2227
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
2228
+ </lion-combobox>
2229
+ `)
2230
+ );
2231
+ const options = el.formElements;
2232
+
2233
+ mimicUserTyping(/** @type {LionCombobox} */ (el), 'c');
2234
+
2235
+ await el.updateComplete;
2236
+ expect(options[0]).lightDom.to.equal(`<span aria-label="Artichoke">Arti<b>c</b>hoke</span>`);
2237
+ el.reset();
2238
+ await el.updateComplete;
2239
+ expect(options[0]).lightDom.to.equal(`Artichoke`);
2240
+ });
2241
+
2173
2242
  it('synchronizes textbox when autocomplete is "inline" or "both"', async () => {
2174
2243
  const el = /** @type {LionCombobox} */ (
2175
2244
  await fixture(html`
@@ -3018,6 +3087,64 @@ describe('lion-combobox', () => {
3018
3087
  expect(el.opened).to.equal(true);
3019
3088
  });
3020
3089
 
3090
+ it('clears textbox after selection of a new item on [enter]', async () => {
3091
+ const el = /** @type {LionCombobox} */ (
3092
+ await fixture(html`
3093
+ <lion-combobox name="foo" multiple-choice>
3094
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
3095
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
3096
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
3097
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
3098
+ </lion-combobox>
3099
+ `)
3100
+ );
3101
+
3102
+ const { _inputNode } = getComboboxMembers(el);
3103
+ const options = el.formElements;
3104
+
3105
+ mimicUserTyping(el, 'art');
3106
+ await el.updateComplete;
3107
+
3108
+ expect(el.opened).to.equal(true);
3109
+ const visibleOptions = () => options.filter(o => o.style.display !== 'none');
3110
+ expect(visibleOptions().length).to.equal(1);
3111
+
3112
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
3113
+ // because this closely mimics what happens in the browser
3114
+ _inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
3115
+ expect(el.opened).to.equal(true);
3116
+ expect(visibleOptions().length).to.equal(0);
3117
+ expect(_inputNode.value).to.equal('');
3118
+ });
3119
+
3120
+ it('clears textbox after selection of a new item on click', async () => {
3121
+ const el = /** @type {LionCombobox} */ (
3122
+ await fixture(html`
3123
+ <lion-combobox name="foo" multiple-choice>
3124
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
3125
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
3126
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
3127
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
3128
+ </lion-combobox>
3129
+ `)
3130
+ );
3131
+
3132
+ const { _inputNode } = getComboboxMembers(el);
3133
+ const options = el.formElements;
3134
+
3135
+ mimicUserTyping(el, 'art');
3136
+ await el.updateComplete;
3137
+
3138
+ expect(el.opened).to.equal(true);
3139
+ const visibleOptions = () => options.filter(o => o.style.display !== 'none');
3140
+ expect(visibleOptions().length).to.equal(1);
3141
+
3142
+ visibleOptions()[0].click();
3143
+ expect(el.opened).to.equal(true);
3144
+ expect(visibleOptions().length).to.equal(0);
3145
+ expect(_inputNode.value).to.equal('');
3146
+ });
3147
+
3021
3148
  it('submits form on [Enter] when listbox is closed', async () => {
3022
3149
  const submitSpy = sinon.spy(e => e.preventDefault());
3023
3150
  const el = /** @type {HTMLFormElement} */ (
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable lit-a11y/no-autofocus */
2
- import { expect, fixture as _fixture, html, unsafeStatic } from '@open-wc/testing';
2
+ import { expect, fixture as _fixture, html, unsafeStatic, aTimeout } from '@open-wc/testing';
3
3
  import { runOverlayMixinSuite } from '../../overlays/test-suites/OverlayMixin.suite.js';
4
4
  import '@lion/ui/define/lion-dialog.js';
5
5
 
@@ -127,5 +127,63 @@ describe('lion-dialog', () => {
127
127
  invokerNode.click();
128
128
  expect(document.activeElement).to.equal(invokerNode);
129
129
  });
130
+
131
+ it('opened-changed event should send detail object with opened state', async () => {
132
+ const el = /** @type {LionDialog} */ await fixture(html`
133
+ <lion-dialog .config=${{ trapsKeyboardFocus: false }}>
134
+ <button slot="invoker">invoker button</button>
135
+ <div slot="content">
136
+ <label for="myInput">Label</label>
137
+ <input id="myInput" autofocus />
138
+ </div>
139
+ </lion-dialog>
140
+ `);
141
+
142
+ el.setAttribute('opened', '');
143
+ expect(el.opened).to.be.true;
144
+
145
+ el.addEventListener('opened-changed', e => {
146
+ // @ts-expect-error [allow-detail-since-custom-event]
147
+ expect(e.detail.opened).to.be.false;
148
+ });
149
+ el.removeAttribute('opened');
150
+ });
151
+
152
+ it("opened-changed event's target should point to lion-dialog", async () => {
153
+ const el = /** @type {LionDialog} */ await fixture(html`
154
+ <lion-dialog .config=${{ trapsKeyboardFocus: false }}>
155
+ <button slot="invoker">invoker button</button>
156
+ <div slot="content">
157
+ <label for="myInput">Label</label>
158
+ <input id="myInput" autofocus />
159
+ </div>
160
+ </lion-dialog>
161
+ `);
162
+
163
+ el.addEventListener('opened-changed', e => {
164
+ expect(e.target).to.equal(el);
165
+ });
166
+ el.setAttribute('opened', '');
167
+ });
168
+ });
169
+
170
+ describe('Accessibility', () => {
171
+ it('does not add [aria-expanded] to invoker button', async () => {
172
+ const el = await fixture(
173
+ html` <lion-dialog>
174
+ <div slot="content" class="dialog">Hey there</div>
175
+ <button slot="invoker">Popup button</button>
176
+ </lion-dialog>`,
177
+ );
178
+ const invokerButton = /** @type {HTMLElement} */ (el.querySelector('[slot="invoker"]'));
179
+
180
+ expect(invokerButton.getAttribute('aria-expanded')).to.equal(null);
181
+ await invokerButton.click();
182
+ await aTimeout(0);
183
+ expect(invokerButton.getAttribute('aria-expanded')).to.equal(null);
184
+ await invokerButton.click();
185
+ await aTimeout(0);
186
+ expect(invokerButton.getAttribute('aria-expanded')).to.equal(null);
187
+ });
130
188
  });
131
189
  });
@@ -102,23 +102,6 @@ export class LionInputDatepicker extends ScopedElementsMixin(
102
102
  };
103
103
  }
104
104
 
105
- _inputFormatter = new Intl.DateTimeFormat('en-US', {
106
- year: 'numeric',
107
- month: '2-digit',
108
- day: '2-digit',
109
- }).formatToParts;
110
-
111
- /**
112
- * @param {Date} date
113
- */
114
- // eslint-disable-next-line class-methods-use-this
115
- _formatDate(date) {
116
- const day = String(date.getDate()).padStart(2, '0');
117
- const month = String(date.getMonth() + 1).padStart(2, '0');
118
- const year = String(date.getFullYear());
119
- return `${day}/${month}/${year}`;
120
- }
121
-
122
105
  get slots() {
123
106
  return {
124
107
  ...super.slots,
@@ -370,18 +353,15 @@ export class LionInputDatepicker extends ScopedElementsMixin(
370
353
  if (this._syncOnUserSelect) {
371
354
  // Synchronize new selectedDate value to input
372
355
  this._isHandlingUserInput = true;
356
+ this._isHandlingCalendarUserInput = true;
373
357
 
374
358
  if (
375
359
  Array.isArray(this.__calendarDisableDates) &&
376
360
  this.__calendarDisableDates.includes(selectedDate)
377
361
  ) {
378
362
  // If the selected date is disabled, reset the values
379
- this.value = '';
380
- this.formattedValue = '';
381
363
  this.modelValue = undefined;
382
364
  } else {
383
- this.formattedValue = this._formatDate(selectedDate);
384
- this.value = this.formattedValue;
385
365
  this.modelValue = selectedDate;
386
366
  }
387
367
 
@@ -263,6 +263,33 @@ describe('<lion-input-datepicker>', () => {
263
263
  expect(el.value).to.equal('18/12/2019');
264
264
  });
265
265
 
266
+ describe('localization', () => {
267
+ /** @type {import('@lion/ui/localize.js').LocalizeManager} */ let localizeManager;
268
+
269
+ before(async () => {
270
+ const { getLocalizeManager } = await import('@lion/ui/localize-no-side-effects.js');
271
+ localizeManager = getLocalizeManager();
272
+ });
273
+
274
+ it('should update formatted value and value in accordance of localization upon model value update', async () => {
275
+ localizeManager.locale = 'nl-NL';
276
+
277
+ const el = await fixture(html`
278
+ <lion-input-datepicker .modelValue="${new Date('2022/12/30')}"></lion-input-datepicker>
279
+ `);
280
+
281
+ await el.updateComplete;
282
+
283
+ expect(el.formattedValue).to.equal('30-12-2022');
284
+ expect(el.value).to.equal('30-12-2022');
285
+ });
286
+
287
+ after(() => {
288
+ // @ts-ignore - using protected method to undo setting explict locale
289
+ localizeManager.locale = localizeManager._fallbackLocale;
290
+ });
291
+ });
292
+
266
293
  describe('Validators', () => {
267
294
  /**
268
295
  * Validators are the Application Developer facing API in <lion-input-datepicker>:
@@ -495,11 +522,11 @@ describe('<lion-input-datepicker>', () => {
495
522
  const el = await fixture(html`<lion-input-datepicker></lion-input-datepicker>`);
496
523
  const elObj = new DatepickerInputObject(el);
497
524
 
498
- expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
525
+ expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
499
526
  await elObj.openCalendar();
500
- expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('true');
527
+ expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
501
528
  await elObj.closeCalendar();
502
- expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
529
+ expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
503
530
  });
504
531
 
505
532
  it('is accessible when closed', async () => {
@@ -53,7 +53,7 @@ export class FileHandle {
53
53
  */
54
54
  // eslint-disable-next-line class-methods-use-this
55
55
  _getFileNameExtension(fileName) {
56
- return fileName.slice(fileName.lastIndexOf('.') + 1);
56
+ return fileName.slice(fileName.lastIndexOf('.'));
57
57
  }
58
58
 
59
59
  // TODO: seems to suggest upload is going on...
@@ -232,7 +232,7 @@ export class LionInputFile extends ScopedElementsMixin(LocalizeMixin(LionField))
232
232
  /** @type {string[]} */
233
233
  let allowedFileExtensions = [];
234
234
  if (this.accept) {
235
- const acceptedFiles = this.accept.replace(/\s+/g, '').replace(/\.+/g, '').split(',');
235
+ const acceptedFiles = this.accept.replace(/\s+/g, '').split(',');
236
236
  allowedFileTypes = acceptedFiles.filter(acceptedFile => acceptedFile.includes('/'));
237
237
  allowedFileExtensions = acceptedFiles.filter(acceptedFile => !acceptedFile.includes('/'));
238
238
  }
@@ -692,8 +692,6 @@ export class LionInputFile extends ScopedElementsMixin(LocalizeMixin(LionField))
692
692
 
693
693
  if (allowedFileExtensions.length) {
694
694
  array = allowedFileExtensions;
695
- // eslint-disable-next-line no-return-assign
696
- array = array.map(item => (item = `.${item}`));
697
695
  lastItem = array.pop();
698
696
  arrayLength = array.length;
699
697
  } else if (allowedFileTypes.length) {
@@ -158,6 +158,17 @@ describe('lion-input-file', () => {
158
158
  })
159
159
  );
160
160
 
161
+ it('error should not be there when the file extensions are accepted', async () => {
162
+ const el = await fixture(html`
163
+ <lion-input-file label="Select" accept=".txt"></lion-input-file>
164
+ `);
165
+
166
+ mimicSelectFile(el, [fileWrongType]);
167
+ await el.updateComplete;
168
+
169
+ expect(el.hasFeedbackFor.length).to.equal(0);
170
+ });
171
+
161
172
  it('should not be added to the selected list', async () => {
162
173
  const el = await fixture(html`
163
174
  <lion-input-file label="Select" accept="text/plain"></lion-input-file>
@@ -343,20 +354,6 @@ describe('lion-input-file', () => {
343
354
  expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.');
344
355
  });
345
356
  });
346
-
347
- it('error message should add all file extensions to the validator message also works without dots "."', async () => {
348
- const el = await fixture(html`
349
- <lion-input-file label="Select" accept="jpg, png, pdf"></lion-input-file>
350
- `);
351
-
352
- mimicSelectFile(el, [fileWrongType]);
353
- await el.updateComplete;
354
-
355
- // @ts-expect-error [allow-protected-in-test]
356
- el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
357
- expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.');
358
- });
359
- });
360
357
  });
361
358
 
362
359
  describe('invalid file sizes', async () => {