@lion/ui 0.15.5 → 0.16.0-alpha.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 +99 -66
- package/components/overlays/src/OverlaysManager.js +0 -33
- package/components/overlays/test/OverlayController.test.js +52 -30
- 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/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,8 +1,8 @@
|
|
|
1
1
|
import { overlays } from './singleton.js';
|
|
2
|
-
import { containFocus } from './utils/contain-focus.js';
|
|
3
2
|
import { deepContains } from './utils/deep-contains.js';
|
|
4
3
|
import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
|
5
4
|
import { _adoptStyleUtils } from './utils/adopt-styles.js';
|
|
5
|
+
import { getFocusableElements } from './utils/get-focusable-elements.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {'setup'|'init'|'teardown'|'before-show'|'show'|'hide'|'add'|'remove'} OverlayPhase
|
|
@@ -113,6 +113,11 @@ const childDialogsClosedInEventLoopWeakmap = new WeakMap();
|
|
|
113
113
|
*
|
|
114
114
|
*/
|
|
115
115
|
export class OverlayController extends EventTarget {
|
|
116
|
+
/**
|
|
117
|
+
* 'True' when Shift key is pressed, 'false' otherwise
|
|
118
|
+
*/
|
|
119
|
+
#isShiftPressed = false;
|
|
120
|
+
|
|
116
121
|
/**
|
|
117
122
|
* @constructor
|
|
118
123
|
* @param {OverlayConfig} config initial config. Will be remembered as shared config
|
|
@@ -201,8 +206,6 @@ export class OverlayController extends EventTarget {
|
|
|
201
206
|
this.__escKeyHandler = this.__escKeyHandler.bind(this);
|
|
202
207
|
this.updateConfig(config);
|
|
203
208
|
/** @private */
|
|
204
|
-
this.__hasActiveTrapsKeyboardFocus = false;
|
|
205
|
-
/** @private */
|
|
206
209
|
this.__hasActiveBackdrop = true;
|
|
207
210
|
/** @private */
|
|
208
211
|
this.__cancelHandler = this.__cancelHandler.bind(this);
|
|
@@ -591,9 +594,7 @@ export class OverlayController extends EventTarget {
|
|
|
591
594
|
* @private
|
|
592
595
|
*/
|
|
593
596
|
__initContentDomStructure() {
|
|
594
|
-
const wrappingDialogElement = document.createElement(
|
|
595
|
-
this.config?._noDialogEl ? 'div' : 'dialog',
|
|
596
|
-
);
|
|
597
|
+
const wrappingDialogElement = document.createElement('dialog');
|
|
597
598
|
// We use a dialog for its visual capabilities: it renders to the top layer.
|
|
598
599
|
// A11y will depend on the type of overlay and is arranged on contentNode level.
|
|
599
600
|
// Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
|
|
@@ -625,7 +626,6 @@ export class OverlayController extends EventTarget {
|
|
|
625
626
|
contentWrapperNodeL2: this.contentWrapperNode,
|
|
626
627
|
contentNodeL3: this.contentNode,
|
|
627
628
|
});
|
|
628
|
-
// @ts-expect-error
|
|
629
629
|
wrappingDialogElement.open = true;
|
|
630
630
|
|
|
631
631
|
if (this.isTooltip) {
|
|
@@ -1145,65 +1145,97 @@ export class OverlayController extends EventTarget {
|
|
|
1145
1145
|
}
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
|
-
get hasActiveTrapsKeyboardFocus() {
|
|
1149
|
-
return this.__hasActiveTrapsKeyboardFocus;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
1148
|
/**
|
|
1153
|
-
* @param {
|
|
1154
|
-
* @protected
|
|
1149
|
+
* @param {KeyboardEvent} event
|
|
1155
1150
|
*/
|
|
1156
|
-
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1159
|
-
if ('showModal' in this.__wrappingDialogNode) {
|
|
1160
|
-
// @ts-ignore
|
|
1161
|
-
this.__wrappingDialogNode.close();
|
|
1162
|
-
// @ts-ignore
|
|
1163
|
-
this.__wrappingDialogNode.showModal();
|
|
1164
|
-
}
|
|
1165
|
-
// else {
|
|
1166
|
-
this.enableTrapsKeyboardFocus();
|
|
1167
|
-
// }
|
|
1168
|
-
} else if (phase === 'hide' || phase === 'teardown') {
|
|
1169
|
-
this.disableTrapsKeyboardFocus();
|
|
1151
|
+
#isShiftPressedOnKeyDownHandler = event => {
|
|
1152
|
+
if (event.key === 'Shift') {
|
|
1153
|
+
this.#isShiftPressed = true;
|
|
1170
1154
|
}
|
|
1171
|
-
}
|
|
1155
|
+
};
|
|
1172
1156
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
if (
|
|
1178
|
-
this
|
|
1157
|
+
/**
|
|
1158
|
+
* @param {KeyboardEvent} event
|
|
1159
|
+
*/
|
|
1160
|
+
#isShiftPressedOnKeyUpHandler = event => {
|
|
1161
|
+
if (event.key === 'Shift') {
|
|
1162
|
+
this.#isShiftPressed = false;
|
|
1179
1163
|
}
|
|
1164
|
+
};
|
|
1180
1165
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
'[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root',
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1166
|
+
#handleShiftKeyPress = () => {
|
|
1167
|
+
window.addEventListener('keydown', this.#isShiftPressedOnKeyDownHandler);
|
|
1168
|
+
window.addEventListener('keyup', this.#isShiftPressedOnKeyUpHandler);
|
|
1169
|
+
};
|
|
1188
1170
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1171
|
+
#stopHandlingShiftKeyPress = () => {
|
|
1172
|
+
window.removeEventListener('keydown', this.#isShiftPressedOnKeyDownHandler);
|
|
1173
|
+
window.removeEventListener('keyup', this.#isShiftPressedOnKeyUpHandler);
|
|
1174
|
+
};
|
|
1195
1175
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1176
|
+
#getInitialElementToFocus = () => {
|
|
1177
|
+
const focusableElements = getFocusableElements(this.contentNode);
|
|
1178
|
+
// Initial focus goes to first element with autofocus, or `contentNode`
|
|
1179
|
+
return focusableElements.find(e => e.hasAttribute('autofocus')) || this.contentNode;
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* When a `dialog` element gets focused, we focus programmatically something
|
|
1184
|
+
* else inside dialog for better a11y. A dialog element gets focused natively in these cases:
|
|
1185
|
+
* 1) When called by `showModal()` first time
|
|
1186
|
+
* 2) When focus is rotating. That is when a user navigates using Tab key through
|
|
1187
|
+
* all the dialog's focusable elements, then the focus goes to the browser's URL,
|
|
1188
|
+
* all its tabs and then the focus goes back to the dialog element
|
|
1189
|
+
* 3) Same as in the point #2, but when a user navigates backward by hitting `Shift + Tab`.
|
|
1190
|
+
* In this case we do not intercept and let the focus pass through. Otherwise the focus
|
|
1191
|
+
* will never leaves the dialog
|
|
1192
|
+
*
|
|
1193
|
+
* Note, Chrome does not focus `Dialog` element when Tabbing. When dialog is opened first time,
|
|
1194
|
+
* it focuses the contentNode if that has `tabindex` set. But the second time when we
|
|
1195
|
+
* move to the dialog from URL bar, nor the dialog element, nor the `contentNode` are focused.
|
|
1196
|
+
* Instead the first focusable element is focused right away
|
|
1197
|
+
*/
|
|
1198
|
+
#handleFocusInsideDialog = () => {
|
|
1199
|
+
this.__wrappingDialogNode?.addEventListener('focus', () => {
|
|
1200
|
+
if (!this.#isShiftPressed) {
|
|
1201
|
+
this.#getInitialElementToFocus().focus();
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* @param {{ phase: OverlayPhase }} config
|
|
1208
|
+
* @protected
|
|
1209
|
+
*/
|
|
1210
|
+
_handleTrapsKeyboardFocus({ phase }) {
|
|
1211
|
+
if (phase === 'init') {
|
|
1212
|
+
this.contentNode.style.outline = 'none';
|
|
1213
|
+
this.contentNode.tabIndex = -1;
|
|
1214
|
+
|
|
1215
|
+
const isContentShadowHost = Boolean(this.contentNode.shadowRoot);
|
|
1216
|
+
if (isContentShadowHost) {
|
|
1217
|
+
// eslint-disable-next-line no-console
|
|
1218
|
+
console.warn(
|
|
1219
|
+
'[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root',
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1199
1222
|
}
|
|
1200
|
-
if (
|
|
1201
|
-
this
|
|
1202
|
-
this
|
|
1223
|
+
if (phase === 'show') {
|
|
1224
|
+
this.#handleShiftKeyPress();
|
|
1225
|
+
this.#handleFocusInsideDialog();
|
|
1226
|
+
this.__wrappingDialogNode?.close();
|
|
1227
|
+
this.__wrappingDialogNode?.showModal();
|
|
1228
|
+
/**
|
|
1229
|
+
* At this moment `#handleFocusInsideDialog` should handle the focus.
|
|
1230
|
+
* But for some reason Firefox on the testing setup does not
|
|
1231
|
+
* focus the native `dialog` on showModal() and focuses the first
|
|
1232
|
+
* focusable element inside the dialog instead. Hence here we focus
|
|
1233
|
+
* contentNode explicitly
|
|
1234
|
+
*/
|
|
1235
|
+
this.#getInitialElementToFocus().focus();
|
|
1203
1236
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
this.manager.informTrapsKeyboardFocusGotDisabled({ disabledCtrl: this, findNewTrap });
|
|
1237
|
+
if (phase === 'hide') {
|
|
1238
|
+
this.#stopHandlingShiftKeyPress();
|
|
1207
1239
|
}
|
|
1208
1240
|
}
|
|
1209
1241
|
|
|
@@ -1231,12 +1263,7 @@ export class OverlayController extends EventTarget {
|
|
|
1231
1263
|
return;
|
|
1232
1264
|
}
|
|
1233
1265
|
|
|
1234
|
-
|
|
1235
|
-
event.composedPath().includes(this.contentNode) ||
|
|
1236
|
-
(this.invokerNode && event.composedPath().includes(this.invokerNode)) ||
|
|
1237
|
-
deepContains(this.contentNode, /** @type {HTMLElement|ShadowRoot} */ (event.target));
|
|
1238
|
-
|
|
1239
|
-
if (hasPressedInside) {
|
|
1266
|
+
if (this.#hasPressedInside(event)) {
|
|
1240
1267
|
this.__escKeyHandlerCalled = true;
|
|
1241
1268
|
this.hide();
|
|
1242
1269
|
// We could do event.stopPropagation() here, but we don't want to hide info for
|
|
@@ -1246,6 +1273,15 @@ export class OverlayController extends EventTarget {
|
|
|
1246
1273
|
}
|
|
1247
1274
|
}
|
|
1248
1275
|
|
|
1276
|
+
/**
|
|
1277
|
+
* @param {KeyboardEvent} event
|
|
1278
|
+
* @returns {boolean}
|
|
1279
|
+
*/
|
|
1280
|
+
#hasPressedInside = event =>
|
|
1281
|
+
event.composedPath().includes(/** @type {EventTarget} */ (this.__wrappingDialogNode)) ||
|
|
1282
|
+
(this.invokerNode && event.composedPath().includes(this.invokerNode)) ||
|
|
1283
|
+
deepContains(this.contentNode, /** @type {HTMLElement|ShadowRoot} */ (event.target));
|
|
1284
|
+
|
|
1249
1285
|
/**
|
|
1250
1286
|
* @param {KeyboardEvent} event
|
|
1251
1287
|
* @returns {void}
|
|
@@ -1253,10 +1289,7 @@ export class OverlayController extends EventTarget {
|
|
|
1253
1289
|
#outsideEscKeyHandler = event => {
|
|
1254
1290
|
if (event.key !== 'Escape') return;
|
|
1255
1291
|
|
|
1256
|
-
|
|
1257
|
-
event.composedPath().includes(this.contentNode) ||
|
|
1258
|
-
deepContains(this.contentNode, /** @type {HTMLElement|ShadowRoot} */ (event.target));
|
|
1259
|
-
if (hasPressedInside) return;
|
|
1292
|
+
if (this.#hasPressedInside(event)) return;
|
|
1260
1293
|
this.hide();
|
|
1261
1294
|
};
|
|
1262
1295
|
|
|
@@ -134,39 +134,6 @@ export class OverlaysManager {
|
|
|
134
134
|
return this.__siblingsInert;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
disableTrapsKeyboardFocusForAll() {
|
|
138
|
-
this.shownList.forEach(ctrl => {
|
|
139
|
-
if (ctrl.trapsKeyboardFocus === true && ctrl.disableTrapsKeyboardFocus) {
|
|
140
|
-
ctrl.disableTrapsKeyboardFocus({ findNewTrap: false });
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @param {'local' | 'global' | undefined} placementMode
|
|
147
|
-
*/
|
|
148
|
-
informTrapsKeyboardFocusGotEnabled(placementMode) {
|
|
149
|
-
if (this.siblingsInert === false && placementMode === 'global') {
|
|
150
|
-
this.__siblingsInert = true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @param {{ disabledCtrl?:OverlayController, findNewTrap?:boolean }} options
|
|
156
|
-
*/
|
|
157
|
-
informTrapsKeyboardFocusGotDisabled({ disabledCtrl, findNewTrap = true } = {}) {
|
|
158
|
-
const next = this.shownList.find(
|
|
159
|
-
ctrl => ctrl !== disabledCtrl && ctrl.trapsKeyboardFocus === true,
|
|
160
|
-
);
|
|
161
|
-
if (next) {
|
|
162
|
-
if (findNewTrap) {
|
|
163
|
-
next.enableTrapsKeyboardFocus();
|
|
164
|
-
}
|
|
165
|
-
} else if (this.siblingsInert === true) {
|
|
166
|
-
this.__siblingsInert = false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
137
|
/** PreventsScroll */
|
|
171
138
|
|
|
172
139
|
// eslint-disable-next-line class-methods-use-this
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-new */
|
|
2
|
+
/* eslint-disable lit-a11y/no-autofocus */
|
|
2
3
|
import { OverlayController, overlays } from '@lion/ui/overlays.js';
|
|
3
4
|
import { mimicClick } from '@lion/ui/overlays-test-helpers.js';
|
|
4
5
|
import { sendKeys } from '@web/test-runner-commands';
|
|
@@ -18,7 +19,7 @@ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
|
|
|
18
19
|
import { createShadowHost } from '../test-helpers/createShadowHost.js';
|
|
19
20
|
import { _adoptStyleUtils } from '../src/utils/adopt-styles.js';
|
|
20
21
|
import { simulateTab } from '../src/utils/simulate-tab.js';
|
|
21
|
-
import {
|
|
22
|
+
import { browserDetection } from '../../core/src/browserDetection.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* @typedef {import('../types/OverlayConfig.js').ViewportPlacement} ViewportPlacement
|
|
@@ -585,30 +586,57 @@ describe('OverlayController', () => {
|
|
|
585
586
|
|
|
586
587
|
describe('Feature Configuration', () => {
|
|
587
588
|
describe('trapsKeyboardFocus', () => {
|
|
588
|
-
it('
|
|
589
|
+
it('focuses the overlay on show', async () => {
|
|
589
590
|
const ctrl = new OverlayController({
|
|
590
591
|
...withGlobalTestConfig(),
|
|
591
592
|
trapsKeyboardFocus: true,
|
|
592
593
|
});
|
|
593
|
-
expect(ctrl.hasActiveTrapsKeyboardFocus).to.be.false;
|
|
594
|
-
|
|
595
594
|
await ctrl.show();
|
|
596
|
-
expect(ctrl.
|
|
595
|
+
expect(isActiveElement(ctrl.contentNode)).to.be.true;
|
|
597
596
|
});
|
|
598
597
|
|
|
599
|
-
it('
|
|
598
|
+
it('keeps focus within the overlay e.g. you can not tab out by accident', async () => {
|
|
599
|
+
const contentNode = /** @type {HTMLElement} */ (
|
|
600
|
+
await fixture(html` <div><input id="input1" /><input id="input2" /></div> `)
|
|
601
|
+
);
|
|
600
602
|
const ctrl = new OverlayController({
|
|
601
603
|
...withGlobalTestConfig(),
|
|
602
604
|
trapsKeyboardFocus: true,
|
|
605
|
+
contentNode,
|
|
603
606
|
});
|
|
604
607
|
await ctrl.show();
|
|
605
608
|
|
|
606
|
-
|
|
609
|
+
await fixture(html`<button>click me</button>`);
|
|
610
|
+
const input1 = ctrl.contentNode.querySelectorAll('input')[0];
|
|
611
|
+
const input2 = ctrl.contentNode.querySelectorAll('input')[1];
|
|
612
|
+
input2.focus();
|
|
613
|
+
await sendKeys({ press: 'Tab' });
|
|
614
|
+
|
|
615
|
+
if (browserDetection.isChrome) {
|
|
616
|
+
expect(isActiveElement(document.body)).to.be.true;
|
|
617
|
+
await sendKeys({ press: 'Tab' });
|
|
618
|
+
expect(isActiveElement(input1)).to.be.true;
|
|
619
|
+
}
|
|
620
|
+
if (browserDetection.isFirefox) {
|
|
621
|
+
// For some reason with the headless firefox setup,
|
|
622
|
+
// once we focus on the latest focusable element, `input2` in this case,
|
|
623
|
+
// any further Tab presses keeps the focus on `input2` element
|
|
624
|
+
expect(isActiveElement(input2)).to.be.true;
|
|
625
|
+
await sendKeys({ press: 'Tab' });
|
|
626
|
+
expect(isActiveElement(input2)).to.be.true;
|
|
627
|
+
}
|
|
628
|
+
if (browserDetection.isMacSafari) {
|
|
629
|
+
expect(isActiveElement(document.body)).to.be.true;
|
|
630
|
+
await sendKeys({ press: 'Tab' });
|
|
631
|
+
expect(isActiveElement(ctrl.contentNode)).to.be.true;
|
|
632
|
+
await sendKeys({ press: 'Tab' });
|
|
633
|
+
expect(isActiveElement(input1)).to.be.true;
|
|
634
|
+
}
|
|
607
635
|
});
|
|
608
636
|
|
|
609
|
-
it('
|
|
637
|
+
it('focuses the element with [autofocus] when dialog gets opened', async () => {
|
|
610
638
|
const contentNode = /** @type {HTMLElement} */ (
|
|
611
|
-
await fixture(html` <div><input id="input1" /><input id="input2" /></div> `)
|
|
639
|
+
await fixture(html` <div><input id="input1" /><input id="input2" autofocus /></div> `)
|
|
612
640
|
);
|
|
613
641
|
const ctrl = new OverlayController({
|
|
614
642
|
...withGlobalTestConfig(),
|
|
@@ -617,21 +645,21 @@ describe('OverlayController', () => {
|
|
|
617
645
|
});
|
|
618
646
|
await ctrl.show();
|
|
619
647
|
|
|
620
|
-
const elOutside = /** @type {HTMLElement} */ (
|
|
621
|
-
await fixture(html`<button>click me</button>`)
|
|
622
|
-
);
|
|
623
|
-
const input1 = ctrl.contentNode.querySelectorAll('input')[0];
|
|
624
648
|
const input2 = ctrl.contentNode.querySelectorAll('input')[1];
|
|
649
|
+
expect(isActiveElement(input2)).to.be.true;
|
|
650
|
+
});
|
|
625
651
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
652
|
+
it('focuses the contentNode when dialog gets opened and there is no [autofocus]', async () => {
|
|
653
|
+
const contentNode = /** @type {HTMLElement} */ (
|
|
654
|
+
await fixture(html` <div><input id="input1" /><input id="input2" /></div> `)
|
|
655
|
+
);
|
|
656
|
+
const ctrl = new OverlayController({
|
|
657
|
+
...withGlobalTestConfig(),
|
|
658
|
+
trapsKeyboardFocus: true,
|
|
659
|
+
contentNode,
|
|
660
|
+
});
|
|
661
|
+
await ctrl.show();
|
|
662
|
+
expect(isActiveElement(contentNode)).to.be.true;
|
|
635
663
|
});
|
|
636
664
|
|
|
637
665
|
it('allows to move the focus outside of the overlay if trapsKeyboardFocus is disabled', async () => {
|
|
@@ -668,12 +696,10 @@ describe('OverlayController', () => {
|
|
|
668
696
|
|
|
669
697
|
await ctrl0.show();
|
|
670
698
|
await ctrl1.show();
|
|
671
|
-
expect(
|
|
672
|
-
expect(ctrl1.hasActiveTrapsKeyboardFocus).to.be.true;
|
|
699
|
+
expect(isActiveElement(ctrl1.contentNode)).to.be.true;
|
|
673
700
|
|
|
674
701
|
await ctrl1.hide();
|
|
675
|
-
expect(ctrl0.
|
|
676
|
-
expect(ctrl1.hasActiveTrapsKeyboardFocus).to.be.false;
|
|
702
|
+
expect(isActiveElement(ctrl0.contentNode)).to.be.true;
|
|
677
703
|
});
|
|
678
704
|
|
|
679
705
|
it('warns when contentNode is a host for a shadowRoot', async () => {
|
|
@@ -812,10 +838,6 @@ describe('OverlayController', () => {
|
|
|
812
838
|
);
|
|
813
839
|
const { parentOverlay, childOverlay } = await createNestedEscControllers(parentContent);
|
|
814
840
|
await mimicEscapePress(childOverlay.contentNode);
|
|
815
|
-
|
|
816
|
-
// without this line, the test is unstable on FF sometimes
|
|
817
|
-
await aTimeout(100);
|
|
818
|
-
|
|
819
841
|
await waitUntil(() => !parentOverlay.isShown);
|
|
820
842
|
await waitUntil(() => childOverlay.isShown);
|
|
821
843
|
|