@lion/ui 0.8.8 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/combobox/src/LionCombobox.js +6 -2
- package/components/combobox/test/lion-combobox.test.js +56 -3
- package/components/core/src/DisabledMixin.js +6 -0
- package/components/core/test/DisabledMixin.test.js +22 -1
- package/components/core/test/SlotMixin.test.js +3 -3
- package/components/form-core/test/FocusMixin.test.js +6 -4
- package/components/form-core/test-suites/NativeTextFieldMixin.suite.js +4 -8
- package/components/form-integrations/test/form-integrations.test.js +2 -2
- package/components/input-datepicker/translations/es.js +1 -1
- package/components/input-stepper/src/LionInputStepper.js +110 -35
- package/components/input-stepper/test/lion-input-stepper.test.js +96 -1
- package/components/input-tel-dropdown/src/LionInputTelDropdown.js +7 -6
- package/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +62 -10
- package/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js +16 -12
- package/components/overlays/src/OverlayController.js +9 -0
- package/components/overlays/test/OverlayController.test.js +27 -3
- package/components/select-rich/test/lion-select-rich.test.js +9 -8
- package/components/switch/test/lion-switch.test.js +1 -1
- package/components/tabs/test/lion-tabs.test.js +12 -5
- package/custom-elements.json +506 -69
- package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
- package/dist-types/components/core/src/DisabledMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/test-suites/NativeTextFieldMixin.suite.d.ts.map +1 -1
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +33 -24
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
- package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
- package/dist-types/components/input-tel-dropdown/test/LionInputTelDropdown.test.d.ts +2 -2
- package/dist-types/components/input-tel-dropdown/test/LionInputTelDropdown.test.d.ts.map +1 -1
- package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts +2 -2
- package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/tabs/test/lion-tabs.test.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/button/api-table.md +3 -0
- package/docs/components/checkbox-group/api-table.md +3 -0
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/api-table.md +1 -0
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/api-table.md +1 -0
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/form/use-cases.md +2 -2
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/api-table.md +1 -0
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/api-table.md +1 -0
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/api-table.md +1 -0
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/api-table.md +1 -0
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/api-table.md +1 -0
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/api-table.md +1 -0
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/api-table.md +1 -0
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/api-table.md +1 -0
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +7 -3
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-stepper/use-cases.md +30 -0
- package/docs/components/input-tel/api-table.md +1 -0
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/api-table.md +1 -0
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/api-table.md +2 -0
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/radio-group/api-table.md +2 -0
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/api-table.md +1 -0
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/api-table.md +2 -0
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/switch/api-table.md +2 -0
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/textarea/api-table.md +1 -0
- package/docs/fundamentals/index.md +4 -4
- 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/api-table.md +1 -0
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +2 -2
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- 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/styling.md +1 -1
- package/package.json +1 -1
|
@@ -663,7 +663,11 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
|
|
|
663
663
|
) {
|
|
664
664
|
return false;
|
|
665
665
|
}
|
|
666
|
-
if (
|
|
666
|
+
if (
|
|
667
|
+
this.filled ||
|
|
668
|
+
this.showAllOnEmpty ||
|
|
669
|
+
(!this.filled && this.multipleChoice && this.__prevCboxValueNonSelected)
|
|
670
|
+
) {
|
|
667
671
|
return true;
|
|
668
672
|
}
|
|
669
673
|
// when no keyboard action involved (on focused change), return current opened state
|
|
@@ -705,7 +709,7 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(CustomChoiceGroupMi
|
|
|
705
709
|
// eslint-disable-next-line no-unused-vars
|
|
706
710
|
_textboxOnInput(ev) {
|
|
707
711
|
this.__shouldAutocompleteNextUpdate = true;
|
|
708
|
-
this.opened =
|
|
712
|
+
this.opened = this._showOverlayCondition({});
|
|
709
713
|
}
|
|
710
714
|
|
|
711
715
|
/**
|
|
@@ -931,6 +931,50 @@ describe('lion-combobox', () => {
|
|
|
931
931
|
expect(el.opened).to.equal(false);
|
|
932
932
|
});
|
|
933
933
|
|
|
934
|
+
it('does not flash the menu when _showOverlayCondition returns "false"', async () => {
|
|
935
|
+
class ComplexCombobox extends LionCombobox {
|
|
936
|
+
_showOverlayCondition() {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const tagName = defineCE(ComplexCombobox);
|
|
942
|
+
const tag = unsafeStatic(tagName);
|
|
943
|
+
|
|
944
|
+
const el = /** @type {LionCombobox} */ (
|
|
945
|
+
await fixture(html`
|
|
946
|
+
<${tag} name="combo" label="Display only the label once selected">
|
|
947
|
+
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
|
|
948
|
+
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
|
|
949
|
+
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
|
|
950
|
+
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
|
951
|
+
</${tag}>
|
|
952
|
+
`)
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
const dialog = el.shadowRoot?.querySelector('dialog');
|
|
956
|
+
/**
|
|
957
|
+
* hasDropdownFlashed is `true` if the menu was shown for a short period of time and then got closed
|
|
958
|
+
*/
|
|
959
|
+
let hasDropdownFlashed = false;
|
|
960
|
+
const observer = new MutationObserver(mutationList => {
|
|
961
|
+
// eslint-disable-next-line no-unused-vars
|
|
962
|
+
for (const mutation of mutationList) {
|
|
963
|
+
if (dialog?.style.display === '') {
|
|
964
|
+
hasDropdownFlashed = true;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
observer.observe(/** @type {Node} */ (dialog), { attributeFilter: ['style'] });
|
|
969
|
+
|
|
970
|
+
const { _inputNode } = getComboboxMembers(el);
|
|
971
|
+
_inputNode.focus();
|
|
972
|
+
await sendKeys({
|
|
973
|
+
type: 'art',
|
|
974
|
+
});
|
|
975
|
+
expect(hasDropdownFlashed).to.be.false;
|
|
976
|
+
});
|
|
977
|
+
|
|
934
978
|
it('shows overlay on click when filled', async () => {
|
|
935
979
|
const el = /** @type {LionCombobox} */ (
|
|
936
980
|
await fixture(html`
|
|
@@ -1157,7 +1201,10 @@ describe('lion-combobox', () => {
|
|
|
1157
1201
|
class ShowOverlayConditionCombobox extends LionCombobox {
|
|
1158
1202
|
/** @param {{ currentValue: string, lastKey:string }} options */
|
|
1159
1203
|
_showOverlayCondition(options) {
|
|
1160
|
-
return
|
|
1204
|
+
return (
|
|
1205
|
+
// @ts-ignore
|
|
1206
|
+
this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
|
|
1207
|
+
);
|
|
1161
1208
|
}
|
|
1162
1209
|
}
|
|
1163
1210
|
const tagName = defineCE(ShowOverlayConditionCombobox);
|
|
@@ -1182,7 +1229,10 @@ describe('lion-combobox', () => {
|
|
|
1182
1229
|
class ShowOverlayConditionCombobox extends LionCombobox {
|
|
1183
1230
|
/** @param {{ currentValue: string, lastKey:string }} options */
|
|
1184
1231
|
_showOverlayCondition(options) {
|
|
1185
|
-
return
|
|
1232
|
+
return (
|
|
1233
|
+
// @ts-ignore
|
|
1234
|
+
this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
|
|
1235
|
+
);
|
|
1186
1236
|
}
|
|
1187
1237
|
}
|
|
1188
1238
|
const tagName = defineCE(ShowOverlayConditionCombobox);
|
|
@@ -1207,7 +1257,10 @@ describe('lion-combobox', () => {
|
|
|
1207
1257
|
class ShowOverlayConditionCombobox extends LionCombobox {
|
|
1208
1258
|
/** @param {{ currentValue: string, lastKey:string }} options */
|
|
1209
1259
|
_showOverlayCondition(options) {
|
|
1210
|
-
return
|
|
1260
|
+
return (
|
|
1261
|
+
// @ts-ignore
|
|
1262
|
+
this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
|
|
1263
|
+
);
|
|
1211
1264
|
}
|
|
1212
1265
|
}
|
|
1213
1266
|
const tagName = defineCE(ShowOverlayConditionCombobox);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { expect, fixture } from '@open-wc/testing';
|
|
2
|
+
import { DisabledMixin } from '@lion/ui/core.js';
|
|
2
3
|
import { html } from 'lit/static-html.js';
|
|
3
4
|
import { LitElement } from 'lit';
|
|
4
|
-
import
|
|
5
|
+
import sinon from 'sinon';
|
|
5
6
|
|
|
6
7
|
describe('DisabledMixin', () => {
|
|
7
8
|
class CanBeDisabled extends DisabledMixin(LitElement) {}
|
|
@@ -84,4 +85,24 @@ describe('DisabledMixin', () => {
|
|
|
84
85
|
el.retractRequestToBeDisabled();
|
|
85
86
|
expect(el.disabled).to.be.false;
|
|
86
87
|
});
|
|
88
|
+
|
|
89
|
+
it('will not perform click when disabled', async () => {
|
|
90
|
+
const el = /** @type {CanBeDisabled} */ (
|
|
91
|
+
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
|
92
|
+
);
|
|
93
|
+
const spy = sinon.spy();
|
|
94
|
+
el.addEventListener('click', spy);
|
|
95
|
+
|
|
96
|
+
el.click();
|
|
97
|
+
|
|
98
|
+
expect(spy.callCount).to.equal(1);
|
|
99
|
+
|
|
100
|
+
el.disabled = true;
|
|
101
|
+
el.click();
|
|
102
|
+
expect(spy.callCount).to.equal(1);
|
|
103
|
+
|
|
104
|
+
el.disabled = false;
|
|
105
|
+
el.click();
|
|
106
|
+
expect(spy.callCount).to.equal(2);
|
|
107
|
+
});
|
|
87
108
|
});
|
|
@@ -273,13 +273,13 @@ describe('SlotMixin', () => {
|
|
|
273
273
|
|
|
274
274
|
el._focusableNode._buttonNode.focus();
|
|
275
275
|
|
|
276
|
-
expect(el._focusableNode.
|
|
276
|
+
expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
|
|
277
277
|
|
|
278
278
|
el.currentValue = 1;
|
|
279
279
|
await el.updateComplete;
|
|
280
280
|
|
|
281
281
|
expect(isActiveElement(el._focusableNode)).to.be.true;
|
|
282
|
-
expect(el._focusableNode.
|
|
282
|
+
expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
it('allows for rerendering complex shadow root into slot as a direct child', async () => {
|
|
@@ -342,7 +342,7 @@ describe('SlotMixin', () => {
|
|
|
342
342
|
await el.updateComplete;
|
|
343
343
|
|
|
344
344
|
expect(isActiveElement(el._focusableNode)).to.be.true;
|
|
345
|
-
expect(el._focusableNode.
|
|
345
|
+
expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
describe('firstRenderOnConnected (for backwards compatibility)', () => {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { LitElement } from 'lit';
|
|
2
1
|
import { defineCE, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing';
|
|
3
|
-
import sinon from 'sinon';
|
|
4
2
|
import { FocusMixin } from '@lion/ui/form-core.js';
|
|
3
|
+
import { LitElement } from 'lit';
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
|
|
6
|
+
import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
|
|
5
7
|
|
|
6
8
|
const windowWithOptionalPolyfill =
|
|
7
9
|
/** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
|
|
@@ -84,9 +86,9 @@ describe('FocusMixin', () => {
|
|
|
84
86
|
const { _focusableNode } = el;
|
|
85
87
|
|
|
86
88
|
el.focus();
|
|
87
|
-
expect(
|
|
89
|
+
expect(isActiveElement(_focusableNode)).to.be.true;
|
|
88
90
|
el.blur();
|
|
89
|
-
expect(
|
|
91
|
+
expect(isActiveElement(_focusableNode)).to.be.false;
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
it('has an attribute focused when focused', async () => {
|
|
@@ -51,7 +51,7 @@ export function runNativeTextFieldMixinSuite(customConfig) {
|
|
|
51
51
|
expect(_inputNode.selectionEnd).to.equal(2);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('
|
|
54
|
+
it('moves focus to a next focusable element after writing some text', async () => {
|
|
55
55
|
const el = /** @type {NativeTextFieldClass} */ (await fixture(html`<${tag}></${tag}>`));
|
|
56
56
|
// @ts-ignore [allow-protected] in test
|
|
57
57
|
const setValueAndPreserveCaretSpy = spy(el, '_setValueAndPreserveCaret');
|
|
@@ -59,12 +59,8 @@ export function runNativeTextFieldMixinSuite(customConfig) {
|
|
|
59
59
|
await triggerFocusFor(el);
|
|
60
60
|
await el.updateComplete;
|
|
61
61
|
expect(isActiveElement(_inputNode)).to.be.true;
|
|
62
|
-
await sendKeys({
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
await sendKeys({
|
|
66
|
-
press: 'Tab',
|
|
67
|
-
});
|
|
62
|
+
await sendKeys({ press: 'h' });
|
|
63
|
+
await sendKeys({ press: 'Tab' });
|
|
68
64
|
|
|
69
65
|
expect(setValueAndPreserveCaretSpy.calledOnce).to.be.false;
|
|
70
66
|
|
|
@@ -73,7 +69,7 @@ export function runNativeTextFieldMixinSuite(customConfig) {
|
|
|
73
69
|
return;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
expect(
|
|
72
|
+
expect(isActiveElement(_inputNode)).to.be.false;
|
|
77
73
|
});
|
|
78
74
|
});
|
|
79
75
|
}
|
|
@@ -48,7 +48,7 @@ describe('Form Integrations', () => {
|
|
|
48
48
|
notifications: { value: '', checked: false },
|
|
49
49
|
rsvp: '',
|
|
50
50
|
tel: '',
|
|
51
|
-
'tel-dropdown': '
|
|
51
|
+
'tel-dropdown': '',
|
|
52
52
|
comments: '',
|
|
53
53
|
});
|
|
54
54
|
});
|
|
@@ -82,7 +82,7 @@ describe('Form Integrations', () => {
|
|
|
82
82
|
notifications: '',
|
|
83
83
|
rsvp: '',
|
|
84
84
|
tel: '',
|
|
85
|
-
'tel-dropdown': '
|
|
85
|
+
'tel-dropdown': '',
|
|
86
86
|
comments: '',
|
|
87
87
|
});
|
|
88
88
|
});
|
|
@@ -21,6 +21,19 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
21
21
|
.input-group__container > .input-group__input ::slotted(.form-control) {
|
|
22
22
|
text-align: center;
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
.input-stepper__value {
|
|
26
|
+
position: absolute;
|
|
27
|
+
width: 1px;
|
|
28
|
+
height: 1px;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
clip-path: inset(100%);
|
|
31
|
+
clip: rect(1px, 1px, 1px, 1px);
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
border: 0;
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
}
|
|
24
37
|
`,
|
|
25
38
|
];
|
|
26
39
|
}
|
|
@@ -36,6 +49,9 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
36
49
|
type: Number,
|
|
37
50
|
reflect: true,
|
|
38
51
|
},
|
|
52
|
+
valueTextMapping: {
|
|
53
|
+
type: Object,
|
|
54
|
+
},
|
|
39
55
|
step: {
|
|
40
56
|
type: Number,
|
|
41
57
|
reflect: true,
|
|
@@ -66,6 +82,11 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
66
82
|
this.formatter = formatNumber;
|
|
67
83
|
this.min = Infinity;
|
|
68
84
|
this.max = Infinity;
|
|
85
|
+
/**
|
|
86
|
+
* The aria-valuetext attribute defines the human-readable text alternative of aria-valuenow.
|
|
87
|
+
* @type {{[key: number]: string}}
|
|
88
|
+
*/
|
|
89
|
+
this.valueTextMapping = {};
|
|
69
90
|
this.step = 1;
|
|
70
91
|
this.values = {
|
|
71
92
|
max: this.max,
|
|
@@ -75,7 +96,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
75
96
|
|
|
76
97
|
this.__increment = this.__increment.bind(this);
|
|
77
98
|
this.__decrement = this.__decrement.bind(this);
|
|
78
|
-
this.
|
|
99
|
+
this._onEnterButton = this._onEnterButton.bind(this);
|
|
100
|
+
this._onLeaveButton = this._onLeaveButton.bind(this);
|
|
79
101
|
}
|
|
80
102
|
|
|
81
103
|
connectedCallback() {
|
|
@@ -85,11 +107,12 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
85
107
|
min: this.min,
|
|
86
108
|
step: this.step,
|
|
87
109
|
};
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
if (this._inputNode) {
|
|
111
|
+
this._inputNode.role = 'spinbutton';
|
|
112
|
+
this._inputNode.setAttribute('inputmode', 'decimal');
|
|
113
|
+
this._inputNode.setAttribute('autocomplete', 'off');
|
|
114
|
+
}
|
|
90
115
|
this.addEventListener('keydown', this.__keyDownHandler);
|
|
91
|
-
this._inputNode.setAttribute('inputmode', 'decimal');
|
|
92
|
-
this._inputNode.setAttribute('autocomplete', 'off');
|
|
93
116
|
this.__setDefaultValidators();
|
|
94
117
|
this.__toggleSpinnerButtonsState();
|
|
95
118
|
}
|
|
@@ -110,27 +133,33 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
110
133
|
if (changedProperties.has('min')) {
|
|
111
134
|
this._inputNode.min = `${this.min}`;
|
|
112
135
|
this.values.min = this.min;
|
|
136
|
+
if (this.min !== Infinity) {
|
|
137
|
+
this._inputNode.setAttribute('aria-valuemin', `${this.min}`);
|
|
138
|
+
} else {
|
|
139
|
+
this._inputNode.removeAttribute('aria-valuemin');
|
|
140
|
+
}
|
|
113
141
|
this.__toggleSpinnerButtonsState();
|
|
114
142
|
}
|
|
115
143
|
|
|
116
144
|
if (changedProperties.has('max')) {
|
|
117
145
|
this._inputNode.max = `${this.max}`;
|
|
118
146
|
this.values.max = this.max;
|
|
147
|
+
if (this.max !== Infinity) {
|
|
148
|
+
this._inputNode.setAttribute('aria-valuemax', `${this.max}`);
|
|
149
|
+
} else {
|
|
150
|
+
this._inputNode.removeAttribute('aria-valuemax');
|
|
151
|
+
}
|
|
119
152
|
this.__toggleSpinnerButtonsState();
|
|
120
153
|
}
|
|
121
154
|
|
|
155
|
+
if (changedProperties.has('valueTextMapping')) {
|
|
156
|
+
this._updateAriaAttributes();
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
if (changedProperties.has('step')) {
|
|
123
160
|
this._inputNode.step = `${this.step}`;
|
|
124
161
|
this.values.step = this.step;
|
|
125
162
|
}
|
|
126
|
-
|
|
127
|
-
if (changedProperties.has('_ariaLabelledNodes')) {
|
|
128
|
-
this.__reflectAriaAttrToSpinButton('aria-labelledby', this._ariaLabelledNodes);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (changedProperties.has('_ariaDescribedNodes')) {
|
|
132
|
-
this.__reflectAriaAttrToSpinButton('aria-describedby', this._ariaDescribedNodes);
|
|
133
|
-
}
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
get slots() {
|
|
@@ -141,22 +170,6 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
141
170
|
};
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
/**
|
|
145
|
-
* Based on FormControlMixin __reflectAriaAttr()
|
|
146
|
-
*
|
|
147
|
-
* Will handle help text, validation feedback and character counter,
|
|
148
|
-
* prefix/suffix/before/after (if they contain data-description flag attr).
|
|
149
|
-
* Also, contents of id references that will be put in the <lion-field>._ariaDescribedby property
|
|
150
|
-
* from an external context, will be read by a screen reader.
|
|
151
|
-
* @param {string} attrName
|
|
152
|
-
* @param {Element[]} nodes
|
|
153
|
-
* @private
|
|
154
|
-
*/
|
|
155
|
-
__reflectAriaAttrToSpinButton(attrName, nodes) {
|
|
156
|
-
const string = nodes.map(n => n.id).join(' ');
|
|
157
|
-
this.setAttribute(attrName, string);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
173
|
/**
|
|
161
174
|
* Set aria labels and apply validators
|
|
162
175
|
* @private
|
|
@@ -193,8 +206,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
193
206
|
*/
|
|
194
207
|
__toggleSpinnerButtonsState() {
|
|
195
208
|
const { min, max } = this.values;
|
|
196
|
-
const decrementButton = this.__getSlot('prefix');
|
|
197
|
-
const incrementButton = this.__getSlot('suffix');
|
|
209
|
+
const decrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('prefix'));
|
|
210
|
+
const incrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('suffix'));
|
|
198
211
|
const disableIncrementor = this.currentValue >= max && max !== Infinity;
|
|
199
212
|
const disableDecrementor = this.currentValue <= min && min !== Infinity;
|
|
200
213
|
if (
|
|
@@ -205,7 +218,32 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
205
218
|
}
|
|
206
219
|
decrementButton[disableDecrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
|
207
220
|
incrementButton[disableIncrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
|
208
|
-
this.
|
|
221
|
+
this._updateAriaAttributes();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @protected
|
|
226
|
+
*/
|
|
227
|
+
_updateAriaAttributes() {
|
|
228
|
+
const displayValue = this._inputNode.value;
|
|
229
|
+
if (displayValue) {
|
|
230
|
+
this._inputNode.setAttribute('aria-valuenow', `${displayValue}`);
|
|
231
|
+
if (
|
|
232
|
+
Object.keys(this.valueTextMapping).length !== 0 &&
|
|
233
|
+
Object.keys(this.valueTextMapping).find(key => Number(key) === this.currentValue)
|
|
234
|
+
) {
|
|
235
|
+
this.__valueText = this.valueTextMapping[this.currentValue];
|
|
236
|
+
} else {
|
|
237
|
+
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
|
238
|
+
// This can be fixed by setting valuetext to the same value as valuenow
|
|
239
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
|
240
|
+
this.__valueText = displayValue;
|
|
241
|
+
}
|
|
242
|
+
this._inputNode.setAttribute('aria-valuetext', `${this.__valueText}`);
|
|
243
|
+
} else {
|
|
244
|
+
this._inputNode.removeAttribute('aria-valuenow');
|
|
245
|
+
this._inputNode.removeAttribute('aria-valuetext');
|
|
246
|
+
}
|
|
209
247
|
}
|
|
210
248
|
|
|
211
249
|
/**
|
|
@@ -326,7 +364,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
326
364
|
<button
|
|
327
365
|
?disabled=${this.disabled || this.readOnly}
|
|
328
366
|
@click=${this.__decrement}
|
|
329
|
-
@
|
|
367
|
+
@focus=${this._onEnterButton}
|
|
368
|
+
@blur=${this._onLeaveButton}
|
|
330
369
|
type="button"
|
|
331
370
|
aria-label="${this.msgLit('lion-input-stepper:decrease')}"
|
|
332
371
|
>
|
|
@@ -345,7 +384,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
345
384
|
<button
|
|
346
385
|
?disabled=${this.disabled || this.readOnly}
|
|
347
386
|
@click=${this.__increment}
|
|
348
|
-
@
|
|
387
|
+
@focus=${this._onEnterButton}
|
|
388
|
+
@blur=${this._onLeaveButton}
|
|
349
389
|
type="button"
|
|
350
390
|
aria-label="${this.msgLit('lion-input-stepper:increase')}"
|
|
351
391
|
>
|
|
@@ -354,6 +394,33 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
354
394
|
`;
|
|
355
395
|
}
|
|
356
396
|
|
|
397
|
+
/** @protected */
|
|
398
|
+
_inputGroupTemplate() {
|
|
399
|
+
return html`
|
|
400
|
+
<div class="input-stepper__value">${this.__valueText}</div>
|
|
401
|
+
<div class="input-group">
|
|
402
|
+
${this._inputGroupBeforeTemplate()}
|
|
403
|
+
<div class="input-group__container">
|
|
404
|
+
${this._inputGroupPrefixTemplate()} ${this._inputGroupInputTemplate()}
|
|
405
|
+
${this._inputGroupSuffixTemplate()}
|
|
406
|
+
</div>
|
|
407
|
+
${this._inputGroupAfterTemplate()}
|
|
408
|
+
</div>
|
|
409
|
+
`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @protected
|
|
414
|
+
* @param {Event} ev
|
|
415
|
+
*/
|
|
416
|
+
// eslint-disable-next-line no-unused-vars
|
|
417
|
+
_onEnterButton(ev) {
|
|
418
|
+
const valueNode = /** @type {HTMLElement} */ (
|
|
419
|
+
this.shadowRoot?.querySelector('.input-stepper__value')
|
|
420
|
+
);
|
|
421
|
+
valueNode.setAttribute('aria-live', 'assertive');
|
|
422
|
+
}
|
|
423
|
+
|
|
357
424
|
/**
|
|
358
425
|
* Redispatch leave event on host when catching leave event
|
|
359
426
|
* on the incrementor and decrementor button.
|
|
@@ -363,8 +430,16 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
363
430
|
*
|
|
364
431
|
* Interacting with the buttons is "user interactions"
|
|
365
432
|
* the same way as focusing + blurring the field (native input)
|
|
433
|
+
*
|
|
434
|
+
* @protected
|
|
435
|
+
* @param {Event} ev
|
|
366
436
|
*/
|
|
367
|
-
|
|
437
|
+
// eslint-disable-next-line no-unused-vars
|
|
438
|
+
_onLeaveButton(ev) {
|
|
439
|
+
const valueNode = /** @type {HTMLElement} */ (
|
|
440
|
+
this.shadowRoot?.querySelector('.input-stepper__value')
|
|
441
|
+
);
|
|
442
|
+
valueNode.removeAttribute('aria-live');
|
|
368
443
|
this.dispatchEvent(new Event(this._leaveEvent));
|
|
369
444
|
}
|
|
370
445
|
}
|
|
@@ -278,11 +278,106 @@ describe('<lion-input-stepper>', () => {
|
|
|
278
278
|
await expect(el).to.be.accessible();
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
it('has role="spinbutton"', async () => {
|
|
282
|
+
const el = await fixture(defaultInputStepper);
|
|
283
|
+
expect(el._inputNode.hasAttribute('role')).to.be.true;
|
|
284
|
+
expect(el._inputNode.getAttribute('role')).to.equal('spinbutton');
|
|
285
|
+
});
|
|
286
|
+
|
|
281
287
|
it('updates aria-valuenow when stepper is changed', async () => {
|
|
282
288
|
const el = await fixture(defaultInputStepper);
|
|
289
|
+
el.modelValue = 1;
|
|
290
|
+
|
|
291
|
+
await el.updateComplete;
|
|
292
|
+
expect(el._inputNode.hasAttribute('aria-valuenow')).to.be.true;
|
|
293
|
+
expect(el._inputNode.getAttribute('aria-valuenow')).to.equal('1');
|
|
294
|
+
|
|
295
|
+
el.modelValue = '';
|
|
296
|
+
await el.updateComplete;
|
|
297
|
+
expect(el._inputNode.hasAttribute('aria-valuenow')).to.be.false;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('updates aria-valuetext when stepper is changed', async () => {
|
|
301
|
+
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
|
302
|
+
// This can be fixed by setting valuetext to the same value as valuenow
|
|
303
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
|
304
|
+
const el = await fixture(defaultInputStepper);
|
|
305
|
+
el.modelValue = 1;
|
|
306
|
+
await el.updateComplete;
|
|
307
|
+
|
|
308
|
+
expect(el._inputNode.hasAttribute('aria-valuetext')).to.be.true;
|
|
309
|
+
expect(el._inputNode.getAttribute('aria-valuetext')).to.equal('1');
|
|
310
|
+
|
|
311
|
+
el.modelValue = '';
|
|
312
|
+
await el.updateComplete;
|
|
313
|
+
expect(el._inputNode.hasAttribute('aria-valuetext')).to.be.false;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('can give aria-valuetext to override default value as a human-readable text alternative', async () => {
|
|
317
|
+
const values = {
|
|
318
|
+
1: 'first',
|
|
319
|
+
2: 'second',
|
|
320
|
+
3: 'third',
|
|
321
|
+
};
|
|
322
|
+
const el = await fixture(html`
|
|
323
|
+
<lion-input-stepper min="1" max="3" .valueTextMapping="${values}"></lion-input-stepper>
|
|
324
|
+
`);
|
|
325
|
+
el.modelValue = 1;
|
|
326
|
+
await el.updateComplete;
|
|
327
|
+
expect(el._inputNode.hasAttribute('aria-valuetext')).to.be.true;
|
|
328
|
+
expect(el._inputNode.getAttribute('aria-valuetext')).to.equal('first');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('updates aria-valuemin when stepper is changed', async () => {
|
|
332
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
283
333
|
const incrementButton = el.querySelector('[slot=suffix]');
|
|
284
334
|
incrementButton?.dispatchEvent(new Event('click'));
|
|
285
|
-
expect(el
|
|
335
|
+
expect(el._inputNode.hasAttribute('aria-valuemin')).to.be.true;
|
|
336
|
+
expect(el._inputNode.getAttribute('aria-valuemin')).to.equal('100');
|
|
337
|
+
|
|
338
|
+
el.min = 0;
|
|
339
|
+
await el.updateComplete;
|
|
340
|
+
expect(el._inputNode.hasAttribute('aria-valuemin')).to.be.true;
|
|
341
|
+
expect(el._inputNode.getAttribute('aria-valuemin')).to.equal('0');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('updates aria-valuemax when stepper is changed', async () => {
|
|
345
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
346
|
+
const incrementButton = el.querySelector('[slot=suffix]');
|
|
347
|
+
incrementButton?.dispatchEvent(new Event('click'));
|
|
348
|
+
expect(el._inputNode.hasAttribute('aria-valuemax')).to.be.true;
|
|
349
|
+
expect(el._inputNode.getAttribute('aria-valuemax')).to.equal('200');
|
|
350
|
+
|
|
351
|
+
el.max = 1000;
|
|
352
|
+
await el.updateComplete;
|
|
353
|
+
expect(el._inputNode.hasAttribute('aria-valuemax')).to.be.true;
|
|
354
|
+
expect(el._inputNode.getAttribute('aria-valuemax')).to.equal('1000');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('when decrease button gets focus, it sets aria-live to input-stepper__value', async () => {
|
|
358
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
359
|
+
const stepperValue = el.shadowRoot?.querySelector('.input-stepper__value');
|
|
360
|
+
const decrementButton = el.querySelector('[slot=prefix]');
|
|
361
|
+
|
|
362
|
+
decrementButton?.dispatchEvent(new Event('focus'));
|
|
363
|
+
expect(stepperValue?.hasAttribute('aria-live')).to.be.true;
|
|
364
|
+
expect(stepperValue?.getAttribute('aria-live')).to.equal('assertive');
|
|
365
|
+
|
|
366
|
+
decrementButton?.dispatchEvent(new Event('blur'));
|
|
367
|
+
expect(stepperValue?.hasAttribute('aria-live')).to.be.false;
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('when increase button gets focus, it sets aria-live to input-stepper__value', async () => {
|
|
371
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
372
|
+
const stepperValue = el.shadowRoot?.querySelector('.input-stepper__value');
|
|
373
|
+
const incrementButton = el.querySelector('[slot=suffix]');
|
|
374
|
+
|
|
375
|
+
incrementButton?.dispatchEvent(new Event('focus'));
|
|
376
|
+
expect(stepperValue?.hasAttribute('aria-live')).to.be.true;
|
|
377
|
+
expect(stepperValue?.getAttribute('aria-live')).to.equal('assertive');
|
|
378
|
+
|
|
379
|
+
incrementButton?.dispatchEvent(new Event('blur'));
|
|
380
|
+
expect(stepperValue?.hasAttribute('aria-live')).to.be.false;
|
|
286
381
|
});
|
|
287
382
|
});
|
|
288
383
|
});
|
|
@@ -158,11 +158,11 @@ export class LionInputTelDropdown extends LionInputTel {
|
|
|
158
158
|
super.styles,
|
|
159
159
|
css`
|
|
160
160
|
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
* We need to align the height of the dropdown with the height of the text field.
|
|
162
|
+
* We target the HTMLDivElement (render wrapper from SlotMixin) here. Its child,
|
|
163
|
+
* [data-ref=dropdown], receives a 100% height as well via inline styles (since we
|
|
164
|
+
* can't target from shadow styles).
|
|
165
|
+
*/
|
|
166
166
|
::slotted([slot='prefix']) {
|
|
167
167
|
height: 100%;
|
|
168
168
|
}
|
|
@@ -301,7 +301,8 @@ export class LionInputTelDropdown extends LionInputTel {
|
|
|
301
301
|
} else {
|
|
302
302
|
this.__initializedRegionCode = `+${countryCode}`;
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
// on init we set the value to the _inputNode and not the modelValue to prevent validation
|
|
305
|
+
this._inputNode.value = this.__initializedRegionCode;
|
|
305
306
|
this._initialModelValue = this.__initializedRegionCode;
|
|
306
307
|
this.initInteractionState();
|
|
307
308
|
}
|