@vaadin/overlay 24.1.0-beta1 → 24.1.0-beta3

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-beta1",
3
+ "version": "24.1.0-beta3",
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-beta1",
40
- "@vaadin/component-base": "24.1.0-beta1",
41
- "@vaadin/vaadin-lumo-styles": "24.1.0-beta1",
42
- "@vaadin/vaadin-material-styles": "24.1.0-beta1",
43
- "@vaadin/vaadin-themable-mixin": "24.1.0-beta1"
39
+ "@vaadin/a11y-base": "24.1.0-beta3",
40
+ "@vaadin/component-base": "24.1.0-beta3",
41
+ "@vaadin/vaadin-lumo-styles": "24.1.0-beta3",
42
+ "@vaadin/vaadin-material-styles": "24.1.0-beta3",
43
+ "@vaadin/vaadin-themable-mixin": "24.1.0-beta3"
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": "f0ddb6576073a6af05ab29867bc5ec82e334f9d7"
51
+ "gitHead": "60c032c22301d2c0a2742fa673a8e82dd2583ce3"
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,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 ControllerMixin
76
+ * @mixes OverlayFocusMixin
79
77
  */
80
- class Overlay extends ThemableMixin(DirMixin(ControllerMixin(PolymerElement))) {
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
- if (this.restoreFocusOnClose) {
487
- this.__storeFocus();
488
- }
452
+ this._storeFocus();
489
453
 
490
454
  this._animatedOpening();
491
455
 
492
456
  afterNextRender(this, () => {
493
- if (this.focusTrap) {
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
- if (this.focusTrap) {
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')) {