@vaadin/vaadin-overlay 22.0.2 → 23.0.0-alpha4
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/vaadin-overlay",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "23.0.0-alpha4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"main": "vaadin-overlay.js",
|
|
20
20
|
"module": "vaadin-overlay.js",
|
|
21
|
+
"type": "module",
|
|
21
22
|
"files": [
|
|
22
23
|
"src",
|
|
23
24
|
"theme",
|
|
@@ -34,20 +35,20 @@
|
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"@polymer/polymer": "^3.0.0",
|
|
37
|
-
"@vaadin/component-base": "
|
|
38
|
-
"@vaadin/vaadin-lumo-styles": "
|
|
39
|
-
"@vaadin/vaadin-material-styles": "
|
|
40
|
-
"@vaadin/vaadin-themable-mixin": "
|
|
38
|
+
"@vaadin/component-base": "23.0.0-alpha4",
|
|
39
|
+
"@vaadin/vaadin-lumo-styles": "23.0.0-alpha4",
|
|
40
|
+
"@vaadin/vaadin-material-styles": "23.0.0-alpha4",
|
|
41
|
+
"@vaadin/vaadin-themable-mixin": "23.0.0-alpha4"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@esm-bundle/chai": "^4.3.4",
|
|
44
45
|
"@polymer/iron-overlay-behavior": "^3.0.0",
|
|
45
|
-
"@vaadin/button": "
|
|
46
|
-
"@vaadin/radio-group": "
|
|
46
|
+
"@vaadin/button": "23.0.0-alpha4",
|
|
47
|
+
"@vaadin/radio-group": "23.0.0-alpha4",
|
|
47
48
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
48
|
-
"@vaadin/text-field": "
|
|
49
|
+
"@vaadin/text-field": "23.0.0-alpha4",
|
|
49
50
|
"lit": "^2.0.0",
|
|
50
51
|
"sinon": "^9.2.1"
|
|
51
52
|
},
|
|
52
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "81e2deee5147bb7c1f4884760f4598613306f1fb"
|
|
53
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c)
|
|
3
|
+
* Copyright (c) 2017 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -76,6 +76,7 @@ export const PositionMixin = (superClass) =>
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
|
|
79
80
|
static get observers() {
|
|
80
81
|
return [
|
|
81
82
|
'__positionSettingsChanged(positionTarget, horizontalAlign, verticalAlign, noHorizontalOverlap, noVerticalOverlap)',
|
|
@@ -212,6 +213,7 @@ export const PositionMixin = (superClass) =>
|
|
|
212
213
|
);
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
// eslint-disable-next-line max-params
|
|
215
217
|
__shouldAlignStart(targetRect, contentSize, viewportSize, margins, defaultAlignStart, noOverlap, propNames) {
|
|
216
218
|
const spaceForStartAlignment =
|
|
217
219
|
viewportSize - targetRect[noOverlap ? propNames.end : propNames.start] - margins[propNames.end];
|
|
@@ -230,6 +232,7 @@ export const PositionMixin = (superClass) =>
|
|
|
230
232
|
* Returns an object with CSS position properties to set,
|
|
231
233
|
* e.g. { top: "100px", bottom: "" }
|
|
232
234
|
*/
|
|
235
|
+
// eslint-disable-next-line max-params
|
|
233
236
|
__calculatePositionInOneDimension(targetRect, overlayRect, noOverlap, propNames, overlay, shouldAlignStart) {
|
|
234
237
|
const cssPropNameToSet = shouldAlignStart ? propNames.start : propNames.end;
|
|
235
238
|
const cssPropNameToClear = shouldAlignStart ? propNames.end : propNames.start;
|
package/src/vaadin-overlay.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c)
|
|
3
|
+
* Copyright (c) 2017 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
+
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
6
7
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
7
8
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
8
9
|
|
|
@@ -13,8 +14,41 @@ export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: ob
|
|
|
13
14
|
*/
|
|
14
15
|
export type OverlayOpenedChangedEvent = CustomEvent<{ value: boolean }>;
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Fired after the overlay is opened.
|
|
19
|
+
*/
|
|
20
|
+
export type OverlayOpenEvent = CustomEvent;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fired before the overlay will be closed.
|
|
24
|
+
* If canceled the closing of the overlay is canceled as well.
|
|
25
|
+
*/
|
|
26
|
+
export type OverlayCloseEvent = CustomEvent;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fired when the overlay will be closed.
|
|
30
|
+
*/
|
|
31
|
+
export type OverlayClosingEvent = CustomEvent;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fired before the overlay will be closed on outside click.
|
|
35
|
+
* If canceled the closing of the overlay is canceled as well.
|
|
36
|
+
*/
|
|
37
|
+
export type OverlayOutsideClickEvent = CustomEvent<{ sourceEvent: MouseEvent }>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Fired before the overlay will be closed on ESC button press.
|
|
41
|
+
* If canceled the closing of the overlay is canceled as well.
|
|
42
|
+
*/
|
|
43
|
+
export type OverlayEscapePressEvent = CustomEvent<{ sourceEvent: KeyboardEvent }>;
|
|
44
|
+
|
|
16
45
|
export interface OverlayElementEventMap {
|
|
17
46
|
'opened-changed': OverlayOpenedChangedEvent;
|
|
47
|
+
'vaadin-overlay-open': OverlayOpenEvent;
|
|
48
|
+
'vaadin-overlay-close': OverlayCloseEvent;
|
|
49
|
+
'vaadin-overlay-closing': OverlayClosingEvent;
|
|
50
|
+
'vaadin-overlay-outside-click': OverlayOutsideClickEvent;
|
|
51
|
+
'vaadin-overlay-escape-press': OverlayEscapePressEvent;
|
|
18
52
|
}
|
|
19
53
|
|
|
20
54
|
export type OverlayEventMap = HTMLElementEventMap & OverlayElementEventMap;
|
|
@@ -61,8 +95,7 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayElementEventMap;
|
|
|
61
95
|
*
|
|
62
96
|
* The overlay provides `forwardHostProp` when calling
|
|
63
97
|
* `Polymer.Templatize.templatize` for the template, so that the bindings
|
|
64
|
-
* from the parent scope propagate to the content.
|
|
65
|
-
* custom `instanceProps` object using the `instanceProps` property.
|
|
98
|
+
* from the parent scope propagate to the content.
|
|
66
99
|
*
|
|
67
100
|
* ```html
|
|
68
101
|
* <vaadin-overlay>
|
|
@@ -70,10 +103,6 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayElementEventMap;
|
|
|
70
103
|
* </vaadin-overlay>
|
|
71
104
|
* ```
|
|
72
105
|
*
|
|
73
|
-
* **NOTE:** when using `instanceProps`: because of the Polymer limitation,
|
|
74
|
-
* every template can only be templatized once, so it is important
|
|
75
|
-
* to set `instanceProps` before the `template` is assigned to the overlay.
|
|
76
|
-
*
|
|
77
106
|
* ### Styling
|
|
78
107
|
*
|
|
79
108
|
* To style the overlay content, use styles in the parent scope:
|
|
@@ -109,8 +138,13 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayElementEventMap;
|
|
|
109
138
|
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
110
139
|
*
|
|
111
140
|
* @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
|
|
141
|
+
* @fires {CustomEvent} vaadin-overlay-open - Fired after the overlay is opened.
|
|
142
|
+
* @fires {CustomEvent} vaadin-overlay-close - Fired before the overlay will be closed. If canceled the closing of the overlay is canceled as well.
|
|
143
|
+
* @fires {CustomEvent} vaadin-overlay-closing - Fired when the overlay will be closed.
|
|
144
|
+
* @fires {CustomEvent} vaadin-overlay-outside-click - Fired before the overlay will be closed on outside click. If canceled the closing of the overlay is canceled as well.
|
|
145
|
+
* @fires {CustomEvent} vaadin-overlay-escape-press - Fired before the overlay will be closed on ESC button press. If canceled the closing of the overlay is canceled as well.
|
|
112
146
|
*/
|
|
113
|
-
declare class OverlayElement extends ThemableMixin(DirMixin(HTMLElement)) {
|
|
147
|
+
declare class OverlayElement extends ThemableMixin(DirMixin(ControllerMixin(HTMLElement))) {
|
|
114
148
|
/**
|
|
115
149
|
* When true, the overlay is visible and attached to body.
|
|
116
150
|
*/
|
|
@@ -136,11 +170,6 @@ declare class OverlayElement extends ThemableMixin(DirMixin(HTMLElement)) {
|
|
|
136
170
|
*/
|
|
137
171
|
template: HTMLTemplateElement | null | undefined;
|
|
138
172
|
|
|
139
|
-
/**
|
|
140
|
-
* Optional argument for `Polymer.Templatize.templatize`.
|
|
141
|
-
*/
|
|
142
|
-
instanceProps: object | null | undefined;
|
|
143
|
-
|
|
144
173
|
/**
|
|
145
174
|
* References the content container after the template is stamped.
|
|
146
175
|
*/
|
|
@@ -179,6 +208,12 @@ declare class OverlayElement extends ThemableMixin(DirMixin(HTMLElement)) {
|
|
|
179
208
|
*/
|
|
180
209
|
restoreFocusOnClose: boolean;
|
|
181
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Set to specify the element which should be focused on overlay close,
|
|
213
|
+
* if `restoreFocusOnClose` is set to true.
|
|
214
|
+
*/
|
|
215
|
+
restoreFocusNode?: HTMLElement;
|
|
216
|
+
|
|
182
217
|
close(sourceEvent?: Event | null): void;
|
|
183
218
|
|
|
184
219
|
/**
|
package/src/vaadin-overlay.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c)
|
|
3
|
+
* Copyright (c) 2017 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
|
|
@@ -8,9 +8,10 @@ import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
|
8
8
|
import { templatize } from '@polymer/polymer/lib/utils/templatize.js';
|
|
9
9
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
10
10
|
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
11
|
+
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
11
12
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
13
|
+
import { FocusTrapController } from '@vaadin/component-base/src/focus-trap-controller.js';
|
|
12
14
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
13
|
-
import { FocusablesHelper } from './vaadin-focusables-helper.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
*
|
|
@@ -55,18 +56,7 @@ import { FocusablesHelper } from './vaadin-focusables-helper.js';
|
|
|
55
56
|
*
|
|
56
57
|
* The overlay provides `forwardHostProp` when calling
|
|
57
58
|
* `Polymer.Templatize.templatize` for the template, so that the bindings
|
|
58
|
-
* from the parent scope propagate to the content.
|
|
59
|
-
* custom `instanceProps` object using the `instanceProps` property.
|
|
60
|
-
*
|
|
61
|
-
* ```html
|
|
62
|
-
* <vaadin-overlay>
|
|
63
|
-
* <template>Overlay content</template>
|
|
64
|
-
* </vaadin-overlay>
|
|
65
|
-
* ```
|
|
66
|
-
*
|
|
67
|
-
* **NOTE:** when using `instanceProps`: because of the Polymer limitation,
|
|
68
|
-
* every template can only be templatized once, so it is important
|
|
69
|
-
* to set `instanceProps` before the `template` is assigned to the overlay.
|
|
59
|
+
* from the parent scope propagate to the content.
|
|
70
60
|
*
|
|
71
61
|
* ### Styling
|
|
72
62
|
*
|
|
@@ -103,12 +93,18 @@ import { FocusablesHelper } from './vaadin-focusables-helper.js';
|
|
|
103
93
|
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
104
94
|
*
|
|
105
95
|
* @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
|
|
96
|
+
* @fires {CustomEvent} vaadin-overlay-open - Fired after the overlay is opened.
|
|
97
|
+
* @fires {CustomEvent} vaadin-overlay-close - Fired before the overlay will be closed. If canceled the closing of the overlay is canceled as well.
|
|
98
|
+
* @fires {CustomEvent} vaadin-overlay-closing - Fired when the overlay will be closed.
|
|
99
|
+
* @fires {CustomEvent} vaadin-overlay-outside-click - Fired before the overlay will be closed on outside click. If canceled the closing of the overlay is canceled as well.
|
|
100
|
+
* @fires {CustomEvent} vaadin-overlay-escape-press - Fired before the overlay will be closed on ESC button press. If canceled the closing of the overlay is canceled as well.
|
|
106
101
|
*
|
|
107
102
|
* @extends HTMLElement
|
|
108
103
|
* @mixes ThemableMixin
|
|
109
104
|
* @mixes DirMixin
|
|
105
|
+
* @mixes ControllerMixin
|
|
110
106
|
*/
|
|
111
|
-
class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
107
|
+
class OverlayElement extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
|
|
112
108
|
static get template() {
|
|
113
109
|
return html`
|
|
114
110
|
<style>
|
|
@@ -227,13 +223,6 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
227
223
|
notify: true
|
|
228
224
|
},
|
|
229
225
|
|
|
230
|
-
/**
|
|
231
|
-
* Optional argument for `Polymer.Templatize.templatize`.
|
|
232
|
-
*/
|
|
233
|
-
instanceProps: {
|
|
234
|
-
type: Object
|
|
235
|
-
},
|
|
236
|
-
|
|
237
226
|
/**
|
|
238
227
|
* References the content container after the template is stamped.
|
|
239
228
|
* @type {!HTMLElement | undefined}
|
|
@@ -300,6 +289,15 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
300
289
|
value: false
|
|
301
290
|
},
|
|
302
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Set to specify the element which should be focused on overlay close,
|
|
294
|
+
* if `restoreFocusOnClose` is set to true.
|
|
295
|
+
* @type {HTMLElement}
|
|
296
|
+
*/
|
|
297
|
+
restoreFocusNode: {
|
|
298
|
+
type: HTMLElement
|
|
299
|
+
},
|
|
300
|
+
|
|
303
301
|
/** @private */
|
|
304
302
|
_mouseDownInside: {
|
|
305
303
|
type: Boolean
|
|
@@ -330,9 +328,6 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
330
328
|
/** @private */
|
|
331
329
|
_oldTemplate: Object,
|
|
332
330
|
|
|
333
|
-
/** @private */
|
|
334
|
-
_oldInstanceProps: Object,
|
|
335
|
-
|
|
336
331
|
/** @private */
|
|
337
332
|
_oldRenderer: Object,
|
|
338
333
|
|
|
@@ -342,7 +337,7 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
342
337
|
}
|
|
343
338
|
|
|
344
339
|
static get observers() {
|
|
345
|
-
return ['_templateOrRendererChanged(template, renderer, owner, model,
|
|
340
|
+
return ['_templateOrRendererChanged(template, renderer, owner, model, opened)'];
|
|
346
341
|
}
|
|
347
342
|
|
|
348
343
|
constructor() {
|
|
@@ -363,6 +358,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
363
358
|
if (isIOS) {
|
|
364
359
|
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
365
360
|
}
|
|
361
|
+
|
|
362
|
+
this.__focusTrapController = new FocusTrapController(this);
|
|
366
363
|
}
|
|
367
364
|
|
|
368
365
|
/** @protected */
|
|
@@ -377,6 +374,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
377
374
|
// and <vaadin-context-menu>).
|
|
378
375
|
this.addEventListener('click', () => {});
|
|
379
376
|
this.$.backdrop.addEventListener('click', () => {});
|
|
377
|
+
|
|
378
|
+
this.addController(this.__focusTrapController);
|
|
380
379
|
}
|
|
381
380
|
|
|
382
381
|
/** @private */
|
|
@@ -516,15 +515,7 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
516
515
|
return;
|
|
517
516
|
}
|
|
518
517
|
|
|
519
|
-
|
|
520
|
-
if (event.key === 'Tab' && this.focusTrap && !event.defaultPrevented) {
|
|
521
|
-
// if only tab key is pressed, cycle forward, else cycle backwards.
|
|
522
|
-
this._cycleTab(event.shiftKey ? -1 : 1);
|
|
523
|
-
|
|
524
|
-
event.preventDefault();
|
|
525
|
-
|
|
526
|
-
// ESC
|
|
527
|
-
} else if (event.key === 'Escape' || event.key === 'Esc') {
|
|
518
|
+
if (event.key === 'Escape') {
|
|
528
519
|
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
529
520
|
bubbles: true,
|
|
530
521
|
cancelable: true,
|
|
@@ -560,8 +551,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
560
551
|
this._animatedOpening();
|
|
561
552
|
|
|
562
553
|
afterNextRender(this, () => {
|
|
563
|
-
if (this.focusTrap
|
|
564
|
-
this.
|
|
554
|
+
if (this.focusTrap) {
|
|
555
|
+
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
565
556
|
}
|
|
566
557
|
|
|
567
558
|
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
@@ -572,6 +563,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
572
563
|
this._addGlobalListeners();
|
|
573
564
|
}
|
|
574
565
|
} else if (wasOpened) {
|
|
566
|
+
this.__focusTrapController.releaseFocus();
|
|
567
|
+
|
|
575
568
|
this._animatedClosing();
|
|
576
569
|
|
|
577
570
|
if (!this.modeless) {
|
|
@@ -638,16 +631,12 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
638
631
|
}
|
|
639
632
|
this.setAttribute('opening', '');
|
|
640
633
|
|
|
641
|
-
const finishOpening = () => {
|
|
642
|
-
document.addEventListener('iron-overlay-canceled', this._boundIronOverlayCanceledListener);
|
|
643
|
-
|
|
644
|
-
this.removeAttribute('opening');
|
|
645
|
-
};
|
|
646
|
-
|
|
647
634
|
if (this._shouldAnimate()) {
|
|
648
|
-
this._enqueueAnimation('opening',
|
|
635
|
+
this._enqueueAnimation('opening', () => {
|
|
636
|
+
this._finishOpening();
|
|
637
|
+
});
|
|
649
638
|
} else {
|
|
650
|
-
|
|
639
|
+
this._finishOpening();
|
|
651
640
|
}
|
|
652
641
|
}
|
|
653
642
|
|
|
@@ -660,6 +649,25 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
660
649
|
}
|
|
661
650
|
|
|
662
651
|
/** @protected */
|
|
652
|
+
_finishOpening() {
|
|
653
|
+
document.addEventListener('iron-overlay-canceled', this._boundIronOverlayCanceledListener);
|
|
654
|
+
this.removeAttribute('opening');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** @protected */
|
|
658
|
+
_finishClosing() {
|
|
659
|
+
document.removeEventListener('iron-overlay-canceled', this._boundIronOverlayCanceledListener);
|
|
660
|
+
this._detachOverlay();
|
|
661
|
+
this.$.overlay.style.removeProperty('pointer-events');
|
|
662
|
+
this.removeAttribute('closing');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @event vaadin-overlay-closing
|
|
667
|
+
* Fired when the overlay will be closed.
|
|
668
|
+
*
|
|
669
|
+
* @protected
|
|
670
|
+
*/
|
|
663
671
|
_animatedClosing() {
|
|
664
672
|
if (this.hasAttribute('opening')) {
|
|
665
673
|
this._flushAnimation('opening');
|
|
@@ -667,7 +675,11 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
667
675
|
if (this._placeholder) {
|
|
668
676
|
this._exitModalState();
|
|
669
677
|
|
|
670
|
-
|
|
678
|
+
// Use this.restoreFocusNode if specified, otherwise fallback to the node
|
|
679
|
+
// which was focused before opening the overlay.
|
|
680
|
+
const restoreFocusNode = this.restoreFocusNode || this.__restoreFocusNode;
|
|
681
|
+
|
|
682
|
+
if (this.restoreFocusOnClose && restoreFocusNode) {
|
|
671
683
|
// If the activeElement is `<body>` or inside the overlay,
|
|
672
684
|
// we are allowed to restore the focus. In all the other
|
|
673
685
|
// cases focus might have been moved elsewhere by another
|
|
@@ -676,24 +688,22 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
676
688
|
const activeElement = this._getActiveElement();
|
|
677
689
|
|
|
678
690
|
if (activeElement === document.body || this._deepContains(activeElement)) {
|
|
679
|
-
|
|
691
|
+
// Focusing the restoreFocusNode doesn't always work synchronously on Firefox and Safari
|
|
692
|
+
// (e.g. combo-box overlay close on outside click).
|
|
693
|
+
setTimeout(() => restoreFocusNode.focus());
|
|
680
694
|
}
|
|
681
695
|
this.__restoreFocusNode = null;
|
|
682
696
|
}
|
|
683
697
|
|
|
684
698
|
this.setAttribute('closing', '');
|
|
685
|
-
|
|
686
|
-
const finishClosing = () => {
|
|
687
|
-
document.removeEventListener('iron-overlay-canceled', this._boundIronOverlayCanceledListener);
|
|
688
|
-
this._detachOverlay();
|
|
689
|
-
this.shadowRoot.querySelector('[part="overlay"]').style.removeProperty('pointer-events');
|
|
690
|
-
this.removeAttribute('closing');
|
|
691
|
-
};
|
|
699
|
+
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
692
700
|
|
|
693
701
|
if (this._shouldAnimate()) {
|
|
694
|
-
this._enqueueAnimation('closing',
|
|
702
|
+
this._enqueueAnimation('closing', () => {
|
|
703
|
+
this._finishClosing();
|
|
704
|
+
});
|
|
695
705
|
} else {
|
|
696
|
-
|
|
706
|
+
this._finishClosing();
|
|
697
707
|
}
|
|
698
708
|
}
|
|
699
709
|
}
|
|
@@ -825,15 +835,13 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
825
835
|
|
|
826
836
|
/**
|
|
827
837
|
* @param {!HTMLTemplateElement} template
|
|
828
|
-
* @param {object} instanceProps
|
|
829
838
|
* @protected
|
|
830
839
|
*/
|
|
831
|
-
_stampOverlayTemplate(template
|
|
840
|
+
_stampOverlayTemplate(template) {
|
|
832
841
|
this._removeOldContent();
|
|
833
842
|
|
|
834
843
|
if (!template._Templatizer) {
|
|
835
844
|
template._Templatizer = templatize(template, this, {
|
|
836
|
-
instanceProps: instanceProps,
|
|
837
845
|
forwardHostProp: function (prop, value) {
|
|
838
846
|
if (this._instance) {
|
|
839
847
|
this._instance.forwardHostProp(prop, value);
|
|
@@ -886,7 +894,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
886
894
|
}
|
|
887
895
|
|
|
888
896
|
/** @private */
|
|
889
|
-
|
|
897
|
+
// eslint-disable-next-line max-params
|
|
898
|
+
_templateOrRendererChanged(template, renderer, owner, model, opened) {
|
|
890
899
|
if (template && renderer) {
|
|
891
900
|
this._removeNewRendererOrTemplate(template, this._oldTemplate, renderer, this._oldRenderer);
|
|
892
901
|
throw new Error('You should only use either a renderer or a template for overlay content');
|
|
@@ -896,8 +905,7 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
896
905
|
this._oldModel = model;
|
|
897
906
|
this._oldOwner = owner;
|
|
898
907
|
|
|
899
|
-
const
|
|
900
|
-
this._oldInstanceProps = instanceProps;
|
|
908
|
+
const templateChanged = this._oldTemplate !== template;
|
|
901
909
|
this._oldTemplate = template;
|
|
902
910
|
|
|
903
911
|
const rendererChanged = this._oldRenderer !== renderer;
|
|
@@ -915,8 +923,8 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
915
923
|
delete this.content._$litPart$;
|
|
916
924
|
}
|
|
917
925
|
|
|
918
|
-
if (template &&
|
|
919
|
-
this._stampOverlayTemplate(template
|
|
926
|
+
if (template && templateChanged) {
|
|
927
|
+
this._stampOverlayTemplate(template);
|
|
920
928
|
} else if (renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
921
929
|
if (opened) {
|
|
922
930
|
this.requestContentUpdate();
|
|
@@ -924,59 +932,6 @@ class OverlayElement extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
|
924
932
|
}
|
|
925
933
|
}
|
|
926
934
|
|
|
927
|
-
/**
|
|
928
|
-
* @param {Element} element
|
|
929
|
-
* @return {boolean}
|
|
930
|
-
* @protected
|
|
931
|
-
*/
|
|
932
|
-
_isFocused(element) {
|
|
933
|
-
return element && element.getRootNode().activeElement === element;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
/**
|
|
937
|
-
* @param {Element[]} elements
|
|
938
|
-
* @return {number}
|
|
939
|
-
* @protected
|
|
940
|
-
*/
|
|
941
|
-
_focusedIndex(elements) {
|
|
942
|
-
elements = elements || this._getFocusableElements();
|
|
943
|
-
return elements.indexOf(elements.filter(this._isFocused).pop());
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
/**
|
|
947
|
-
* @param {number} increment
|
|
948
|
-
* @param {number | undefined} index
|
|
949
|
-
* @protected
|
|
950
|
-
*/
|
|
951
|
-
_cycleTab(increment, index) {
|
|
952
|
-
const focusableElements = this._getFocusableElements();
|
|
953
|
-
|
|
954
|
-
if (index === undefined) {
|
|
955
|
-
index = this._focusedIndex(focusableElements);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
index += increment;
|
|
959
|
-
|
|
960
|
-
// rollover to first item
|
|
961
|
-
if (index >= focusableElements.length) {
|
|
962
|
-
index = 0;
|
|
963
|
-
// go to last item
|
|
964
|
-
} else if (index < 0) {
|
|
965
|
-
index = focusableElements.length - 1;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
focusableElements[index].focus();
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* @return {!Array<!HTMLElement>}
|
|
973
|
-
* @protected
|
|
974
|
-
*/
|
|
975
|
-
_getFocusableElements() {
|
|
976
|
-
// collect all focusable elements
|
|
977
|
-
return FocusablesHelper.getTabbableNodes(this.$.overlay);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
935
|
/**
|
|
981
936
|
* @return {!Element}
|
|
982
937
|
* @protected
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `Polymer.IronFocusablesHelper` relies on some Polymer-specific legacy API,
|
|
3
|
-
* especially the `root` property which does not exist for native shadow DOM.
|
|
4
|
-
* That's why we have this helper here.
|
|
5
|
-
* See https://github.com/PolymerElements/iron-overlay-behavior/issues/282
|
|
6
|
-
*/
|
|
7
|
-
declare class FocusablesHelper {
|
|
8
|
-
/**
|
|
9
|
-
* Returns a sorted array of tabbable nodes, including the root node.
|
|
10
|
-
* It searches the tabbable nodes in the light and shadow dom of the children,
|
|
11
|
-
* sorting the result by tabindex.
|
|
12
|
-
*/
|
|
13
|
-
static getTabbableNodes(node: Node): HTMLElement[];
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Returns if a element is focusable.
|
|
17
|
-
*/
|
|
18
|
-
static isFocusable(element: HTMLElement): boolean;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns if a element is tabbable. To be tabbable, a element must be
|
|
22
|
-
* focusable, visible, and with a tabindex !== -1.
|
|
23
|
-
*/
|
|
24
|
-
static isTabbable(element: HTMLElement): boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export { FocusablesHelper };
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* `Polymer.IronFocusablesHelper` relies on some Polymer-specific legacy API,
|
|
9
|
-
* especially the `root` property which does not exist for native shadow DOM.
|
|
10
|
-
* That's why we have this helper here.
|
|
11
|
-
* See https://github.com/PolymerElements/iron-overlay-behavior/issues/282
|
|
12
|
-
*/
|
|
13
|
-
class FocusablesHelper {
|
|
14
|
-
/**
|
|
15
|
-
* Returns a sorted array of tabbable nodes, including the root node.
|
|
16
|
-
* It searches the tabbable nodes in the light and shadow dom of the children,
|
|
17
|
-
* sorting the result by tabindex.
|
|
18
|
-
* @param {!Node} node
|
|
19
|
-
* @return {!Array<!HTMLElement>}
|
|
20
|
-
*/
|
|
21
|
-
static getTabbableNodes(node) {
|
|
22
|
-
const result = [];
|
|
23
|
-
// If there is at least one element with tabindex > 0, we need to sort
|
|
24
|
-
// the final array by tabindex.
|
|
25
|
-
const needsSortByTabIndex = this._collectTabbableNodes(node, result);
|
|
26
|
-
if (needsSortByTabIndex) {
|
|
27
|
-
return this._sortByTabIndex(result);
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Returns if a element is focusable.
|
|
34
|
-
* @param {!HTMLElement} element
|
|
35
|
-
* @return {boolean}
|
|
36
|
-
*/
|
|
37
|
-
static isFocusable(element) {
|
|
38
|
-
// From http://stackoverflow.com/a/1600194/4228703:
|
|
39
|
-
// There isn't a definite list, it's up to the browser. The only
|
|
40
|
-
// standard we have is DOM Level 2 HTML
|
|
41
|
-
// https://www.w3.org/TR/DOM-Level-2-HTML/html.html, according to which the
|
|
42
|
-
// only elements that have a focus() method are HTMLInputElement,
|
|
43
|
-
// HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This
|
|
44
|
-
// notably omits HTMLButtonElement and HTMLAreaElement. Referring to these
|
|
45
|
-
// tests with tabbables in different browsers
|
|
46
|
-
// http://allyjs.io/data-tables/focusable.html
|
|
47
|
-
|
|
48
|
-
// Elements that cannot be focused if they have [disabled] attribute.
|
|
49
|
-
if (element.matches('input, select, textarea, button, object')) {
|
|
50
|
-
return element.matches(':not([disabled])');
|
|
51
|
-
}
|
|
52
|
-
// Elements that can be focused even if they have [disabled] attribute.
|
|
53
|
-
return element.matches('a[href], area[href], iframe, [tabindex], [contentEditable]');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Returns if a element is tabbable. To be tabbable, a element must be
|
|
58
|
-
* focusable, visible, and with a tabindex !== -1.
|
|
59
|
-
* @param {!HTMLElement} element
|
|
60
|
-
* @return {boolean}
|
|
61
|
-
*/
|
|
62
|
-
static isTabbable(element) {
|
|
63
|
-
return this.isFocusable(element) && element.matches(':not([tabindex="-1"])') && this._isVisible(element);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Returns the normalized element tabindex. If not focusable, returns -1.
|
|
68
|
-
* It checks for the attribute "tabindex" instead of the element property
|
|
69
|
-
* `tabIndex` since browsers assign different values to it.
|
|
70
|
-
* e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
|
|
71
|
-
* @param {!HTMLElement} element
|
|
72
|
-
* @return {!number}
|
|
73
|
-
* @private
|
|
74
|
-
*/
|
|
75
|
-
static _normalizedTabIndex(element) {
|
|
76
|
-
if (this.isFocusable(element)) {
|
|
77
|
-
const tabIndex = element.getAttribute('tabindex') || 0;
|
|
78
|
-
return Number(tabIndex);
|
|
79
|
-
}
|
|
80
|
-
return -1;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Searches for nodes that are tabbable and adds them to the `result` array.
|
|
85
|
-
* Returns if the `result` array needs to be sorted by tabindex.
|
|
86
|
-
* @param {!Node} node The starting point for the search; added to `result` if tabbable.
|
|
87
|
-
* @param {!Array<!HTMLElement>} result
|
|
88
|
-
* @return {boolean}
|
|
89
|
-
* @private
|
|
90
|
-
*/
|
|
91
|
-
static _collectTabbableNodes(node, result) {
|
|
92
|
-
// If not an element or not visible, no need to explore children.
|
|
93
|
-
if (node.nodeType !== Node.ELEMENT_NODE || !this._isVisible(node)) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
const element = /** @type {!HTMLElement} */ (node);
|
|
97
|
-
const tabIndex = this._normalizedTabIndex(element);
|
|
98
|
-
let needsSort = tabIndex > 0;
|
|
99
|
-
if (tabIndex >= 0) {
|
|
100
|
-
result.push(element);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// In ShadowDOM v1, tab order is affected by the order of distribution.
|
|
104
|
-
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
|
|
105
|
-
// in ShadowDOM v0 tab order is not affected by the distribution order,
|
|
106
|
-
// in fact getTabbableNodes(#root) returns [#B, #A].
|
|
107
|
-
// <div id="root">
|
|
108
|
-
// <!-- shadow -->
|
|
109
|
-
// <slot name="a">
|
|
110
|
-
// <slot name="b">
|
|
111
|
-
// <!-- /shadow -->
|
|
112
|
-
// <input id="A" slot="a">
|
|
113
|
-
// <input id="B" slot="b" tabindex="1">
|
|
114
|
-
// </div>
|
|
115
|
-
let children;
|
|
116
|
-
if (element.localName === 'slot') {
|
|
117
|
-
children = element.assignedNodes({ flatten: true });
|
|
118
|
-
} else {
|
|
119
|
-
// Use shadow root if possible, will check for distributed nodes.
|
|
120
|
-
children = (element.shadowRoot || element).children;
|
|
121
|
-
}
|
|
122
|
-
if (children) {
|
|
123
|
-
for (let i = 0; i < children.length; i++) {
|
|
124
|
-
// Ensure method is always invoked to collect tabbable children.
|
|
125
|
-
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return needsSort;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Returns false if the element has `visibility: hidden` or `display: none`
|
|
133
|
-
* @param {!HTMLElement} element
|
|
134
|
-
* @return {boolean}
|
|
135
|
-
* @private
|
|
136
|
-
*/
|
|
137
|
-
static _isVisible(element) {
|
|
138
|
-
// Check inline style first to save a re-flow. If looks good, check also
|
|
139
|
-
// computed style.
|
|
140
|
-
let style = element.style;
|
|
141
|
-
if (style.visibility !== 'hidden' && style.display !== 'none') {
|
|
142
|
-
style = window.getComputedStyle(element);
|
|
143
|
-
return style.visibility !== 'hidden' && style.display !== 'none';
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Sorts an array of tabbable elements by tabindex. Returns a new array.
|
|
150
|
-
* @param {!Array<!HTMLElement>} tabbables
|
|
151
|
-
* @return {!Array<!HTMLElement>}
|
|
152
|
-
* @private
|
|
153
|
-
*/
|
|
154
|
-
static _sortByTabIndex(tabbables) {
|
|
155
|
-
// Implement a merge sort as Array.prototype.sort does a non-stable sort
|
|
156
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
|
|
157
|
-
const len = tabbables.length;
|
|
158
|
-
if (len < 2) {
|
|
159
|
-
return tabbables;
|
|
160
|
-
}
|
|
161
|
-
const pivot = Math.ceil(len / 2);
|
|
162
|
-
const left = this._sortByTabIndex(tabbables.slice(0, pivot));
|
|
163
|
-
const right = this._sortByTabIndex(tabbables.slice(pivot));
|
|
164
|
-
return this._mergeSortByTabIndex(left, right);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Merge sort iterator, merges the two arrays into one, sorted by tab index.
|
|
169
|
-
* @param {!Array<!HTMLElement>} left
|
|
170
|
-
* @param {!Array<!HTMLElement>} right
|
|
171
|
-
* @return {!Array<!HTMLElement>}
|
|
172
|
-
* @private
|
|
173
|
-
*/
|
|
174
|
-
static _mergeSortByTabIndex(left, right) {
|
|
175
|
-
const result = [];
|
|
176
|
-
while (left.length > 0 && right.length > 0) {
|
|
177
|
-
if (this._hasLowerTabOrder(left[0], right[0])) {
|
|
178
|
-
result.push(right.shift());
|
|
179
|
-
} else {
|
|
180
|
-
result.push(left.shift());
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return result.concat(left, right);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Returns if element `a` has lower tab order compared to element `b`
|
|
189
|
-
* (both elements are assumed to be focusable and tabbable).
|
|
190
|
-
* Elements with tabindex = 0 have lower tab order compared to elements
|
|
191
|
-
* with tabindex > 0.
|
|
192
|
-
* If both have same tabindex, it returns false.
|
|
193
|
-
* @param {!HTMLElement} a
|
|
194
|
-
* @param {!HTMLElement} b
|
|
195
|
-
* @return {boolean}
|
|
196
|
-
* @private
|
|
197
|
-
*/
|
|
198
|
-
static _hasLowerTabOrder(a, b) {
|
|
199
|
-
// Normalize tabIndexes
|
|
200
|
-
// e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
|
|
201
|
-
const ati = Math.max(a.tabIndex, 0);
|
|
202
|
-
const bti = Math.max(b.tabIndex, 0);
|
|
203
|
-
return ati === 0 || bti === 0 ? bti > ati : ati > bti;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export { FocusablesHelper };
|