@lion/ui 0.8.7 → 0.9.0
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/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/input-stepper/src/LionInputStepper.js +50 -3
- package/components/input-stepper/test/lion-input-stepper.test.js +60 -1
- package/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +12 -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/pagination/src/LionPagination.js +1 -0
- package/components/pagination/test/lion-pagination.test.js +16 -0
- 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 +471 -30
- 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 +11 -0
- package/dist-types/components/input-stepper/src/LionInputStepper.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/pagination/src/LionPagination.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 +4 -0
- 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
|
@@ -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
|
}
|
|
@@ -36,6 +36,9 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
36
36
|
type: Number,
|
|
37
37
|
reflect: true,
|
|
38
38
|
},
|
|
39
|
+
valueTextMapping: {
|
|
40
|
+
type: Object,
|
|
41
|
+
},
|
|
39
42
|
step: {
|
|
40
43
|
type: Number,
|
|
41
44
|
reflect: true,
|
|
@@ -66,6 +69,11 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
66
69
|
this.formatter = formatNumber;
|
|
67
70
|
this.min = Infinity;
|
|
68
71
|
this.max = Infinity;
|
|
72
|
+
/**
|
|
73
|
+
* The aria-valuetext attribute defines the human-readable text alternative of aria-valuenow.
|
|
74
|
+
* @type {{[key: number]: string}}
|
|
75
|
+
*/
|
|
76
|
+
this.valueTextMapping = {};
|
|
69
77
|
this.step = 1;
|
|
70
78
|
this.values = {
|
|
71
79
|
max: this.max,
|
|
@@ -110,15 +118,29 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
110
118
|
if (changedProperties.has('min')) {
|
|
111
119
|
this._inputNode.min = `${this.min}`;
|
|
112
120
|
this.values.min = this.min;
|
|
121
|
+
if (this.min !== Infinity) {
|
|
122
|
+
this.setAttribute('aria-valuemin', `${this.min}`);
|
|
123
|
+
} else {
|
|
124
|
+
this.removeAttribute('aria-valuemin');
|
|
125
|
+
}
|
|
113
126
|
this.__toggleSpinnerButtonsState();
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
if (changedProperties.has('max')) {
|
|
117
130
|
this._inputNode.max = `${this.max}`;
|
|
118
131
|
this.values.max = this.max;
|
|
132
|
+
if (this.max !== Infinity) {
|
|
133
|
+
this.setAttribute('aria-valuemax', `${this.max}`);
|
|
134
|
+
} else {
|
|
135
|
+
this.removeAttribute('aria-valuemax');
|
|
136
|
+
}
|
|
119
137
|
this.__toggleSpinnerButtonsState();
|
|
120
138
|
}
|
|
121
139
|
|
|
140
|
+
if (changedProperties.has('valueTextMapping')) {
|
|
141
|
+
this._updateAriaAttributes();
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
if (changedProperties.has('step')) {
|
|
123
145
|
this._inputNode.step = `${this.step}`;
|
|
124
146
|
this.values.step = this.step;
|
|
@@ -193,8 +215,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
193
215
|
*/
|
|
194
216
|
__toggleSpinnerButtonsState() {
|
|
195
217
|
const { min, max } = this.values;
|
|
196
|
-
const decrementButton = this.__getSlot('prefix');
|
|
197
|
-
const incrementButton = this.__getSlot('suffix');
|
|
218
|
+
const decrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('prefix'));
|
|
219
|
+
const incrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('suffix'));
|
|
198
220
|
const disableIncrementor = this.currentValue >= max && max !== Infinity;
|
|
199
221
|
const disableDecrementor = this.currentValue <= min && min !== Infinity;
|
|
200
222
|
if (
|
|
@@ -205,7 +227,32 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|
|
205
227
|
}
|
|
206
228
|
decrementButton[disableDecrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
|
207
229
|
incrementButton[disableIncrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
|
208
|
-
|
|
230
|
+
|
|
231
|
+
this._updateAriaAttributes();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @protected
|
|
236
|
+
*/
|
|
237
|
+
_updateAriaAttributes() {
|
|
238
|
+
const displayValue = this._inputNode.value;
|
|
239
|
+
if (displayValue) {
|
|
240
|
+
this.setAttribute('aria-valuenow', `${displayValue}`);
|
|
241
|
+
if (
|
|
242
|
+
Object.keys(this.valueTextMapping).length !== 0 &&
|
|
243
|
+
Object.keys(this.valueTextMapping).find(key => Number(key) === this.currentValue)
|
|
244
|
+
) {
|
|
245
|
+
this.setAttribute('aria-valuetext', `${this.valueTextMapping[this.currentValue]}`);
|
|
246
|
+
} else {
|
|
247
|
+
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
|
248
|
+
// This can be fixed by setting valuetext to the same value as valuenow
|
|
249
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
|
250
|
+
this.setAttribute('aria-valuetext', `${displayValue}`);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
this.removeAttribute('aria-valuenow');
|
|
254
|
+
this.removeAttribute('aria-valuetext');
|
|
255
|
+
}
|
|
209
256
|
}
|
|
210
257
|
|
|
211
258
|
/**
|
|
@@ -280,9 +280,68 @@ describe('<lion-input-stepper>', () => {
|
|
|
280
280
|
|
|
281
281
|
it('updates aria-valuenow when stepper is changed', async () => {
|
|
282
282
|
const el = await fixture(defaultInputStepper);
|
|
283
|
+
el.modelValue = 1;
|
|
284
|
+
|
|
285
|
+
await el.updateComplete;
|
|
286
|
+
expect(el.hasAttribute('aria-valuenow')).to.be.true;
|
|
287
|
+
expect(el.getAttribute('aria-valuenow')).to.equal('1');
|
|
288
|
+
|
|
289
|
+
el.modelValue = '';
|
|
290
|
+
await el.updateComplete;
|
|
291
|
+
expect(el.hasAttribute('aria-valuenow')).to.be.false;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('updates aria-valuetext when stepper is changed', async () => {
|
|
295
|
+
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
|
296
|
+
// This can be fixed by setting valuetext to the same value as valuenow
|
|
297
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
|
298
|
+
const el = await fixture(defaultInputStepper);
|
|
299
|
+
el.modelValue = 1;
|
|
300
|
+
await el.updateComplete;
|
|
301
|
+
|
|
302
|
+
expect(el.hasAttribute('aria-valuetext')).to.be.true;
|
|
303
|
+
expect(el.getAttribute('aria-valuetext')).to.equal('1');
|
|
304
|
+
|
|
305
|
+
el.modelValue = '';
|
|
306
|
+
await el.updateComplete;
|
|
307
|
+
expect(el.hasAttribute('aria-valuetext')).to.be.false;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('can give aria-valuetext to override default value as a human-readable text alternative', async () => {
|
|
311
|
+
const values = {
|
|
312
|
+
1: 'first',
|
|
313
|
+
2: 'second',
|
|
314
|
+
3: 'third',
|
|
315
|
+
};
|
|
316
|
+
const el = await fixture(html`
|
|
317
|
+
<lion-input-stepper min="1" max="3" .valueTextMapping="${values}"></lion-input-stepper>
|
|
318
|
+
`);
|
|
319
|
+
el.modelValue = 1;
|
|
320
|
+
await el.updateComplete;
|
|
321
|
+
expect(el.hasAttribute('aria-valuetext')).to.be.true;
|
|
322
|
+
expect(el.getAttribute('aria-valuetext')).to.equal('first');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('updates aria-valuemin when stepper is changed', async () => {
|
|
326
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
327
|
+
const incrementButton = el.querySelector('[slot=suffix]');
|
|
328
|
+
incrementButton?.dispatchEvent(new Event('click'));
|
|
329
|
+
expect(el).to.have.attribute('aria-valuemin', '100');
|
|
330
|
+
|
|
331
|
+
el.min = 0;
|
|
332
|
+
await el.updateComplete;
|
|
333
|
+
expect(el).to.have.attribute('aria-valuemin', '0');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('updates aria-valuemax when stepper is changed', async () => {
|
|
337
|
+
const el = await fixture(inputStepperWithAttrs);
|
|
283
338
|
const incrementButton = el.querySelector('[slot=suffix]');
|
|
284
339
|
incrementButton?.dispatchEvent(new Event('click'));
|
|
285
|
-
expect(el).to.have.attribute('aria-
|
|
340
|
+
expect(el).to.have.attribute('aria-valuemax', '200');
|
|
341
|
+
|
|
342
|
+
el.max = 1000;
|
|
343
|
+
await el.updateComplete;
|
|
344
|
+
expect(el).to.have.attribute('aria-valuemax', '1000');
|
|
286
345
|
});
|
|
287
346
|
});
|
|
288
347
|
});
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
+
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
|
2
|
+
import { runInputTelDropdownSuite } from '@lion/ui/input-tel-dropdown-test-suites.js';
|
|
3
|
+
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
|
1
4
|
import { runInputTelSuite } from '@lion/ui/input-tel-test-suites.js';
|
|
5
|
+
import { aTimeout, expect, fixture } from '@open-wc/testing';
|
|
6
|
+
import { LionSelectRich } from '@lion/ui/select-rich.js';
|
|
2
7
|
import { repeat } from 'lit/directives/repeat.js';
|
|
8
|
+
import { LionOption } from '@lion/ui/listbox.js';
|
|
3
9
|
import { ref } from 'lit/directives/ref.js';
|
|
4
10
|
import { html } from 'lit';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import { LionOption } from '@lion/ui/listbox.js';
|
|
8
|
-
import { LionSelectRich } from '@lion/ui/select-rich.js';
|
|
9
|
-
import { runInputTelDropdownSuite } from '@lion/ui/input-tel-dropdown-test-suites.js';
|
|
10
|
-
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
|
11
|
+
|
|
12
|
+
import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
|
|
11
13
|
import { ScopedElementsMixin } from '../../core/src/ScopedElementsMixin.js';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* @typedef {import('lit').TemplateResult} TemplateResult
|
|
15
|
-
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
|
16
16
|
* @typedef {import('../types/index.js').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
|
17
|
+
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
|
17
18
|
* @typedef {import('../types/index.js').RegionMeta} RegionMeta
|
|
19
|
+
* @typedef {import('lit').TemplateResult} TemplateResult
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
class WithFormControlInputTelDropdown extends ScopedElementsMixin(LionInputTelDropdown) {
|
|
@@ -114,7 +116,7 @@ describe('WithFormControlInputTelDropdown', () => {
|
|
|
114
116
|
await el.updateComplete;
|
|
115
117
|
await aTimeout(0);
|
|
116
118
|
// @ts-expect-error [allow-protected-in-tests]
|
|
117
|
-
expect(el._inputNode).to.
|
|
119
|
+
expect(isActiveElement(el._inputNode)).to.be.true;
|
|
118
120
|
}
|
|
119
121
|
});
|
|
120
122
|
|
|
@@ -167,7 +169,7 @@ describe('WithFormControlInputTelDropdown', () => {
|
|
|
167
169
|
await el.updateComplete;
|
|
168
170
|
await aTimeout(0);
|
|
169
171
|
// @ts-expect-error [allow-protected-in-tests]
|
|
170
|
-
expect(el._inputNode).to.
|
|
172
|
+
expect(isActiveElement(el._inputNode)).to.be.false;
|
|
171
173
|
}
|
|
172
174
|
});
|
|
173
175
|
});
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { PhoneUtilManager } from '@lion/ui/input-tel.js';
|
|
2
1
|
import { mockPhoneUtilManager, restorePhoneUtilManager } from '@lion/ui/input-tel-test-helpers.js';
|
|
2
|
+
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
|
3
|
+
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
|
4
|
+
import { PhoneUtilManager } from '@lion/ui/input-tel.js';
|
|
5
|
+
import sinon from 'sinon';
|
|
3
6
|
import {
|
|
7
|
+
fixtureSync as _fixtureSync,
|
|
8
|
+
fixture as _fixture,
|
|
9
|
+
unsafeStatic,
|
|
4
10
|
aTimeout,
|
|
5
11
|
defineCE,
|
|
6
12
|
expect,
|
|
7
|
-
fixture as _fixture,
|
|
8
|
-
fixtureSync as _fixtureSync,
|
|
9
13
|
html,
|
|
10
|
-
unsafeStatic,
|
|
11
14
|
} from '@open-wc/testing';
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
|
15
|
+
|
|
16
|
+
import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
* @typedef {import('lit').TemplateResult} TemplateResult
|
|
18
|
-
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
|
19
19
|
* @typedef {import('../types/index.js').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
|
20
|
+
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
|
21
|
+
* @typedef {import('lit').TemplateResult} TemplateResult
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
const fixture = /** @type {(arg: string | TemplateResult) => Promise<LionInputTelDropdown>} */ (
|
|
@@ -206,12 +208,14 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
|
|
206
208
|
|
|
207
209
|
it('syncs disabled attribute to dropdown', async () => {
|
|
208
210
|
const el = await fixture(html` <${tag} disabled></${tag}> `);
|
|
209
|
-
expect(el.refs.dropdown.value?.hasAttribute('disabled')).to.be
|
|
211
|
+
expect(/** @type {HTMLElement} */ (el.refs.dropdown.value)?.hasAttribute('disabled')).to.be
|
|
212
|
+
.true;
|
|
210
213
|
});
|
|
211
214
|
|
|
212
215
|
it('disables dropdown on readonly', async () => {
|
|
213
216
|
const el = await fixture(html` <${tag} readonly></${tag}> `);
|
|
214
|
-
expect(el.refs.dropdown.value?.hasAttribute('disabled')).to.be
|
|
217
|
+
expect(/** @type {HTMLElement} */ (el.refs.dropdown.value)?.hasAttribute('disabled')).to.be
|
|
218
|
+
.true;
|
|
215
219
|
});
|
|
216
220
|
|
|
217
221
|
it('renders to prefix slot in light dom', async () => {
|
|
@@ -332,7 +336,7 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
|
|
332
336
|
mimicUserChangingDropdown(dropdownElement, 'BE');
|
|
333
337
|
await el.updateComplete;
|
|
334
338
|
// @ts-expect-error [allow-protected-in-tests]
|
|
335
|
-
expect(el._inputNode).to.
|
|
339
|
+
expect(isActiveElement(el._inputNode)).to.be.false;
|
|
336
340
|
});
|
|
337
341
|
});
|
|
338
342
|
|
|
@@ -1121,6 +1121,15 @@ export class OverlayController extends EventTarget {
|
|
|
1121
1121
|
if (this.manager) {
|
|
1122
1122
|
this.manager.disableTrapsKeyboardFocusForAll();
|
|
1123
1123
|
}
|
|
1124
|
+
|
|
1125
|
+
const isContentShadowHost = Boolean(this.contentNode.shadowRoot);
|
|
1126
|
+
if (isContentShadowHost) {
|
|
1127
|
+
// eslint-disable-next-line no-console
|
|
1128
|
+
console.warn(
|
|
1129
|
+
'[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root',
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1124
1133
|
this._containFocusHandler = containFocus(this.contentNode);
|
|
1125
1134
|
this.__hasActiveTrapsKeyboardFocus = true;
|
|
1126
1135
|
if (this.manager) {
|
|
@@ -603,8 +603,8 @@ describe('OverlayController', () => {
|
|
|
603
603
|
event.keyCode = keyCodes.tab;
|
|
604
604
|
window.dispatchEvent(event);
|
|
605
605
|
|
|
606
|
-
expect(elOutside).to.
|
|
607
|
-
expect(input1).to.
|
|
606
|
+
expect(isActiveElement(elOutside)).to.be.false;
|
|
607
|
+
expect(isActiveElement(input1)).to.be.true;
|
|
608
608
|
});
|
|
609
609
|
|
|
610
610
|
it('allows to move the focus outside of the overlay if trapsKeyboardFocus is disabled', async () => {
|
|
@@ -625,7 +625,7 @@ describe('OverlayController', () => {
|
|
|
625
625
|
input.focus();
|
|
626
626
|
simulateTab();
|
|
627
627
|
|
|
628
|
-
expect(elOutside).to.
|
|
628
|
+
expect(isActiveElement(elOutside)).to.be.true;
|
|
629
629
|
});
|
|
630
630
|
|
|
631
631
|
it('keeps focus within overlay with multiple overlays with all traps on true', async () => {
|
|
@@ -648,6 +648,30 @@ describe('OverlayController', () => {
|
|
|
648
648
|
expect(ctrl0.hasActiveTrapsKeyboardFocus).to.be.true;
|
|
649
649
|
expect(ctrl1.hasActiveTrapsKeyboardFocus).to.be.false;
|
|
650
650
|
});
|
|
651
|
+
|
|
652
|
+
it('warns when contentNode is a host for a shadowRoot', async () => {
|
|
653
|
+
const warnSpy = sinon.spy(console, 'warn');
|
|
654
|
+
|
|
655
|
+
const contentNode = /** @type {HTMLDivElement} */ (await fixture(html` <div></div> `));
|
|
656
|
+
contentNode.attachShadow({ mode: 'open' });
|
|
657
|
+
const shadowRootContentNode = /** @type {HTMLElement} */ (
|
|
658
|
+
await fixture(html` <div><input id="input1" /><input id="input2" /></div> `)
|
|
659
|
+
);
|
|
660
|
+
contentNode.shadowRoot?.appendChild(shadowRootContentNode);
|
|
661
|
+
|
|
662
|
+
const ctrl = new OverlayController({
|
|
663
|
+
...withGlobalTestConfig(),
|
|
664
|
+
trapsKeyboardFocus: true,
|
|
665
|
+
contentNode,
|
|
666
|
+
});
|
|
667
|
+
await ctrl.show();
|
|
668
|
+
|
|
669
|
+
// @ts-expect-error
|
|
670
|
+
expect(console.warn.args[0][0]).to.equal(
|
|
671
|
+
'[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root',
|
|
672
|
+
);
|
|
673
|
+
warnSpy.restore();
|
|
674
|
+
});
|
|
651
675
|
});
|
|
652
676
|
|
|
653
677
|
describe('hidesOnEsc', () => {
|
|
@@ -287,6 +287,7 @@ export class LionPagination extends LocalizeMixin(LitElement) {
|
|
|
287
287
|
<button
|
|
288
288
|
aria-label="${this.msgLit('lion-pagination:page', { page })}"
|
|
289
289
|
aria-current=${page === this.current}
|
|
290
|
+
aria-live="${page === this.current ? 'polite' : 'off'}"
|
|
290
291
|
@click=${() => this.__fire(page)}
|
|
291
292
|
>
|
|
292
293
|
${page}
|
|
@@ -105,6 +105,22 @@ describe('Pagination', () => {
|
|
|
105
105
|
expect(el.current).to.equal(2);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
it('should announce next and previous page using `next()` and `previous()`', async () => {
|
|
109
|
+
const el = await fixture(html` <lion-pagination count="6" current="2"></lion-pagination> `);
|
|
110
|
+
const buttons = Array.from(
|
|
111
|
+
/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('button'),
|
|
112
|
+
);
|
|
113
|
+
expect(buttons[2].getAttribute('aria-live')).to.equal('polite');
|
|
114
|
+
el.next();
|
|
115
|
+
await el.updateComplete;
|
|
116
|
+
expect(buttons[3].getAttribute('aria-live')).to.equal('polite');
|
|
117
|
+
expect(buttons[2].getAttribute('aria-live')).to.equal('off');
|
|
118
|
+
el.previous();
|
|
119
|
+
await el.updateComplete;
|
|
120
|
+
expect(buttons[3].getAttribute('aria-live')).to.equal('off');
|
|
121
|
+
expect(buttons[2].getAttribute('aria-live')).to.equal('polite');
|
|
122
|
+
});
|
|
123
|
+
|
|
108
124
|
it('should goto next and previous page using `next()` and `previous()`', async () => {
|
|
109
125
|
const el = await fixture(html` <lion-pagination count="6" current="2"></lion-pagination> `);
|
|
110
126
|
el.next();
|
|
@@ -31,10 +31,11 @@ const fixture = /** @type {(arg: TemplateResult) => Promise<LionSelectRich>} */
|
|
|
31
31
|
describe('lion-select-rich', () => {
|
|
32
32
|
it('clicking the label should focus the invoker', async () => {
|
|
33
33
|
const el = await fixture(html` <lion-select-rich label="foo"> </lion-select-rich> `);
|
|
34
|
-
expect(document.
|
|
34
|
+
expect(isActiveElement(document.body)).to.be.true;
|
|
35
|
+
|
|
35
36
|
const { _labelNode, _invokerNode } = getSelectRichMembers(el);
|
|
36
37
|
_labelNode.click();
|
|
37
|
-
expect(
|
|
38
|
+
expect(isActiveElement(_invokerNode)).to.be.true;
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
it('has an attribute focused when focused', async () => {
|
|
@@ -276,14 +277,14 @@ describe('lion-select-rich', () => {
|
|
|
276
277
|
|
|
277
278
|
await _overlayCtrl.show();
|
|
278
279
|
await el.updateComplete;
|
|
279
|
-
expect(
|
|
280
|
-
expect(
|
|
280
|
+
expect(isActiveElement(_listboxNode)).to.be.true;
|
|
281
|
+
expect(isActiveElement(_invokerNode)).to.be.false;
|
|
281
282
|
|
|
282
283
|
el.opened = false;
|
|
283
284
|
await el.updateComplete;
|
|
284
285
|
await el.updateComplete; // safari takes a little longer
|
|
285
|
-
expect(
|
|
286
|
-
expect(
|
|
286
|
+
expect(isActiveElement(_listboxNode)).to.be.false;
|
|
287
|
+
expect(isActiveElement(_invokerNode)).to.be.true;
|
|
287
288
|
});
|
|
288
289
|
|
|
289
290
|
it('opens the listbox with checked option as active', async () => {
|
|
@@ -472,7 +473,7 @@ describe('lion-select-rich', () => {
|
|
|
472
473
|
<lion-select-rich .config=${{ trapsKeyboardFocus: true }}></lion-select-rich>
|
|
473
474
|
`);
|
|
474
475
|
const { _listboxNode } = getSelectRichMembers(el);
|
|
475
|
-
expect(
|
|
476
|
+
expect(isActiveElement(_listboxNode)).to.be.false;
|
|
476
477
|
|
|
477
478
|
el.opened = true;
|
|
478
479
|
await el.updateComplete;
|
|
@@ -481,7 +482,7 @@ describe('lion-select-rich', () => {
|
|
|
481
482
|
el.opened = false;
|
|
482
483
|
await el.updateComplete;
|
|
483
484
|
await el.updateComplete; // safari takes a little longer
|
|
484
|
-
expect(
|
|
485
|
+
expect(isActiveElement(_listboxNode)).to.be.false;
|
|
485
486
|
});
|
|
486
487
|
});
|
|
487
488
|
|
|
@@ -69,7 +69,7 @@ describe('lion-switch', () => {
|
|
|
69
69
|
const { _inputNode, _labelNode } = getSwitchMembers(el);
|
|
70
70
|
|
|
71
71
|
_labelNode.click();
|
|
72
|
-
expect(
|
|
72
|
+
expect(isActiveElement(_inputNode)).to.be.false;
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
it('should sync its "disabled" state to child button', async () => {
|
|
@@ -2,6 +2,8 @@ import { expect, fixture } from '@open-wc/testing';
|
|
|
2
2
|
import { html } from 'lit/static-html.js';
|
|
3
3
|
import sinon from 'sinon';
|
|
4
4
|
|
|
5
|
+
import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* @typedef {import('../src/LionTabs.js').LionTabs} LionTabs
|
|
7
9
|
*/
|
|
@@ -397,9 +399,11 @@ describe('<lion-tabs>', () => {
|
|
|
397
399
|
`)
|
|
398
400
|
);
|
|
399
401
|
|
|
402
|
+
const secondTab = /** @type {HTMLElement} */ (
|
|
403
|
+
el.querySelector('[slot="tab"]:nth-of-type(2)')
|
|
404
|
+
);
|
|
400
405
|
el.selectedIndex = 1;
|
|
401
|
-
expect(
|
|
402
|
-
.false;
|
|
406
|
+
expect(isActiveElement(secondTab)).to.be.false;
|
|
403
407
|
});
|
|
404
408
|
|
|
405
409
|
it('does not focus a tab on firstUpdate', async () => {
|
|
@@ -414,7 +418,7 @@ describe('<lion-tabs>', () => {
|
|
|
414
418
|
`)
|
|
415
419
|
);
|
|
416
420
|
const tabs = Array.from(el.children).filter(child => child.slot === 'tab');
|
|
417
|
-
expect(tabs.some(tab => tab
|
|
421
|
+
expect(tabs.some(tab => isActiveElement(tab))).to.be.false;
|
|
418
422
|
});
|
|
419
423
|
|
|
420
424
|
it('focuses on a tab when setting with _setSelectedIndexWithFocus method', async () => {
|
|
@@ -429,9 +433,12 @@ describe('<lion-tabs>', () => {
|
|
|
429
433
|
`)
|
|
430
434
|
);
|
|
431
435
|
|
|
436
|
+
const secondTab = /** @type {HTMLElement} */ (
|
|
437
|
+
el.querySelector('[slot="tab"]:nth-of-type(2)')
|
|
438
|
+
);
|
|
432
439
|
// @ts-ignore : this el is LionTabs
|
|
433
440
|
el._setSelectedIndexWithFocus(1);
|
|
434
|
-
expect(
|
|
441
|
+
expect(isActiveElement(secondTab)).to.be.true;
|
|
435
442
|
});
|
|
436
443
|
});
|
|
437
444
|
|
|
@@ -448,7 +455,7 @@ describe('<lion-tabs>', () => {
|
|
|
448
455
|
);
|
|
449
456
|
const secondTab = /** @type {Element} */ (el.querySelector('[slot="tab"]:nth-of-type(2)'));
|
|
450
457
|
secondTab.dispatchEvent(new MouseEvent('click'));
|
|
451
|
-
expect(secondTab
|
|
458
|
+
expect(isActiveElement(secondTab)).to.be.true;
|
|
452
459
|
});
|
|
453
460
|
|
|
454
461
|
describe('Accessibility', () => {
|