@vaadin/overlay 24.1.0-alpha9 → 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,12 +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
8
|
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
10
|
-
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
11
9
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
12
10
|
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
13
11
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
+
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
|
|
@@ -74,9 +73,9 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
74
73
|
* @extends HTMLElement
|
|
75
74
|
* @mixes ThemableMixin
|
|
76
75
|
* @mixes DirMixin
|
|
77
|
-
* @mixes
|
|
76
|
+
* @mixes OverlayFocusMixin
|
|
78
77
|
*/
|
|
79
|
-
class Overlay extends ThemableMixin(DirMixin(
|
|
78
|
+
class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
80
79
|
static get template() {
|
|
81
80
|
return html`
|
|
82
81
|
<style>
|
|
@@ -224,34 +223,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
224
223
|
observer: '_hiddenChanged',
|
|
225
224
|
},
|
|
226
225
|
|
|
227
|
-
/**
|
|
228
|
-
* When true move focus to the first focusable element in the overlay,
|
|
229
|
-
* or to the overlay if there are no focusable elements.
|
|
230
|
-
* @type {boolean}
|
|
231
|
-
*/
|
|
232
|
-
focusTrap: {
|
|
233
|
-
type: Boolean,
|
|
234
|
-
value: false,
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Set to true to enable restoring of focus when overlay is closed.
|
|
239
|
-
* @type {boolean}
|
|
240
|
-
*/
|
|
241
|
-
restoreFocusOnClose: {
|
|
242
|
-
type: Boolean,
|
|
243
|
-
value: false,
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Set to specify the element which should be focused on overlay close,
|
|
248
|
-
* if `restoreFocusOnClose` is set to true.
|
|
249
|
-
* @type {HTMLElement}
|
|
250
|
-
*/
|
|
251
|
-
restoreFocusNode: {
|
|
252
|
-
type: HTMLElement,
|
|
253
|
-
},
|
|
254
|
-
|
|
255
226
|
/** @private */
|
|
256
227
|
_mouseDownInside: {
|
|
257
228
|
type: Boolean,
|
|
@@ -301,8 +272,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
301
272
|
if (isIOS) {
|
|
302
273
|
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
303
274
|
}
|
|
304
|
-
|
|
305
|
-
this.__focusTrapController = new FocusTrapController(this);
|
|
306
275
|
}
|
|
307
276
|
|
|
308
277
|
/**
|
|
@@ -325,8 +294,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
325
294
|
this.addEventListener('click', () => {});
|
|
326
295
|
this.$.backdrop.addEventListener('click', () => {});
|
|
327
296
|
|
|
328
|
-
this.addController(this.__focusTrapController);
|
|
329
|
-
|
|
330
297
|
processTemplates(this);
|
|
331
298
|
}
|
|
332
299
|
|
|
@@ -482,14 +449,12 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
482
449
|
/** @private */
|
|
483
450
|
_openedChanged(opened, wasOpened) {
|
|
484
451
|
if (opened) {
|
|
485
|
-
|
|
486
|
-
|
|
452
|
+
this._storeFocus();
|
|
453
|
+
|
|
487
454
|
this._animatedOpening();
|
|
488
455
|
|
|
489
456
|
afterNextRender(this, () => {
|
|
490
|
-
|
|
491
|
-
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
492
|
-
}
|
|
457
|
+
this._trapFocus();
|
|
493
458
|
|
|
494
459
|
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
495
460
|
this.dispatchEvent(evt);
|
|
@@ -501,9 +466,7 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
501
466
|
this._addGlobalListeners();
|
|
502
467
|
}
|
|
503
468
|
} else if (wasOpened) {
|
|
504
|
-
|
|
505
|
-
this.__focusTrapController.releaseFocus();
|
|
506
|
-
}
|
|
469
|
+
this._resetFocus();
|
|
507
470
|
|
|
508
471
|
this._animatedClosing();
|
|
509
472
|
|
|
@@ -611,27 +574,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
611
574
|
}
|
|
612
575
|
if (this._placeholder) {
|
|
613
576
|
this._exitModalState();
|
|
614
|
-
|
|
615
|
-
// Use this.restoreFocusNode if specified, otherwise fallback to the node
|
|
616
|
-
// which was focused before opening the overlay.
|
|
617
|
-
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
618
|
-
|
|
619
|
-
if (this.restoreFocusOnClose && restoreFocusNode) {
|
|
620
|
-
// If the activeElement is `<body>` or inside the overlay,
|
|
621
|
-
// we are allowed to restore the focus. In all the other
|
|
622
|
-
// cases focus might have been moved elsewhere by another
|
|
623
|
-
// component or by the user interaction (e.g. click on a
|
|
624
|
-
// button outside the overlay).
|
|
625
|
-
const activeElement = this._getActiveElement();
|
|
626
|
-
|
|
627
|
-
if (activeElement === document.body || this._deepContains(activeElement)) {
|
|
628
|
-
// Focusing the restoreFocusNode doesn't always work synchronously on Firefox and Safari
|
|
629
|
-
// (e.g. combo-box overlay close on outside click).
|
|
630
|
-
setTimeout(() => restoreFocusNode.focus());
|
|
631
|
-
}
|
|
632
|
-
this.__restoreFocusNode = null;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
577
|
this.setAttribute('closing', '');
|
|
636
578
|
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
637
579
|
|
|
@@ -747,20 +689,6 @@ class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
|
747
689
|
}
|
|
748
690
|
}
|
|
749
691
|
|
|
750
|
-
/**
|
|
751
|
-
* @return {!Element}
|
|
752
|
-
* @private
|
|
753
|
-
*/
|
|
754
|
-
_getActiveElement() {
|
|
755
|
-
// Document.activeElement can be null
|
|
756
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
|
|
757
|
-
let active = document.activeElement || document.body;
|
|
758
|
-
while (active.shadowRoot && active.shadowRoot.activeElement) {
|
|
759
|
-
active = active.shadowRoot.activeElement;
|
|
760
|
-
}
|
|
761
|
-
return active;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
692
|
/**
|
|
765
693
|
* @param {!Node} node
|
|
766
694
|
* @return {boolean}
|