@vaadin/overlay 25.2.7 → 25.3.0-alpha10

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.
@@ -26,6 +26,16 @@
26
26
  "description": "",
27
27
  "name": "OverlayFocusMixin",
28
28
  "members": [
29
+ {
30
+ "kind": "field",
31
+ "name": "autofocus",
32
+ "privacy": "public",
33
+ "type": {
34
+ "text": "boolean"
35
+ },
36
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
37
+ "attribute": "autofocus"
38
+ },
29
39
  {
30
40
  "kind": "field",
31
41
  "name": "focusTrap",
@@ -33,7 +43,7 @@
33
43
  "type": {
34
44
  "text": "boolean"
35
45
  },
36
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
46
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
37
47
  "attribute": "focus-trap"
38
48
  },
39
49
  {
@@ -58,12 +68,20 @@
58
68
  }
59
69
  ],
60
70
  "attributes": [
71
+ {
72
+ "name": "autofocus",
73
+ "type": {
74
+ "text": "boolean"
75
+ },
76
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
77
+ "fieldName": "autofocus"
78
+ },
61
79
  {
62
80
  "name": "focus-trap",
63
81
  "type": {
64
82
  "text": "boolean"
65
83
  },
66
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
84
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
67
85
  "fieldName": "focusTrap"
68
86
  },
69
87
  {
@@ -110,6 +128,20 @@
110
128
  "description": "",
111
129
  "name": "OverlayMixin",
112
130
  "members": [
131
+ {
132
+ "kind": "field",
133
+ "name": "autofocus",
134
+ "privacy": "public",
135
+ "type": {
136
+ "text": "boolean"
137
+ },
138
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
139
+ "attribute": "autofocus",
140
+ "inheritedFrom": {
141
+ "name": "OverlayFocusMixin",
142
+ "module": "src/vaadin-overlay-focus-mixin.js"
143
+ }
144
+ },
113
145
  {
114
146
  "kind": "method",
115
147
  "name": "bringToFront",
@@ -138,7 +170,7 @@
138
170
  "type": {
139
171
  "text": "boolean"
140
172
  },
141
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
173
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
142
174
  "attribute": "focus-trap",
143
175
  "inheritedFrom": {
144
176
  "name": "OverlayFocusMixin",
@@ -284,12 +316,24 @@
284
316
  }
285
317
  ],
286
318
  "attributes": [
319
+ {
320
+ "name": "autofocus",
321
+ "type": {
322
+ "text": "boolean"
323
+ },
324
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
325
+ "fieldName": "autofocus",
326
+ "inheritedFrom": {
327
+ "name": "OverlayFocusMixin",
328
+ "module": "src/vaadin-overlay-focus-mixin.js"
329
+ }
330
+ },
287
331
  {
288
332
  "name": "focus-trap",
289
333
  "type": {
290
334
  "text": "boolean"
291
335
  },
292
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
336
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
293
337
  "fieldName": "focusTrap",
294
338
  "inheritedFrom": {
295
339
  "name": "OverlayFocusMixin",
@@ -593,6 +637,24 @@
593
637
  "name": "rootMargin",
594
638
  "default": "`${-insetTop}px ${-insetRight}px ${-insetBottom}px ${-insetLeft}px`"
595
639
  },
640
+ {
641
+ "kind": "function",
642
+ "name": "getStateAnimations",
643
+ "parameters": [
644
+ {
645
+ "name": "element",
646
+ "type": {
647
+ "text": "HTMLElement"
648
+ }
649
+ }
650
+ ],
651
+ "description": "Collect the animations that report the state of the given element, so that their end can be\nawaited before the element is hidden or removed. The animations are read instead of the\ncomputed style, so that the decision to wait cannot disagree with what the browser actually\ncreated: a keyframes rule that does not exist and an element that is not rendered both give\nan empty list.\n\nOnly CSS animations count, as the transitions and the script animations that `getAnimations()`\nalso returns do not report the state. Animations of any content are already left out, as\n`getAnimations()` only descends into the subtree when asked to. Animations that take no time\nare dropped as well, so that a delay on its own does not hold the state.",
652
+ "return": {
653
+ "type": {
654
+ "text": "!Array<!Animation>"
655
+ }
656
+ }
657
+ },
596
658
  {
597
659
  "kind": "function",
598
660
  "name": "setOverlayStateAttribute",
@@ -631,6 +693,14 @@
631
693
  "module": "src/vaadin-overlay-utils.js"
632
694
  }
633
695
  },
696
+ {
697
+ "kind": "js",
698
+ "name": "getStateAnimations",
699
+ "declaration": {
700
+ "name": "getStateAnimations",
701
+ "module": "src/vaadin-overlay-utils.js"
702
+ }
703
+ },
634
704
  {
635
705
  "kind": "js",
636
706
  "name": "setOverlayStateAttribute",
@@ -650,6 +720,20 @@
650
720
  "description": "`<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay\ncan be populated imperatively by using `renderer` callback function.\n\n### Rendering\n\nThe renderer function provides `root`, `owner`, `model` arguments when applicable.\nGenerate DOM content by using `model` object properties if needed, append it to the `root`\nelement and control the state of the host element by accessing `owner`. Before generating new\ncontent, users are able to check if there is already content in `root` for reusing it.\n\n```html\n<vaadin-overlay id=\"overlay\"></vaadin-overlay>\n```\n```js\nconst overlay = document.querySelector('#overlay');\noverlay.renderer = function(root) {\n root.textContent = \"Overlay content\";\n};\n```\n\nRenderer is called on the opening of the overlay and each time the related model is updated.\nDOM generated during the renderer call can be reused\nin the next renderer call and will be provided with the `root` argument.\nOn first call it will be empty.\n\n### Styling\n\nThe following Shadow DOM parts are available for styling:\n\nPart name | Description\n-----------|---------------------------------------------------------|\n`backdrop` | Backdrop of the overlay\n`overlay` | Container for position/sizing/alignment of the content\n`content` | Content of the overlay\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n----------|------------\n`opening` | Applied just after the overlay is opened. You can apply a CSS animation for this state.\n`closing` | Applied just before the overlay is closed. You can apply a CSS animation for this state.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property | Description | Default value\n---|---|---\n`--vaadin-overlay-viewport-bottom` | Bottom offset of the visible viewport area | `0` or detected offset\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
651
721
  "name": "Overlay",
652
722
  "members": [
723
+ {
724
+ "kind": "field",
725
+ "name": "autofocus",
726
+ "privacy": "public",
727
+ "type": {
728
+ "text": "boolean"
729
+ },
730
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
731
+ "attribute": "autofocus",
732
+ "inheritedFrom": {
733
+ "name": "OverlayFocusMixin",
734
+ "module": "src/vaadin-overlay-focus-mixin.js"
735
+ }
736
+ },
653
737
  {
654
738
  "kind": "method",
655
739
  "name": "bringToFront",
@@ -682,7 +766,7 @@
682
766
  "type": {
683
767
  "text": "boolean"
684
768
  },
685
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
769
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
686
770
  "attribute": "focus-trap",
687
771
  "inheritedFrom": {
688
772
  "name": "OverlayFocusMixin",
@@ -937,12 +1021,24 @@
937
1021
  "tagName": "vaadin-overlay",
938
1022
  "customElement": true,
939
1023
  "attributes": [
1024
+ {
1025
+ "name": "autofocus",
1026
+ "type": {
1027
+ "text": "boolean"
1028
+ },
1029
+ "description": "Set to true to move focus into the overlay automatically on open.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
1030
+ "fieldName": "autofocus",
1031
+ "inheritedFrom": {
1032
+ "name": "OverlayFocusMixin",
1033
+ "module": "src/vaadin-overlay-focus-mixin.js"
1034
+ }
1035
+ },
940
1036
  {
941
1037
  "name": "focus-trap",
942
1038
  "type": {
943
1039
  "text": "boolean"
944
1040
  },
945
- "description": "When true, opening the overlay moves focus to the first focusable child,\nor to the overlay part with tabindex if there are no focusable children.",
1041
+ "description": "Set to true to move focus into the overlay automatically on open\nand keep it inside: Tab and Shift+Tab cycle through the overlay's\ncontent until the overlay is closed.\n\nFocus moves to the first tabbable element in the tab order. This\ncan be the overlay itself if it has `tabindex` attribute set to `0`\non the host element or the `overlay` shadow DOM part.",
946
1042
  "fieldName": "focusTrap",
947
1043
  "inheritedFrom": {
948
1044
  "name": "OverlayFocusMixin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/overlay",
3
- "version": "25.2.7",
3
+ "version": "25.3.0-alpha10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,19 +34,19 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "@open-wc/dedupe-mixin": "^1.3.0",
37
- "@vaadin/a11y-base": "~25.2.7",
38
- "@vaadin/component-base": "~25.2.7",
39
- "@vaadin/vaadin-themable-mixin": "~25.2.7",
37
+ "@vaadin/a11y-base": "25.3.0-alpha10",
38
+ "@vaadin/component-base": "25.3.0-alpha10",
39
+ "@vaadin/vaadin-themable-mixin": "25.3.0-alpha10",
40
40
  "lit": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@vaadin/aura": "~25.2.7",
44
- "@vaadin/chai-plugins": "~25.2.7",
45
- "@vaadin/test-runner-commands": "~25.2.7",
43
+ "@vaadin/aura": "25.3.0-alpha10",
44
+ "@vaadin/chai-plugins": "25.3.0-alpha10",
45
+ "@vaadin/test-runner-commands": "25.3.0-alpha10",
46
46
  "@vaadin/testing-helpers": "^2.0.0",
47
- "@vaadin/vaadin-lumo-styles": "~25.2.7",
47
+ "@vaadin/vaadin-lumo-styles": "25.3.0-alpha10",
48
48
  "sinon": "^22.0.0"
49
49
  },
50
50
  "customElements": "custom-elements.json",
51
- "gitHead": "14be1af674f3968cce68c6eb4742340694c4a949"
51
+ "gitHead": "f2833abdf9b613fa0d0ed216830e3f4de87b7dac"
52
52
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { CSSResult } from 'lit';
7
+
8
+ export const overlayAnimationProperties: CSSResult;
9
+
10
+ export const overlayAnimationStyles: CSSResult;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { css, unsafeCSS } from 'lit';
7
+ import { registerCSSProperty } from '@vaadin/component-base/src/css-utils.js';
8
+
9
+ /**
10
+ * The custom properties that make up the overlay animation CSS API. Both the
11
+ * property registrations and the `overlayAnimationProperties` snippet below are
12
+ * derived from this list, so a new property only needs to be added here.
13
+ *
14
+ * Only the closed state is part of the API. The opened state is left out of the
15
+ * keyframes so that it comes from the part itself, see the keyframes below.
16
+ *
17
+ * `--vaadin-overlay-transform-closed` has no initial value, which makes it resolve
18
+ * to `none` in the keyframes unless a theme sets it.
19
+ */
20
+ const animationProperties = [
21
+ { name: '--vaadin-overlay-animation-duration', syntax: '<time>', initialValue: '0s' },
22
+ { name: '--vaadin-overlay-animation-delay', syntax: '<time>', initialValue: '0s' },
23
+ { name: '--vaadin-overlay-animation-timing-function', syntax: '*', initialValue: 'ease' },
24
+ { name: '--vaadin-overlay-opacity-closed', syntax: '<number>', initialValue: '0' },
25
+ { name: '--vaadin-overlay-translate-closed', syntax: '<length>+ | <percentage>+', initialValue: '0px' },
26
+ { name: '--vaadin-overlay-scale-closed', syntax: '<number> | <percentage>', initialValue: '1' },
27
+ { name: '--vaadin-overlay-transform-closed', syntax: '*' },
28
+ ];
29
+
30
+ animationProperties.forEach((property) => {
31
+ registerCSSProperty({ inherits: false, ...property });
32
+ });
33
+
34
+ /**
35
+ * The overlay animation properties as a set of `inherit` declarations. The properties are
36
+ * registered as non-inheriting, so components with more than one shadow root boundary
37
+ * between the host and the overlay need to forward them explicitly.
38
+ */
39
+ export const overlayAnimationProperties = unsafeCSS(
40
+ animationProperties.map(({ name }) => `${name}: inherit;`).join('\n'),
41
+ );
42
+
43
+ export const overlayAnimationStyles = css`
44
+ :host,
45
+ [part='overlay'],
46
+ [part='backdrop'] {
47
+ ${overlayAnimationProperties}
48
+ }
49
+
50
+ :host(:where([opening], [closing])) {
51
+ /* This empty animation only reports the state, the parts run the visible animation */
52
+ animation-name: --no-op;
53
+ animation-duration: var(--vaadin-overlay-animation-duration);
54
+ animation-delay: var(--vaadin-overlay-animation-delay);
55
+ }
56
+
57
+ :host(:where([closing])) [part='overlay'],
58
+ :host(:where([closing])) ::slotted(*) {
59
+ pointer-events: none !important;
60
+ }
61
+
62
+ :host(:where([opening], [closing])) :is([part='overlay'], [part='backdrop']) {
63
+ animation-name: --fade, --transform;
64
+ animation-duration: var(--vaadin-overlay-animation-duration);
65
+ animation-timing-function: var(--vaadin-overlay-animation-timing-function);
66
+ animation-delay: var(--vaadin-overlay-animation-delay);
67
+ /* Fill backwards only, so the closed value applies during the delay without overriding theme styles */
68
+ animation-fill-mode: backwards;
69
+
70
+ @media (prefers-reduced-motion) {
71
+ animation-name: --fade;
72
+ }
73
+ }
74
+
75
+ :host(:where([opening], [closing])) [part='backdrop'] {
76
+ animation-name: --fade;
77
+ animation-timing-function: linear;
78
+ --vaadin-overlay-opacity-closed: 0;
79
+ }
80
+
81
+ :host(:where([closing])) :is([part='overlay'], [part='backdrop']) {
82
+ animation-direction: reverse;
83
+ }
84
+
85
+ @keyframes --no-op {
86
+ }
87
+
88
+ /* Only the closed state is declared, so the animations end at the value the part already has */
89
+ @keyframes --transform {
90
+ 0% {
91
+ transform: var(--vaadin-overlay-transform-closed);
92
+ translate: var(--vaadin-overlay-translate-closed);
93
+ scale: var(--vaadin-overlay-scale-closed);
94
+ }
95
+ }
96
+
97
+ @keyframes --fade {
98
+ 0% {
99
+ opacity: var(--vaadin-overlay-opacity-closed);
100
+ }
101
+ }
102
+ `;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { CSSResult } from 'lit';
7
+
8
+ export const overlayStyles: CSSResult[];
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import '@vaadin/component-base/src/styles/style-props.js';
7
7
  import { css } from 'lit';
8
+ import { overlayAnimationStyles } from './vaadin-overlay-animation-base-styles.js';
8
9
 
9
- export const overlayStyles = css`
10
+ const overlayBase = css`
10
11
  :host {
11
12
  z-index: 200;
12
13
  position: fixed;
@@ -103,3 +104,5 @@ export const overlayStyles = css`
103
104
  }
104
105
  }
105
106
  `;
107
+
108
+ export const overlayStyles = [overlayBase, overlayAnimationStyles];
@@ -11,12 +11,27 @@ export declare function OverlayFocusMixin<T extends Constructor<HTMLElement>>(
11
11
 
12
12
  export declare class OverlayFocusMixinClass {
13
13
  /**
14
- * When true, opening the overlay moves focus to the first focusable child,
15
- * or to the overlay part with tabindex if there are no focusable children.
14
+ * Set to true to move focus into the overlay automatically on open
15
+ * and keep it inside: Tab and Shift+Tab cycle through the overlay's
16
+ * content until the overlay is closed.
17
+ *
18
+ * Focus moves to the first tabbable element in the tab order. This
19
+ * can be the overlay itself if it has `tabindex` attribute set to `0`
20
+ * on the host element or the `overlay` shadow DOM part.
21
+ *
16
22
  * @attr {boolean} focus-trap
17
23
  */
18
24
  focusTrap: boolean;
19
25
 
26
+ /**
27
+ * Set to true to move focus into the overlay automatically on open.
28
+ *
29
+ * Focus moves to the first tabbable element in the tab order. This
30
+ * can be the overlay itself if it has `tabindex` attribute set to `0`
31
+ * on the host element or the `overlay` shadow DOM part.
32
+ */
33
+ autofocus: boolean;
34
+
20
35
  /**
21
36
  * Set to true to enable restoring of focus when overlay is closed.
22
37
  * @attr {boolean} restore-focus-on-close
@@ -40,9 +55,11 @@ export declare class OverlayFocusMixinClass {
40
55
  protected _saveFocus(): void;
41
56
 
42
57
  /**
43
- * Trap focus within the overlay after opening has completed.
58
+ * Sets up focus after the overlay opening has completed: moves focus into
59
+ * the overlay if `autofocus` is enabled, and traps focus within the overlay
60
+ * if `focusTrap` is enabled.
44
61
  */
45
- protected _trapFocus(): void;
62
+ protected _initFocus(): void;
46
63
 
47
64
  /**
48
65
  * Returns true if focus is still inside the overlay or on the body element,
@@ -5,15 +5,27 @@
5
5
  */
6
6
  import { FocusRestorationController } from '@vaadin/a11y-base/src/focus-restoration-controller.js';
7
7
  import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
8
- import { getDeepActiveElement, isElementHidden, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
8
+ import {
9
+ getDeepActiveElement,
10
+ getTabbableElements,
11
+ isElementFocused,
12
+ isElementHidden,
13
+ isKeyboardActive,
14
+ } from '@vaadin/a11y-base/src/focus-utils.js';
9
15
 
10
16
  export const OverlayFocusMixin = (superClass) =>
11
17
  class OverlayFocusMixin extends superClass {
12
18
  static get properties() {
13
19
  return {
14
20
  /**
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.
21
+ * Set to true to move focus into the overlay automatically on open
22
+ * and keep it inside: Tab and Shift+Tab cycle through the overlay's
23
+ * content until the overlay is closed.
24
+ *
25
+ * Focus moves to the first tabbable element in the tab order. This
26
+ * can be the overlay itself if it has `tabindex` attribute set to `0`
27
+ * on the host element or the `overlay` shadow DOM part.
28
+ *
17
29
  * @attr {boolean} focus-trap
18
30
  */
19
31
  focusTrap: {
@@ -21,6 +33,18 @@ export const OverlayFocusMixin = (superClass) =>
21
33
  value: false,
22
34
  },
23
35
 
36
+ /**
37
+ * Set to true to move focus into the overlay automatically on open.
38
+ *
39
+ * Focus moves to the first tabbable element in the tab order. This
40
+ * can be the overlay itself if it has `tabindex` attribute set to `0`
41
+ * on the host element or the `overlay` shadow DOM part.
42
+ */
43
+ autofocus: {
44
+ type: Boolean,
45
+ value: false,
46
+ },
47
+
24
48
  /**
25
49
  * Set to true to enable restoring of focus when overlay is closed.
26
50
  * @attr {boolean} restore-focus-on-close
@@ -66,11 +90,11 @@ export const OverlayFocusMixin = (superClass) =>
66
90
  }
67
91
 
68
92
  /**
69
- * Override to specify another element used as a focus trap root,
93
+ * Override to specify another element used as a focus root,
70
94
  * e.g. the overlay's owner element, rather than overlay part.
71
95
  * @protected
72
96
  */
73
- get _focusTrapRoot() {
97
+ get _focusRoot() {
74
98
  return this.$.overlay;
75
99
  }
76
100
 
@@ -103,13 +127,26 @@ export const OverlayFocusMixin = (superClass) =>
103
127
  }
104
128
 
105
129
  /**
106
- * Trap focus within the overlay after opening has completed.
130
+ * Sets up focus after the overlay opening has completed: moves focus into
131
+ * the overlay if `autofocus` is enabled, and traps focus within the overlay
132
+ * if `focusTrap` is enabled.
107
133
  *
108
134
  * @protected
109
135
  */
110
- _trapFocus() {
111
- if (this.focusTrap && !isElementHidden(this._focusTrapRoot)) {
112
- this.__focusTrapController.trapFocus(this._focusTrapRoot);
136
+ _initFocus() {
137
+ if (isElementHidden(this._focusRoot)) {
138
+ return;
139
+ }
140
+
141
+ if (this.autofocus) {
142
+ const tabbables = getTabbableElements(this._focusRoot);
143
+ if (!tabbables.some(isElementFocused)) {
144
+ tabbables[0]?.focus({ focusVisible: isKeyboardActive() });
145
+ }
146
+ }
147
+
148
+ if (this.focusTrap) {
149
+ this.__focusTrapController.trapFocus(this._focusRoot);
113
150
  }
114
151
  }
115
152
 
@@ -6,7 +6,7 @@
6
6
  import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
7
7
  import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
8
8
  import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
9
- import { setOverlayStateAttribute } from './vaadin-overlay-utils.js';
9
+ import { getStateAnimations, setOverlayStateAttribute } from './vaadin-overlay-utils.js';
10
10
 
11
11
  export const OverlayMixin = (superClass) =>
12
12
  class OverlayMixin extends OverlayFocusMixin(OverlayStackMixin(superClass)) {
@@ -132,19 +132,6 @@ export const OverlayMixin = (superClass) =>
132
132
  if (this.$.backdrop) {
133
133
  this.$.backdrop.addEventListener('click', () => {});
134
134
  }
135
-
136
- this.addEventListener('mouseup', () => {
137
- // In Chrome, focus moves to body on overlay content mousedown
138
- // See https://github.com/vaadin/flow-components/issues/5507
139
- if (document.activeElement === document.body && this.$.overlay.getAttribute('tabindex') === '0') {
140
- this.$.overlay.focus();
141
- }
142
- });
143
-
144
- this.addEventListener('animationcancel', () => {
145
- this._flushAnimation('opening');
146
- this._flushAnimation('closing');
147
- });
148
135
  }
149
136
 
150
137
  /** @protected */
@@ -359,7 +346,7 @@ export const OverlayMixin = (superClass) =>
359
346
 
360
347
  this.__scheduledOpen = requestAnimationFrame(() => {
361
348
  setTimeout(() => {
362
- this._trapFocus();
349
+ this._initFocus();
363
350
 
364
351
  // Dispatch the event on the overlay. Not using composed, as propagating the event through shadow roots
365
352
  // could have side effects when nesting overlays
@@ -401,33 +388,35 @@ export const OverlayMixin = (superClass) =>
401
388
  }
402
389
 
403
390
  /**
404
- * @return {boolean}
405
- * @private
406
- */
407
- _shouldAnimate() {
408
- const style = getComputedStyle(this);
409
- const name = style.getPropertyValue('animation-name');
410
- const hidden = style.getPropertyValue('display') === 'none';
411
- return !hidden && name && name !== 'none';
412
- }
413
-
414
- /**
391
+ * Run the callback once every animation reporting the state has ended, or right away when
392
+ * there is none.
393
+ *
415
394
  * @param {string} type
416
395
  * @param {Function} callback
417
396
  * @private
418
397
  */
419
398
  _enqueueAnimation(type, callback) {
399
+ const animations = getStateAnimations(this);
400
+ if (animations.length === 0) {
401
+ callback();
402
+ return;
403
+ }
404
+
420
405
  const handler = `__${type}Handler`;
421
- const listener = (event) => {
422
- if (event && event.target !== this) {
406
+ const finish = () => {
407
+ // The phase may already have been finished by _flushAnimation(), or superseded by a
408
+ // later one of the same type, in which case this callback no longer applies
409
+ if (this[handler] !== finish) {
423
410
  return;
424
411
  }
425
- callback();
426
- this.removeEventListener('animationend', listener);
412
+ // Cleared before the callback, so that a listener reopening the overlay synchronously
413
+ // installs its own handler rather than having it deleted afterwards
427
414
  delete this[handler];
415
+ callback();
428
416
  };
429
- this[handler] = listener;
430
- this.addEventListener('animationend', listener);
417
+ this[handler] = finish;
418
+ // A cancelled animation rejects, which ends the phase just as finishing does
419
+ Promise.all(animations.map((animation) => animation.finished)).then(finish, finish);
431
420
  }
432
421
 
433
422
  /**
@@ -448,19 +437,14 @@ export const OverlayMixin = (superClass) =>
448
437
  }
449
438
  this._attachOverlay();
450
439
  this._appendAttachedInstance();
451
- this.bringToFront();
452
440
  if (!this.modeless) {
453
441
  this._enterModalState();
454
442
  }
455
443
  setOverlayStateAttribute(this, 'opening', true);
456
444
 
457
- if (this._shouldAnimate()) {
458
- this._enqueueAnimation('opening', () => {
459
- this._finishOpening();
460
- });
461
- } else {
445
+ this._enqueueAnimation('opening', () => {
462
446
  this._finishOpening();
463
- }
447
+ });
464
448
  }
465
449
 
466
450
  /** @private */
@@ -494,13 +478,9 @@ export const OverlayMixin = (superClass) =>
494
478
  setOverlayStateAttribute(this, 'closing', true);
495
479
  this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
496
480
 
497
- if (this._shouldAnimate()) {
498
- this._enqueueAnimation('closing', () => {
499
- this._finishClosing();
500
- });
501
- } else {
481
+ this._enqueueAnimation('closing', () => {
502
482
  this._finishClosing();
503
- }
483
+ });
504
484
  }
505
485
  }
506
486
 
@@ -557,6 +537,11 @@ export const OverlayMixin = (superClass) =>
557
537
 
558
538
  if (this.opened && !evt.defaultPrevented) {
559
539
  this.close(event);
540
+
541
+ // Only for modal overlays which make underlying content non-interactive.
542
+ if (!this.opened && !this.modeless) {
543
+ event.preventDefault();
544
+ }
560
545
  }
561
546
  }
562
547
 
@@ -570,7 +555,7 @@ export const OverlayMixin = (superClass) =>
570
555
  }
571
556
 
572
557
  // Only close modeless overlay on Esc press when it contains focus
573
- if (!this._shouldAddGlobalListeners() && !event.composedPath().includes(this._focusTrapRoot)) {
558
+ if (!this._shouldAddGlobalListeners() && !event.composedPath().includes(this._focusRoot)) {
574
559
  return;
575
560
  }
576
561
 
@@ -12,6 +12,20 @@
12
12
  */
13
13
  export function observeMove(element: HTMLElement, callback: () => void): () => void;
14
14
 
15
+ /**
16
+ * Collect the animations that report the state of the given element, so that their end can be
17
+ * awaited before the element is hidden or removed. The animations are read instead of the
18
+ * computed style, so that the decision to wait cannot disagree with what the browser actually
19
+ * created: a keyframes rule that does not exist and an element that is not rendered both give
20
+ * an empty list.
21
+ *
22
+ * Only CSS animations count, as the transitions and the script animations that `getAnimations()`
23
+ * also returns do not report the state. Animations of any content are already left out, as
24
+ * `getAnimations()` only descends into the subtree when asked to. Animations that take no time
25
+ * are dropped as well, so that a delay on its own does not hold the state.
26
+ */
27
+ export function getStateAnimations(element: HTMLElement): Animation[];
28
+
15
29
  /**
16
30
  * Toggle the state attribute on the overlay element and also its owner element. This allows targeting state attributes
17
31
  * in the light DOM in case the overlay is in the shadow DOM of its owner.
@@ -85,6 +85,29 @@ export function observeMove(element, callback) {
85
85
  return cleanup;
86
86
  }
87
87
 
88
+ /**
89
+ * Collect the animations that report the state of the given element, so that their end can be
90
+ * awaited before the element is hidden or removed. The animations are read instead of the
91
+ * computed style, so that the decision to wait cannot disagree with what the browser actually
92
+ * created: a keyframes rule that does not exist and an element that is not rendered both give
93
+ * an empty list.
94
+ *
95
+ * Only CSS animations count, as the transitions and the script animations that `getAnimations()`
96
+ * also returns do not report the state. Animations of any content are already left out, as
97
+ * `getAnimations()` only descends into the subtree when asked to. Animations that take no time
98
+ * are dropped as well, so that a delay on its own does not hold the state.
99
+ *
100
+ * @param {HTMLElement} element
101
+ * @return {!Array<!Animation>}
102
+ */
103
+ export function getStateAnimations(element) {
104
+ return element
105
+ .getAnimations()
106
+ .filter(
107
+ (animation) => animation instanceof CSSAnimation && animation.effect.getComputedTiming().activeDuration > 0,
108
+ );
109
+ }
110
+
88
111
  /**
89
112
  * Toggle the state attribute on the overlay element and also its owner element. This allows targeting state attributes
90
113
  * in the light DOM in case the overlay is in the shadow DOM of its owner.