@lion/ui 0.7.6 → 0.7.7
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/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/test/lion-input-datepicker.test.js +3 -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 -55
- package/dist-types/components/button/test-suites/LionButton.suite.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-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 -1
- 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;
|
|
@@ -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
|
});
|
|
@@ -495,11 +495,11 @@ describe('<lion-input-datepicker>', () => {
|
|
|
495
495
|
const el = await fixture(html`<lion-input-datepicker></lion-input-datepicker>`);
|
|
496
496
|
const elObj = new DatepickerInputObject(el);
|
|
497
497
|
|
|
498
|
-
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(
|
|
498
|
+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
|
|
499
499
|
await elObj.openCalendar();
|
|
500
|
-
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(
|
|
500
|
+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
|
|
501
501
|
await elObj.closeCalendar();
|
|
502
|
-
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(
|
|
502
|
+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal(null);
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
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 () => {
|
|
@@ -640,13 +640,14 @@ export class OverlayController extends EventTarget {
|
|
|
640
640
|
__setupTeardownAccessibility({ phase }) {
|
|
641
641
|
if (phase === 'init') {
|
|
642
642
|
this.__storeOriginalAttrs(this.contentNode, ['role', 'id']);
|
|
643
|
+
const isModal = this.trapsKeyboardFocus;
|
|
643
644
|
|
|
644
645
|
if (this.invokerNode) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
'aria-
|
|
648
|
-
|
|
649
|
-
|
|
646
|
+
const attributesToStore = ['aria-labelledby', 'aria-describedby'];
|
|
647
|
+
if (!isModal) {
|
|
648
|
+
attributesToStore.push('aria-expanded');
|
|
649
|
+
}
|
|
650
|
+
this.__storeOriginalAttrs(this.invokerNode, attributesToStore);
|
|
650
651
|
}
|
|
651
652
|
|
|
652
653
|
if (!this.contentNode.id) {
|
|
@@ -661,7 +662,7 @@ export class OverlayController extends EventTarget {
|
|
|
661
662
|
}
|
|
662
663
|
this.contentNode.setAttribute('role', 'tooltip');
|
|
663
664
|
} else {
|
|
664
|
-
if (this.invokerNode) {
|
|
665
|
+
if (this.invokerNode && !isModal) {
|
|
665
666
|
this.invokerNode.setAttribute('aria-expanded', `${this.isShown}`);
|
|
666
667
|
}
|
|
667
668
|
if (!this.contentNode.getAttribute('role')) {
|
|
@@ -787,6 +788,10 @@ export class OverlayController extends EventTarget {
|
|
|
787
788
|
* @protected
|
|
788
789
|
*/
|
|
789
790
|
_keepBodySize({ phase }) {
|
|
791
|
+
if (!this.preventsScroll) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
790
795
|
switch (phase) {
|
|
791
796
|
case 'before-show':
|
|
792
797
|
this.__bodyClientWidth = document.body.clientWidth;
|
|
@@ -1299,7 +1304,8 @@ export class OverlayController extends EventTarget {
|
|
|
1299
1304
|
if (phase === 'init' || phase === 'teardown') {
|
|
1300
1305
|
this.__setupTeardownAccessibility({ phase });
|
|
1301
1306
|
}
|
|
1302
|
-
|
|
1307
|
+
const isModal = this.trapsKeyboardFocus;
|
|
1308
|
+
if (this.invokerNode && !this.isTooltip && !isModal) {
|
|
1303
1309
|
this.invokerNode.setAttribute('aria-expanded', `${phase === 'show'}`);
|
|
1304
1310
|
}
|
|
1305
1311
|
}
|
|
@@ -72,7 +72,11 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
72
72
|
requestUpdate(name, oldValue, options) {
|
|
73
73
|
super.requestUpdate(name, oldValue, options);
|
|
74
74
|
if (name === 'opened' && this.opened !== oldValue) {
|
|
75
|
-
this.dispatchEvent(
|
|
75
|
+
this.dispatchEvent(
|
|
76
|
+
new CustomEvent('opened-changed', {
|
|
77
|
+
detail: { opened: this.opened },
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -1528,6 +1528,23 @@ describe('OverlayController', () => {
|
|
|
1528
1528
|
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal('false');
|
|
1529
1529
|
});
|
|
1530
1530
|
|
|
1531
|
+
it('does not synchronize [aria-expanded] on invoker when the overlay is modal', async () => {
|
|
1532
|
+
const invokerNode = /** @type {HTMLElement} */ (
|
|
1533
|
+
await fixture('<div role="button">invoker</div>')
|
|
1534
|
+
);
|
|
1535
|
+
const ctrl = new OverlayController({
|
|
1536
|
+
...withLocalTestConfig(),
|
|
1537
|
+
trapsKeyboardFocus: true,
|
|
1538
|
+
handlesAccessibility: true,
|
|
1539
|
+
invokerNode,
|
|
1540
|
+
});
|
|
1541
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1542
|
+
await ctrl.show();
|
|
1543
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1544
|
+
await ctrl.hide();
|
|
1545
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1531
1548
|
it('creates unique id for content', async () => {
|
|
1532
1549
|
const ctrl = new OverlayController({
|
|
1533
1550
|
...withLocalTestConfig(),
|
|
@@ -1764,4 +1781,33 @@ describe('OverlayController', () => {
|
|
|
1764
1781
|
);
|
|
1765
1782
|
});
|
|
1766
1783
|
});
|
|
1784
|
+
|
|
1785
|
+
describe('run _keepBodySize only with scroll prevention', () => {
|
|
1786
|
+
/**
|
|
1787
|
+
* @type {OverlayController}
|
|
1788
|
+
*/
|
|
1789
|
+
const overlayControllerNoPrevent = new OverlayController({
|
|
1790
|
+
...withLocalTestConfig(),
|
|
1791
|
+
preventsScroll: false,
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
const overlayControllerPreventsScroll = new OverlayController({
|
|
1795
|
+
...withLocalTestConfig(),
|
|
1796
|
+
preventsScroll: true,
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
it('should not run with scroll prevention', async () => {
|
|
1800
|
+
await overlayControllerNoPrevent.show();
|
|
1801
|
+
|
|
1802
|
+
expect(overlayControllerNoPrevent.__bodyMarginRightInline).to.equal(undefined);
|
|
1803
|
+
expect(overlayControllerNoPrevent.__bodyMarginRight).to.equal(undefined);
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
it('should run with scroll prevention', async () => {
|
|
1807
|
+
await overlayControllerPreventsScroll.show();
|
|
1808
|
+
|
|
1809
|
+
expect(overlayControllerPreventsScroll.__bodyMarginRightInline).to.not.equal(undefined);
|
|
1810
|
+
expect(overlayControllerPreventsScroll.__bodyMarginRight).to.not.equal(undefined);
|
|
1811
|
+
});
|
|
1812
|
+
});
|
|
1767
1813
|
});
|
|
@@ -196,6 +196,18 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
if (changedProperties.has('singleOption')) {
|
|
200
|
+
if (this.singleOption) {
|
|
201
|
+
this._invokerNode.removeAttribute('role');
|
|
202
|
+
this._invokerNode.removeAttribute('aria-haspopup');
|
|
203
|
+
this._invokerNode.removeAttribute('aria-expanded');
|
|
204
|
+
} else {
|
|
205
|
+
this._invokerNode.setAttribute('role', 'button');
|
|
206
|
+
this._invokerNode.setAttribute('aria-haspopup', 'listbox');
|
|
207
|
+
this._invokerNode.setAttribute('aria-expanded', `${this.opened}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
if (this._inputNode && this._invokerNode) {
|
|
200
212
|
if (changedProperties.has('_ariaLabelledNodes')) {
|
|
201
213
|
this._invokerNode.setAttribute(
|
|
@@ -148,6 +148,32 @@ describe('lion-select-rich', () => {
|
|
|
148
148
|
expect(_invokerNode.hasAttribute('single-option')).to.be.true;
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it('sets and removes the button role and aria attributes on change of singleOption', async () => {
|
|
152
|
+
const el = await fixture(html`
|
|
153
|
+
<lion-select-rich>
|
|
154
|
+
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
|
155
|
+
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
|
156
|
+
</lion-select-rich>
|
|
157
|
+
`);
|
|
158
|
+
const { _invokerNode } = getSelectRichMembers(el);
|
|
159
|
+
|
|
160
|
+
expect(_invokerNode.hasAttribute('role')).to.be.true;
|
|
161
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.true;
|
|
162
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.true;
|
|
163
|
+
|
|
164
|
+
el.singleOption = true;
|
|
165
|
+
await el.updateComplete;
|
|
166
|
+
expect(_invokerNode.hasAttribute('role')).to.be.false;
|
|
167
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.false;
|
|
168
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.false;
|
|
169
|
+
|
|
170
|
+
el.singleOption = false;
|
|
171
|
+
await el.updateComplete;
|
|
172
|
+
expect(_invokerNode.hasAttribute('role')).to.be.true;
|
|
173
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.true;
|
|
174
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.true;
|
|
175
|
+
});
|
|
176
|
+
|
|
151
177
|
it('updates the invoker when the selected element is the same but the modelValue was updated asynchronously', async () => {
|
|
152
178
|
const tagString = defineCE(
|
|
153
179
|
class LionCustomOption extends LionOption {
|