@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.
- package/README.md +1 -2
- package/components/button/test-suites/LionButton.suite.js +3 -1
- package/components/calendar/src/LionCalendar.js +9 -5
- package/components/calendar/test/lion-calendar.test.js +33 -6
- package/components/combobox/src/LionCombobox.js +33 -9
- package/components/combobox/test/lion-combobox.test.js +134 -7
- package/components/dialog/test/lion-dialog.test.js +59 -1
- package/components/input-datepicker/src/LionInputDatepicker.js +1 -21
- package/components/input-datepicker/test/lion-input-datepicker.test.js +30 -3
- package/components/input-file/src/FileHandle.js +1 -1
- package/components/input-file/src/LionInputFile.js +1 -3
- package/components/input-file/test/lion-input-file.test.js +11 -14
- package/components/overlays/src/OverlayController.js +13 -7
- package/components/overlays/src/OverlayMixin.js +5 -1
- package/components/overlays/test/OverlayController.test.js +46 -0
- package/components/select-rich/src/LionSelectRich.js +12 -0
- package/components/select-rich/test/lion-select-rich.test.js +26 -0
- package/custom-elements.json +60 -73
- package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
- package/dist-types/components/calendar/src/LionCalendar.d.ts +1 -0
- package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
- package/dist-types/components/combobox/src/LionCombobox.d.ts +9 -0
- package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +0 -5
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
- package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/button/use-cases.md +2 -2
- package/docs/components/calendar/use-cases.md +4 -4
- package/docs/components/checkbox-group/api-table.md +49 -55
- package/docs/components/checkbox-group/overview.md +5 -5
- package/docs/components/checkbox-group/use-cases.md +33 -33
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/collapsible/use-cases.md +3 -3
- package/docs/components/combobox/api-table.md +84 -89
- package/docs/components/combobox/extensions.md +20 -20
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +6 -6
- package/docs/components/dialog/api-table.md +3 -3
- package/docs/components/dialog/overview.md +2 -2
- package/docs/components/dialog/src/external-dialog.js +32 -0
- package/docs/components/dialog/src/slots-dialog-content.js +1 -1
- package/docs/components/dialog/src/styled-dialog-content.js +1 -1
- package/docs/components/dialog/use-cases.md +49 -3
- package/docs/components/drawer/use-cases.md +3 -3
- package/docs/components/fieldset/api-table.md +45 -51
- package/docs/components/fieldset/overview.md +7 -7
- package/docs/components/fieldset/use-cases.md +15 -15
- package/docs/components/form/overview.md +7 -7
- package/docs/components/form/use-cases.md +4 -4
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input/use-cases.md +3 -3
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-amount/use-cases.md +8 -8
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-date/use-cases.md +8 -8
- package/docs/components/input-datepicker/api-table.md +1 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-datepicker/use-cases.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-email/use-cases.md +3 -3
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-iban/use-cases.md +8 -8
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +65 -69
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +9 -9
- package/docs/components/input-tel-dropdown/api-table.md +70 -73
- package/docs/components/input-tel-dropdown/extensions.md +1 -1
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/input-tel-dropdown/use-cases.md +5 -5
- package/docs/components/listbox/api-table.md +64 -70
- package/docs/components/listbox/overview.md +5 -5
- package/docs/components/listbox/use-cases.md +61 -61
- package/docs/components/pagination/use-cases.md +5 -5
- package/docs/components/radio-group/api-table.md +49 -55
- package/docs/components/radio-group/overview.md +6 -6
- package/docs/components/radio-group/use-cases.md +18 -18
- package/docs/components/select/use-cases.md +2 -2
- package/docs/components/select-rich/api-table.md +79 -85
- package/docs/components/select-rich/overview.md +3 -3
- package/docs/components/select-rich/use-cases.md +33 -33
- package/docs/components/steps/overview.md +4 -4
- package/docs/components/steps/use-cases.md +7 -7
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/tabs/use-cases.md +1 -1
- package/docs/components/textarea/use-cases.md +3 -3
- package/docs/components/tooltip/api-table.md +3 -3
- package/docs/components/tooltip/use-cases.md +6 -6
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +8 -8
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/use-cases.md +19 -19
- package/docs/fundamentals/systems/form/validate.md +50 -50
- package/docs/fundamentals/systems/localize/api-table.md +10 -17
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/localize/use-cases.md +4 -4
- package/docs/fundamentals/systems/overlays/_configuration-positioning.md +4 -4
- package/docs/fundamentals/systems/overlays/api-table.md +34 -90
- package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -9
- package/docs/fundamentals/systems/overlays/configuration.md +30 -30
- package/docs/fundamentals/systems/overlays/overview.md +6 -6
- package/docs/fundamentals/systems/overlays/use-cases.md +30 -30
- package/docs/fundamentals/tools/ajax/use-cases.md +20 -20
- package/docs/fundamentals/tools/helpers/action-logger.md +3 -3
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/example-complex/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/example-fail/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/example-success/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +7 -7
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/scoped-elements.md +1 -1
- package/docs/guides/principles/styling.md +1 -1
- 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
|
|
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 ▶</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} */ (
|
|
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/
|
|
469
|
-
expect(isSameDate(elSetting.centralDate, new Date('2019/07/03'))
|
|
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
|
-
|
|
205
|
-
|
|
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
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
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('
|
|
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
|
|
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
|
|
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(
|
|
525
|
+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
|
|
499
526
|
await elObj.openCalendar();
|
|
500
|
-
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(
|
|
527
|
+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
|
|
501
528
|
await elObj.closeCalendar();
|
|
502
|
-
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(
|
|
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('.')
|
|
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, '').
|
|
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 () => {
|