@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-alpha9",
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-alpha9",
40
- "@vaadin/component-base": "24.1.0-alpha9",
41
- "@vaadin/vaadin-lumo-styles": "24.1.0-alpha9",
42
- "@vaadin/vaadin-material-styles": "24.1.0-alpha9",
43
- "@vaadin/vaadin-themable-mixin": "24.1.0-alpha9"
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": "db4fe44603a6702b85b0da2a6d033ddf8ffea5c4"
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
+ };
@@ -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(ControllerMixin(HTMLElement))) {
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
  */
@@ -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 ControllerMixin
76
+ * @mixes OverlayFocusMixin
78
77
  */
79
- class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
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
- // Store focused node.
486
- this.__restoreFocusNode = this._getActiveElement();
452
+ this._storeFocus();
453
+
487
454
  this._animatedOpening();
488
455
 
489
456
  afterNextRender(this, () => {
490
- if (this.focusTrap) {
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
- if (this.focusTrap) {
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}