@lion/ui 0.15.5 → 0.16.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/overlays/src/OverlayController.js +100 -67
- package/components/overlays/src/OverlaysManager.js +0 -33
- package/components/overlays/test/OverlayController.test.js +88 -30
- package/components/overlays/test-suites/OverlayMixin.suite.js +58 -0
- package/custom-elements.json +105 -200
- package/dist-types/components/overlays/src/OverlayController.d.ts +1 -11
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlaysManager.d.ts +0 -12
- package/dist-types/components/overlays/src/OverlaysManager.d.ts.map +1 -1
- package/dist-types/components/overlays/test/OverlayController.test.d.ts.map +1 -1
- package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
- package/dist-types/exports/overlays.d.ts +0 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- 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/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/overview.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- 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 +6 -6
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/use-cases.md +2 -2
- package/docs/components/tabs/examples.md +1 -1
- 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/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/api-table.md +45 -51
- package/docs/fundamentals/systems/overlays/configuration.md +5 -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/exports/overlays.js +0 -1
- package/overlays.js +0 -1
- package/package.json +1 -1
- package/components/overlays/src/utils/contain-focus.js +0 -202
- package/components/overlays/test/utils-tests/contain-focus.test.js +0 -236
- package/dist-types/components/overlays/src/utils/contain-focus.d.ts +0 -20
- package/dist-types/components/overlays/src/utils/contain-focus.d.ts.map +0 -1
- package/dist-types/components/overlays/test/utils-tests/contain-focus.test.d.ts +0 -2
- package/dist-types/components/overlays/test/utils-tests/contain-focus.test.d.ts.map +0 -1
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/* eslint-disable lit-a11y/no-autofocus */
|
|
2
|
-
import { expect, fixture, nextFrame } from '@open-wc/testing';
|
|
3
|
-
import { getFocusableElements } from '@lion/ui/overlays.js';
|
|
4
|
-
import { renderLitAsNode } from '@lion/ui/helpers.js';
|
|
5
|
-
import { html } from 'lit/static-html.js';
|
|
6
|
-
|
|
7
|
-
import { isActiveElement } from '../../../core/test-helpers/isActiveElement.js';
|
|
8
|
-
import { containFocus } from '../../src/utils/contain-focus.js';
|
|
9
|
-
import { keyCodes } from '../../src/utils/key-codes.js';
|
|
10
|
-
|
|
11
|
-
function simulateTabWithinContainFocus() {
|
|
12
|
-
const event = new CustomEvent('keydown', { detail: 0, bubbles: true });
|
|
13
|
-
// @ts-ignore override keyCode
|
|
14
|
-
event.keyCode = keyCodes.tab;
|
|
15
|
-
window.dispatchEvent(event);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {HTMLElement} elToRecieveFocus
|
|
20
|
-
*/
|
|
21
|
-
function simulateTabInWindow(elToRecieveFocus) {
|
|
22
|
-
window.dispatchEvent(new Event('blur'));
|
|
23
|
-
elToRecieveFocus.focus();
|
|
24
|
-
window.dispatchEvent(new Event('focusin'));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const interactionElementsNode = renderLitAsNode(html`
|
|
28
|
-
<div>
|
|
29
|
-
<button id="el1">Button</button>
|
|
30
|
-
<a id="el2" href="#">foo</a>
|
|
31
|
-
<div id="el3" tabindex="0"></div>
|
|
32
|
-
<input id="el4" />
|
|
33
|
-
<div id="el5" contenteditable="true"></div>
|
|
34
|
-
<textarea id="el6"></textarea>
|
|
35
|
-
<select id="el7">
|
|
36
|
-
<option>1</option>
|
|
37
|
-
</select>
|
|
38
|
-
</div>
|
|
39
|
-
`);
|
|
40
|
-
|
|
41
|
-
const lightDomTemplate = html`
|
|
42
|
-
<div>
|
|
43
|
-
<button id="outside-1">outside 1</button>
|
|
44
|
-
<div id="rootElement">${interactionElementsNode}</div>
|
|
45
|
-
<button id="outside-2">outside 2</button>
|
|
46
|
-
</div>
|
|
47
|
-
`;
|
|
48
|
-
|
|
49
|
-
const lightDomAutofocusTemplate = html`
|
|
50
|
-
<div>
|
|
51
|
-
<button id="outside-1">outside 1</button>
|
|
52
|
-
|
|
53
|
-
<div id="rootElement">
|
|
54
|
-
<button id="el1">Button</button>
|
|
55
|
-
<a id="el2" href="#">foo</a>
|
|
56
|
-
<div id="el3" tabindex="0"></div>
|
|
57
|
-
<input id="el4" autofocus />
|
|
58
|
-
<div id="el5" contenteditable="true"></div>
|
|
59
|
-
<textarea id="el6"></textarea>
|
|
60
|
-
<select id="el7">
|
|
61
|
-
<option>1</option>
|
|
62
|
-
</select>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<button id="outside-2">outside 2</button>
|
|
66
|
-
</div>
|
|
67
|
-
`;
|
|
68
|
-
|
|
69
|
-
function createShadowDomNode() {
|
|
70
|
-
const shadowDomNode = renderLitAsNode(html`
|
|
71
|
-
<div>
|
|
72
|
-
<button id="outside-1">outside 1</button>
|
|
73
|
-
<div id="rootElementShadow"></div>
|
|
74
|
-
<button id="outside-2">outside 2</button>
|
|
75
|
-
</div>
|
|
76
|
-
`);
|
|
77
|
-
const rootElementShadow = shadowDomNode?.querySelector('#rootElementShadow');
|
|
78
|
-
rootElementShadow?.attachShadow({ mode: 'open' });
|
|
79
|
-
if (interactionElementsNode) {
|
|
80
|
-
rootElementShadow?.shadowRoot?.appendChild(interactionElementsNode);
|
|
81
|
-
}
|
|
82
|
-
return shadowDomNode;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
describe('containFocus()', () => {
|
|
86
|
-
it('starts focus at the root element when there is no element with [autofocus]', async () => {
|
|
87
|
-
await fixture(lightDomTemplate);
|
|
88
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
89
|
-
const { disconnect } = containFocus(root);
|
|
90
|
-
|
|
91
|
-
expect(isActiveElement(root, { deep: true })).to.be.true;
|
|
92
|
-
expect(root.getAttribute('tabindex')).to.equal('-1');
|
|
93
|
-
expect(root.style.getPropertyValue('outline-style')).to.equal('none');
|
|
94
|
-
|
|
95
|
-
disconnect();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('starts focus at the element with [autofocus] attribute', async () => {
|
|
99
|
-
await fixture(lightDomAutofocusTemplate);
|
|
100
|
-
const el = /** @type {HTMLElement} */ (document.querySelector('input[autofocus]'));
|
|
101
|
-
const { disconnect } = containFocus(el);
|
|
102
|
-
|
|
103
|
-
expect(isActiveElement(el, { deep: true })).to.be.true;
|
|
104
|
-
|
|
105
|
-
disconnect();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('on tab, focuses first focusable element if focus was on element outside root element', async () => {
|
|
109
|
-
await fixture(lightDomTemplate);
|
|
110
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
111
|
-
const focusableElements = getFocusableElements(root);
|
|
112
|
-
const { disconnect } = containFocus(root);
|
|
113
|
-
|
|
114
|
-
/** @type {HTMLElement} */ (document.getElementById('outside-1')).focus();
|
|
115
|
-
|
|
116
|
-
simulateTabWithinContainFocus();
|
|
117
|
-
expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true;
|
|
118
|
-
|
|
119
|
-
disconnect();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('on tab, focuses first focusable element if focus was on the last focusable element', async () => {
|
|
123
|
-
await fixture(lightDomTemplate);
|
|
124
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
125
|
-
const focusableElements = getFocusableElements(root);
|
|
126
|
-
const { disconnect } = containFocus(root);
|
|
127
|
-
|
|
128
|
-
focusableElements[focusableElements.length - 1].focus();
|
|
129
|
-
|
|
130
|
-
simulateTabWithinContainFocus();
|
|
131
|
-
expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true;
|
|
132
|
-
|
|
133
|
-
disconnect();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('on tab, does not interfere if focus remains within the root element', async () => {
|
|
137
|
-
await fixture(lightDomTemplate);
|
|
138
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
139
|
-
const focusableElements = getFocusableElements(root);
|
|
140
|
-
const { disconnect } = containFocus(root);
|
|
141
|
-
|
|
142
|
-
focusableElements[2].focus();
|
|
143
|
-
|
|
144
|
-
simulateTabWithinContainFocus();
|
|
145
|
-
/**
|
|
146
|
-
* We test if focus remained on the same element because we cannot simulate
|
|
147
|
-
* actual tab key press. So the best we can do is if we didn't redirect focus
|
|
148
|
-
* to the first element.
|
|
149
|
-
*/
|
|
150
|
-
expect(isActiveElement(focusableElements[2], { deep: true })).to.be.true;
|
|
151
|
-
|
|
152
|
-
disconnect();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it.skip('restores focus to root element if focusout event happens where activeElement goes outside', async () => {
|
|
156
|
-
await fixture(lightDomTemplate);
|
|
157
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
158
|
-
const focusableElements = getFocusableElements(root);
|
|
159
|
-
const { disconnect } = containFocus(root);
|
|
160
|
-
|
|
161
|
-
focusableElements[2].focus();
|
|
162
|
-
expect(isActiveElement(focusableElements[2], { deep: true })).to.be.true;
|
|
163
|
-
|
|
164
|
-
document.body.click(); // this does not cause focusout event :( doesn't seem possible to mock
|
|
165
|
-
expect(isActiveElement(root, { deep: true })).to.be.true;
|
|
166
|
-
|
|
167
|
-
disconnect();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('Tabbing into window', () => {
|
|
171
|
-
it('reinserts tab detection element when contentNode changes inner content', async () => {
|
|
172
|
-
await fixture(lightDomTemplate);
|
|
173
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
174
|
-
const { disconnect } = containFocus(root);
|
|
175
|
-
expect(root.querySelector('[data-is-tab-detection-element]')).to.exist;
|
|
176
|
-
root.innerHTML = `my content`;
|
|
177
|
-
await nextFrame();
|
|
178
|
-
expect(root.querySelector('[data-is-tab-detection-element]')).to.exist;
|
|
179
|
-
disconnect();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('restores focus within root element', async () => {
|
|
183
|
-
await fixture(lightDomTemplate);
|
|
184
|
-
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
|
185
|
-
const focusableElements = getFocusableElements(root);
|
|
186
|
-
const { disconnect } = containFocus(root);
|
|
187
|
-
|
|
188
|
-
// Simulate tab in window
|
|
189
|
-
simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-1')));
|
|
190
|
-
expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true;
|
|
191
|
-
|
|
192
|
-
// Simulate shift+tab in window
|
|
193
|
-
simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-2')));
|
|
194
|
-
expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be
|
|
195
|
-
.true;
|
|
196
|
-
|
|
197
|
-
disconnect();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('restores focus within root element with shadow dom', async () => {
|
|
201
|
-
const el = await fixture(html`${createShadowDomNode()}`);
|
|
202
|
-
const root = /** @type {HTMLElement} */ (el.querySelector('#rootElementShadow'));
|
|
203
|
-
const focusableElements = getFocusableElements(root);
|
|
204
|
-
const { disconnect } = containFocus(root);
|
|
205
|
-
|
|
206
|
-
// Simulate tab in window
|
|
207
|
-
simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-1')));
|
|
208
|
-
expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true;
|
|
209
|
-
|
|
210
|
-
// Simulate shift+tab in window
|
|
211
|
-
simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-2')));
|
|
212
|
-
expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be
|
|
213
|
-
.true;
|
|
214
|
-
|
|
215
|
-
disconnect();
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('keeps focus if already in rootElement', async () => {
|
|
219
|
-
const el = await fixture(html`${createShadowDomNode()}`);
|
|
220
|
-
const root = /** @type {HTMLElement} */ (el.querySelector('#rootElementShadow'));
|
|
221
|
-
const focusableElements = getFocusableElements(root);
|
|
222
|
-
const { disconnect } = containFocus(root);
|
|
223
|
-
|
|
224
|
-
// Simulate tab in window
|
|
225
|
-
simulateTabInWindow(focusableElements[0]);
|
|
226
|
-
expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true;
|
|
227
|
-
|
|
228
|
-
// Simulate shift+tab in window
|
|
229
|
-
simulateTabInWindow(focusableElements[focusableElements.length - 1]);
|
|
230
|
-
expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be
|
|
231
|
-
.true;
|
|
232
|
-
|
|
233
|
-
disconnect();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rotates focus within a list of elements. If shift key was not pressed and focus
|
|
3
|
-
* is on last item, puts focus on the first item. Reversed if shift key.
|
|
4
|
-
*
|
|
5
|
-
* @param {HTMLElement} rootElement The root element
|
|
6
|
-
* @param {KeyboardEvent} e The keyboard event
|
|
7
|
-
*/
|
|
8
|
-
export function rotateFocus(rootElement: HTMLElement, e: KeyboardEvent): void;
|
|
9
|
-
/**
|
|
10
|
-
* Contains focus within given root element. When focus is on the last focusable
|
|
11
|
-
* element inside the root element, the next focus will be redirected to the first
|
|
12
|
-
* focusable element.
|
|
13
|
-
*
|
|
14
|
-
* @param {HTMLElement} rootElement The element to contain focus within
|
|
15
|
-
* @returns {{ disconnect: () => void }} handler with a disconnect callback
|
|
16
|
-
*/
|
|
17
|
-
export function containFocus(rootElement: HTMLElement): {
|
|
18
|
-
disconnect: () => void;
|
|
19
|
-
};
|
|
20
|
-
//# sourceMappingURL=contain-focus.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contain-focus.d.ts","sourceRoot":"","sources":["../../../../../components/overlays/src/utils/contain-focus.js"],"names":[],"mappings":"AAaA;;;;;;GAMG;AACH,yCAHW,WAAW,KACX,aAAa,QA6CvB;AAED;;;;;;;GAOG;AACH,0CAHW,WAAW;gBACK,MAAM,IAAI;EAkIpC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contain-focus.test.d.ts","sourceRoot":"","sources":["../../../../../components/overlays/test/utils-tests/contain-focus.test.js"],"names":[],"mappings":""}
|