@vaadin/overlay 24.1.0-beta1 → 24.1.0-beta2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/overlay",
|
|
3
|
-
"version": "24.1.0-
|
|
3
|
+
"version": "24.1.0-beta2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
38
38
|
"@polymer/polymer": "^3.0.0",
|
|
39
|
-
"@vaadin/a11y-base": "24.1.0-
|
|
40
|
-
"@vaadin/component-base": "24.1.0-
|
|
41
|
-
"@vaadin/vaadin-lumo-styles": "24.1.0-
|
|
42
|
-
"@vaadin/vaadin-material-styles": "24.1.0-
|
|
43
|
-
"@vaadin/vaadin-themable-mixin": "24.1.0-
|
|
39
|
+
"@vaadin/a11y-base": "24.1.0-beta2",
|
|
40
|
+
"@vaadin/component-base": "24.1.0-beta2",
|
|
41
|
+
"@vaadin/vaadin-lumo-styles": "24.1.0-beta2",
|
|
42
|
+
"@vaadin/vaadin-material-styles": "24.1.0-beta2",
|
|
43
|
+
"@vaadin/vaadin-themable-mixin": "24.1.0-beta2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@esm-bundle/chai": "^4.3.4",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"lit": "^2.0.0",
|
|
49
49
|
"sinon": "^13.0.2"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "83536fcc7d6661a593b2713cb99a8cb74f2fd868"
|
|
52
52
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
+
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
|
+
|
|
9
|
+
export declare function OverlayFocusMixin<T extends Constructor<HTMLElement>>(
|
|
10
|
+
base: T,
|
|
11
|
+
): Constructor<ControllerMixinClass> & Constructor<OverlayFocusMixinClass> & T;
|
|
12
|
+
|
|
13
|
+
export declare class OverlayFocusMixinClass {
|
|
14
|
+
/**
|
|
15
|
+
* When true, opening the overlay moves focus to the first focusable child,
|
|
16
|
+
* or to the overlay part with tabindex if there are no focusable children.
|
|
17
|
+
* @attr {boolean} focus-trap
|
|
18
|
+
*/
|
|
19
|
+
focusTrap: boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set to true to enable restoring of focus when overlay is closed.
|
|
23
|
+
* @attr {boolean} restore-focus-on-close
|
|
24
|
+
*/
|
|
25
|
+
restoreFocusOnClose: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set to specify the element which should be focused on overlay close,
|
|
29
|
+
* if `restoreFocusOnClose` is set to true.
|
|
30
|
+
*/
|
|
31
|
+
restoreFocusNode?: HTMLElement;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Release focus and restore focus after the overlay is closed.
|
|
35
|
+
*/
|
|
36
|
+
protected _resetFocus(): void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Store previously focused node when the overlay starts to open.
|
|
40
|
+
*/
|
|
41
|
+
protected _storeFocus(): void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Trap focus within the overlay after opening has completed.
|
|
45
|
+
*/
|
|
46
|
+
protected _trapFocus(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { AriaModalController } from '@vaadin/a11y-base/src/aria-modal-controller.js';
|
|
7
|
+
import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
|
|
8
|
+
import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
9
|
+
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @polymerMixin
|
|
13
|
+
* @mixes ControllerMixin
|
|
14
|
+
*/
|
|
15
|
+
export const OverlayFocusMixin = (superClass) =>
|
|
16
|
+
class OverlayFocusMixin extends ControllerMixin(superClass) {
|
|
17
|
+
static get properties() {
|
|
18
|
+
return {
|
|
19
|
+
/**
|
|
20
|
+
* When true, opening the overlay moves focus to the first focusable child,
|
|
21
|
+
* or to the overlay part with tabindex if there are no focusable children.
|
|
22
|
+
* @attr {boolean} focus-trap
|
|
23
|
+
*/
|
|
24
|
+
focusTrap: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
value: false,
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set to true to enable restoring of focus when overlay is closed.
|
|
31
|
+
* @attr {boolean} restore-focus-on-close
|
|
32
|
+
*/
|
|
33
|
+
restoreFocusOnClose: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
value: false,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set to specify the element which should be focused on overlay close,
|
|
40
|
+
* if `restoreFocusOnClose` is set to true.
|
|
41
|
+
* @type {HTMLElement}
|
|
42
|
+
*/
|
|
43
|
+
restoreFocusNode: {
|
|
44
|
+
type: HTMLElement,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
|
|
52
|
+
this.__ariaModalController = new AriaModalController(this);
|
|
53
|
+
this.__focusTrapController = new FocusTrapController(this);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @protected */
|
|
57
|
+
ready() {
|
|
58
|
+
super.ready();
|
|
59
|
+
|
|
60
|
+
this.addController(this.__ariaModalController);
|
|
61
|
+
this.addController(this.__focusTrapController);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Release focus and restore focus after the overlay is closed.
|
|
66
|
+
*
|
|
67
|
+
* @protected
|
|
68
|
+
*/
|
|
69
|
+
_resetFocus() {
|
|
70
|
+
if (this.focusTrap) {
|
|
71
|
+
this.__ariaModalController.close();
|
|
72
|
+
this.__focusTrapController.releaseFocus();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (this.restoreFocusOnClose) {
|
|
76
|
+
this.__restoreFocus();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Store previously focused node when the overlay starts to open.
|
|
82
|
+
*
|
|
83
|
+
* @protected
|
|
84
|
+
*/
|
|
85
|
+
_storeFocus() {
|
|
86
|
+
if (this.restoreFocusOnClose) {
|
|
87
|
+
this.__storeFocus();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Trap focus within the overlay after opening has completed.
|
|
93
|
+
*
|
|
94
|
+
* @protected
|
|
95
|
+
*/
|
|
96
|
+
_trapFocus() {
|
|
97
|
+
if (this.focusTrap) {
|
|
98
|
+
this.__ariaModalController.showModal();
|
|
99
|
+
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @private */
|
|
104
|
+
__storeFocus() {
|
|
105
|
+
// Store the focused node.
|
|
106
|
+
this.__restoreFocusNode = getDeepActiveElement();
|
|
107
|
+
|
|
108
|
+
// Determine and store the node that has the `focus-ring` attribute
|
|
109
|
+
// in order to restore the attribute when the overlay closes.
|
|
110
|
+
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
111
|
+
if (restoreFocusNode) {
|
|
112
|
+
const restoreFocusNodeHost = (restoreFocusNode.assignedSlot || restoreFocusNode).getRootNode().host;
|
|
113
|
+
this.__restoreFocusRingNode = [restoreFocusNode, restoreFocusNodeHost].find((node) => {
|
|
114
|
+
return node && node.hasAttribute('focus-ring');
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @private */
|
|
120
|
+
__restoreFocus() {
|
|
121
|
+
// If the activeElement is `<body>` or inside the overlay,
|
|
122
|
+
// we are allowed to restore the focus. In all the other
|
|
123
|
+
// cases focus might have been moved elsewhere by another
|
|
124
|
+
// component or by the user interaction (e.g. click on a
|
|
125
|
+
// button outside the overlay).
|
|
126
|
+
const activeElement = getDeepActiveElement();
|
|
127
|
+
if (activeElement !== document.body && !this._deepContains(activeElement)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Use restoreFocusNode if specified, otherwise fallback to the node
|
|
132
|
+
// which was focused before opening the overlay.
|
|
133
|
+
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
134
|
+
if (restoreFocusNode) {
|
|
135
|
+
// Focusing the restoreFocusNode doesn't always work synchronously on Firefox and Safari
|
|
136
|
+
// (e.g. combo-box overlay close on outside click).
|
|
137
|
+
setTimeout(() => restoreFocusNode.focus());
|
|
138
|
+
this.__restoreFocusNode = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Restore the `focus-ring` attribute if it was present
|
|
142
|
+
// when the overlay was opening.
|
|
143
|
+
if (this.__restoreFocusRingNode) {
|
|
144
|
+
this.__restoreFocusRingNode.setAttribute('focus-ring', '');
|
|
145
|
+
this.__restoreFocusRingNode = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
package/src/vaadin-overlay.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
7
7
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
8
8
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
9
|
+
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
|
|
9
10
|
|
|
10
11
|
export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void;
|
|
11
12
|
|
|
@@ -119,7 +120,7 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayCustomEventMap;
|
|
|
119
120
|
* @fires {CustomEvent} vaadin-overlay-outside-click - Fired before the overlay is closed on outside click. Calling `preventDefault()` on the event cancels the closing.
|
|
120
121
|
* @fires {CustomEvent} vaadin-overlay-escape-press - Fired before the overlay is closed on Escape key press. Calling `preventDefault()` on the event cancels the closing.
|
|
121
122
|
*/
|
|
122
|
-
declare class Overlay extends ThemableMixin(DirMixin(
|
|
123
|
+
declare class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(HTMLElement))) {
|
|
123
124
|
/**
|
|
124
125
|
* When true, the overlay is visible and attached to body.
|
|
125
126
|
*/
|
|
@@ -162,23 +163,6 @@ declare class Overlay extends ThemableMixin(DirMixin(ControllerMixin(HTMLElement
|
|
|
162
163
|
*/
|
|
163
164
|
hidden: boolean;
|
|
164
165
|
|
|
165
|
-
/**
|
|
166
|
-
* When true move focus to the first focusable element in the overlay,
|
|
167
|
-
* or to the overlay if there are no focusable elements.
|
|
168
|
-
*/
|
|
169
|
-
focusTrap: boolean;
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Set to true to enable restoring of focus when overlay is closed.
|
|
173
|
-
*/
|
|
174
|
-
restoreFocusOnClose: boolean;
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Set to specify the element which should be focused on overlay close,
|
|
178
|
-
* if `restoreFocusOnClose` is set to true.
|
|
179
|
-
*/
|
|
180
|
-
restoreFocusNode?: HTMLElement;
|
|
181
|
-
|
|
182
166
|
/**
|
|
183
167
|
* Returns true if this is the last one in the opened overlays stack.
|
|
184
168
|
*/
|
package/src/vaadin-overlay.js
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
7
7
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
8
|
-
import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
|
|
9
|
-
import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
10
8
|
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
11
|
-
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
12
9
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
13
10
|
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
14
11
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
+
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
15
|
* `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
|
|
@@ -75,9 +73,9 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
75
73
|
* @extends HTMLElement
|
|
76
74
|
* @mixes ThemableMixin
|
|
77
75
|
* @mixes DirMixin
|
|
78
|
-
* @mixes
|
|
76
|
+
* @mixes OverlayFocusMixin
|
|
79
77
|
*/
|
|
80
|
-
class Overlay extends ThemableMixin(DirMixin(
|
|
78
|
+
class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
81
79
|
static get template() {
|
|
82
80
|
return html`
|
|
83
81
|
<style>
|
|
@@ -225,34 +223,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
225
223
|
observer: '_hiddenChanged',
|
|
226
224
|
},
|
|
227
225
|
|
|
228
|
-
/**
|
|
229
|
-
* When true move focus to the first focusable element in the overlay,
|
|
230
|
-
* or to the overlay if there are no focusable elements.
|
|
231
|
-
* @type {boolean}
|
|
232
|
-
*/
|
|
233
|
-
focusTrap: {
|
|
234
|
-
type: Boolean,
|
|
235
|
-
value: false,
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Set to true to enable restoring of focus when overlay is closed.
|
|
240
|
-
* @type {boolean}
|
|
241
|
-
*/
|
|
242
|
-
restoreFocusOnClose: {
|
|
243
|
-
type: Boolean,
|
|
244
|
-
value: false,
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Set to specify the element which should be focused on overlay close,
|
|
249
|
-
* if `restoreFocusOnClose` is set to true.
|
|
250
|
-
* @type {HTMLElement}
|
|
251
|
-
*/
|
|
252
|
-
restoreFocusNode: {
|
|
253
|
-
type: HTMLElement,
|
|
254
|
-
},
|
|
255
|
-
|
|
256
226
|
/** @private */
|
|
257
227
|
_mouseDownInside: {
|
|
258
228
|
type: Boolean,
|
|
@@ -302,8 +272,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
302
272
|
if (isIOS) {
|
|
303
273
|
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
304
274
|
}
|
|
305
|
-
|
|
306
|
-
this.__focusTrapController = new FocusTrapController(this);
|
|
307
275
|
}
|
|
308
276
|
|
|
309
277
|
/**
|
|
@@ -326,8 +294,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
326
294
|
this.addEventListener('click', () => {});
|
|
327
295
|
this.$.backdrop.addEventListener('click', () => {});
|
|
328
296
|
|
|
329
|
-
this.addController(this.__focusTrapController);
|
|
330
|
-
|
|
331
297
|
processTemplates(this);
|
|
332
298
|
}
|
|
333
299
|
|
|
@@ -483,16 +449,12 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
483
449
|
/** @private */
|
|
484
450
|
_openedChanged(opened, wasOpened) {
|
|
485
451
|
if (opened) {
|
|
486
|
-
|
|
487
|
-
this.__storeFocus();
|
|
488
|
-
}
|
|
452
|
+
this._storeFocus();
|
|
489
453
|
|
|
490
454
|
this._animatedOpening();
|
|
491
455
|
|
|
492
456
|
afterNextRender(this, () => {
|
|
493
|
-
|
|
494
|
-
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
495
|
-
}
|
|
457
|
+
this._trapFocus();
|
|
496
458
|
|
|
497
459
|
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
498
460
|
this.dispatchEvent(evt);
|
|
@@ -504,13 +466,7 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
504
466
|
this._addGlobalListeners();
|
|
505
467
|
}
|
|
506
468
|
} else if (wasOpened) {
|
|
507
|
-
|
|
508
|
-
this.__focusTrapController.releaseFocus();
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (this.restoreFocusOnClose) {
|
|
512
|
-
this.__restoreFocus();
|
|
513
|
-
}
|
|
469
|
+
this._resetFocus();
|
|
514
470
|
|
|
515
471
|
this._animatedClosing();
|
|
516
472
|
|
|
@@ -522,52 +478,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
522
478
|
}
|
|
523
479
|
}
|
|
524
480
|
|
|
525
|
-
/** @private */
|
|
526
|
-
__storeFocus() {
|
|
527
|
-
// Store the focused node.
|
|
528
|
-
this.__restoreFocusNode = getDeepActiveElement();
|
|
529
|
-
|
|
530
|
-
// Determine and store the node that has the `focus-ring` attribute
|
|
531
|
-
// in order to restore the attribute when the overlay closes.
|
|
532
|
-
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
533
|
-
if (restoreFocusNode) {
|
|
534
|
-
const restoreFocusNodeHost = (restoreFocusNode.assignedSlot || restoreFocusNode).getRootNode().host;
|
|
535
|
-
this.__restoreFocusRingNode = [restoreFocusNode, restoreFocusNodeHost].find((node) => {
|
|
536
|
-
return node && node.hasAttribute('focus-ring');
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/** @private */
|
|
542
|
-
__restoreFocus() {
|
|
543
|
-
// If the activeElement is `<body>` or inside the overlay,
|
|
544
|
-
// we are allowed to restore the focus. In all the other
|
|
545
|
-
// cases focus might have been moved elsewhere by another
|
|
546
|
-
// component or by the user interaction (e.g. click on a
|
|
547
|
-
// button outside the overlay).
|
|
548
|
-
const activeElement = getDeepActiveElement();
|
|
549
|
-
if (activeElement !== document.body && !this._deepContains(activeElement)) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Use restoreFocusNode if specified, otherwise fallback to the node
|
|
554
|
-
// which was focused before opening the overlay.
|
|
555
|
-
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
556
|
-
if (restoreFocusNode) {
|
|
557
|
-
// Focusing the restoreFocusNode doesn't always work synchronously on Firefox and Safari
|
|
558
|
-
// (e.g. combo-box overlay close on outside click).
|
|
559
|
-
setTimeout(() => restoreFocusNode.focus());
|
|
560
|
-
this.__restoreFocusNode = null;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Restore the `focus-ring` attribute if it was present
|
|
564
|
-
// when the overlay was opening.
|
|
565
|
-
if (this.__restoreFocusRingNode) {
|
|
566
|
-
this.__restoreFocusRingNode.setAttribute('focus-ring', '');
|
|
567
|
-
this.__restoreFocusRingNode = null;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
481
|
/** @private */
|
|
572
482
|
_hiddenChanged(hidden) {
|
|
573
483
|
if (hidden && this.hasAttribute('closing')) {
|