@vaadin/overlay 25.3.0-alpha8 → 25.3.0-beta1

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",
@@ -936,13 +1020,26 @@
936
1020
  },
937
1021
  "tagName": "vaadin-overlay",
938
1022
  "customElement": true,
1023
+ "deprecated": "`<vaadin-overlay>` is deprecated and will be removed in Vaadin 26. Consider using `OverlayMixin` and `PositionMixin` instead.",
939
1024
  "attributes": [
1025
+ {
1026
+ "name": "autofocus",
1027
+ "type": {
1028
+ "text": "boolean"
1029
+ },
1030
+ "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.",
1031
+ "fieldName": "autofocus",
1032
+ "inheritedFrom": {
1033
+ "name": "OverlayFocusMixin",
1034
+ "module": "src/vaadin-overlay-focus-mixin.js"
1035
+ }
1036
+ },
940
1037
  {
941
1038
  "name": "focus-trap",
942
1039
  "type": {
943
1040
  "text": "boolean"
944
1041
  },
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.",
1042
+ "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
1043
  "fieldName": "focusTrap",
947
1044
  "inheritedFrom": {
948
1045
  "name": "OverlayFocusMixin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/overlay",
3
- "version": "25.3.0-alpha8",
3
+ "version": "25.3.0-beta1",
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.3.0-alpha8",
38
- "@vaadin/component-base": "25.3.0-alpha8",
39
- "@vaadin/vaadin-themable-mixin": "25.3.0-alpha8",
37
+ "@vaadin/a11y-base": "25.3.0-beta1",
38
+ "@vaadin/component-base": "25.3.0-beta1",
39
+ "@vaadin/vaadin-themable-mixin": "25.3.0-beta1",
40
40
  "lit": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@vaadin/aura": "25.3.0-alpha8",
44
- "@vaadin/chai-plugins": "25.3.0-alpha8",
45
- "@vaadin/test-runner-commands": "25.3.0-alpha8",
43
+ "@vaadin/aura": "25.3.0-beta1",
44
+ "@vaadin/chai-plugins": "25.3.0-beta1",
45
+ "@vaadin/test-runner-commands": "25.3.0-beta1",
46
46
  "@vaadin/testing-helpers": "^2.0.0",
47
- "@vaadin/vaadin-lumo-styles": "25.3.0-alpha8",
47
+ "@vaadin/vaadin-lumo-styles": "25.3.0-beta1",
48
48
  "sinon": "^22.0.0"
49
49
  },
50
50
  "customElements": "custom-elements.json",
51
- "gitHead": "ccbb4aaffb63c745c6da0426b532d4d05e47af29"
51
+ "gitHead": "295432e44a6967e1aff36462b2e3e1d0e64eb8cc"
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;
@@ -55,6 +56,11 @@ export const overlayStyles = css`
55
56
  display: none !important;
56
57
  }
57
58
 
59
+ :host([suppressed]) [part='overlay'],
60
+ :host([suppressed]) ::slotted(*) {
61
+ pointer-events: none !important;
62
+ }
63
+
58
64
  [part='overlay'] {
59
65
  color: var(--vaadin-overlay-text-color, var(--vaadin-text-color));
60
66
  background: var(--vaadin-overlay-background, var(--vaadin-background-color));
@@ -103,3 +109,5 @@ export const overlayStyles = css`
103
109
  }
104
110
  }
105
111
  `;
112
+
113
+ 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,8 +55,9 @@ export declare class OverlayFocusMixinClass {
40
55
  protected _saveFocus(): void;
41
56
 
42
57
  /**
43
- * Sets up focus after the overlay opening has completed: traps focus
44
- * within the overlay if `focusTrap` is enabled.
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.
45
61
  */
46
62
  protected _initFocus(): void;
47
63
 
@@ -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
@@ -103,13 +127,25 @@ export const OverlayFocusMixin = (superClass) =>
103
127
  }
104
128
 
105
129
  /**
106
- * Sets up focus after the overlay opening has completed: traps focus
107
- * within the overlay if `focusTrap` is enabled.
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.
108
133
  *
109
134
  * @protected
110
135
  */
111
136
  _initFocus() {
112
- if (this.focusTrap && !isElementHidden(this._focusRoot)) {
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) {
113
149
  this.__focusTrapController.trapFocus(this._focusRoot);
114
150
  }
115
151
  }
@@ -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 */
@@ -401,37 +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 hasDuration = style
411
- .getPropertyValue('animation-duration')
412
- .split(',')
413
- .some((duration) => parseFloat(duration) > 0);
414
- const hidden = style.getPropertyValue('display') === 'none';
415
- return !hidden && name && name !== 'none' && hasDuration;
416
- }
417
-
418
- /**
391
+ * Run the callback once every animation reporting the state has ended, or right away when
392
+ * there is none.
393
+ *
419
394
  * @param {string} type
420
395
  * @param {Function} callback
421
396
  * @private
422
397
  */
423
398
  _enqueueAnimation(type, callback) {
399
+ const animations = getStateAnimations(this);
400
+ if (animations.length === 0) {
401
+ callback();
402
+ return;
403
+ }
404
+
424
405
  const handler = `__${type}Handler`;
425
- const listener = (event) => {
426
- 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) {
427
410
  return;
428
411
  }
429
- callback();
430
- 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
431
414
  delete this[handler];
415
+ callback();
432
416
  };
433
- this[handler] = listener;
434
- 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);
435
420
  }
436
421
 
437
422
  /**
@@ -452,19 +437,14 @@ export const OverlayMixin = (superClass) =>
452
437
  }
453
438
  this._attachOverlay();
454
439
  this._appendAttachedInstance();
455
- this.bringToFront();
456
440
  if (!this.modeless) {
457
441
  this._enterModalState();
458
442
  }
459
443
  setOverlayStateAttribute(this, 'opening', true);
460
444
 
461
- if (this._shouldAnimate()) {
462
- this._enqueueAnimation('opening', () => {
463
- this._finishOpening();
464
- });
465
- } else {
445
+ this._enqueueAnimation('opening', () => {
466
446
  this._finishOpening();
467
- }
447
+ });
468
448
  }
469
449
 
470
450
  /** @private */
@@ -483,7 +463,7 @@ export const OverlayMixin = (superClass) =>
483
463
  _finishClosing() {
484
464
  this._detachOverlay();
485
465
  this._removeAttachedInstance();
486
- this.$.overlay.style.removeProperty('pointer-events');
466
+ this.toggleAttribute('suppressed', false);
487
467
  setOverlayStateAttribute(this, 'closing', false);
488
468
  this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
489
469
  }
@@ -498,13 +478,9 @@ export const OverlayMixin = (superClass) =>
498
478
  setOverlayStateAttribute(this, 'closing', true);
499
479
  this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
500
480
 
501
- if (this._shouldAnimate()) {
502
- this._enqueueAnimation('closing', () => {
503
- this._finishClosing();
504
- });
505
- } else {
481
+ this._enqueueAnimation('closing', () => {
506
482
  this._finishClosing();
507
- }
483
+ });
508
484
  }
509
485
  }
510
486
 
@@ -561,6 +537,11 @@ export const OverlayMixin = (superClass) =>
561
537
 
562
538
  if (this.opened && !evt.defaultPrevented) {
563
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
+ }
564
545
  }
565
546
  }
566
547
 
@@ -106,10 +106,10 @@ export const OverlayStackMixin = (superClass) =>
106
106
  document.body.style.pointerEvents = 'none';
107
107
  }
108
108
 
109
- // Disable pointer events in other attached overlays
109
+ // Suppress pointer events in other attached overlays
110
110
  getAttachedInstances().forEach((el) => {
111
111
  if (el !== this) {
112
- el.$.overlay.style.pointerEvents = 'none';
112
+ el.toggleAttribute('suppressed', true);
113
113
  }
114
114
  });
115
115
  }
@@ -122,7 +122,7 @@ export const OverlayStackMixin = (superClass) =>
122
122
  delete this._previousDocumentPointerEvents;
123
123
  }
124
124
 
125
- // Restore pointer events in the previous overlay(s)
125
+ // Stop suppressing pointer events in the previous overlay(s)
126
126
  const instances = getAttachedInstances();
127
127
 
128
128
  let el;
@@ -132,7 +132,7 @@ export const OverlayStackMixin = (superClass) =>
132
132
  // Skip the current instance
133
133
  continue;
134
134
  }
135
- el.$.overlay.style.removeProperty('pointer-events');
135
+ el.toggleAttribute('suppressed', false);
136
136
  if (!el.modeless) {
137
137
  // Stop after the last modal
138
138
  break;
@@ -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.
@@ -118,6 +118,8 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayCustomEventMap;
118
118
  * @fires {CustomEvent} vaadin-overlay-closed - Fired after the overlay is closed.
119
119
  * @fires {CustomEvent} vaadin-overlay-outside-click - Fired before the overlay is closed on outside click. Calling `preventDefault()` on the event cancels the closing.
120
120
  * @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
+ * @deprecated `<vaadin-overlay>` is deprecated and will be removed in Vaadin 26.
122
+ * Consider using `OverlayMixin` and `PositionMixin` instead.
121
123
  */
122
124
  declare class Overlay extends OverlayMixin(ThemableMixin(DirMixin(HTMLElement))) {
123
125
  addEventListener<K extends keyof OverlayEventMap>(
@@ -7,6 +7,7 @@ import { html, LitElement } from 'lit';
7
7
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
8
8
  import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
9
9
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10
+ import { issueWarning } from '@vaadin/component-base/src/warnings.js';
10
11
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
11
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
13
  import { overlayStyles } from './styles/vaadin-overlay-base-styles.js';
@@ -73,6 +74,8 @@ import { OverlayMixin } from './vaadin-overlay-mixin.js';
73
74
  *
74
75
  * @customElement vaadin-overlay
75
76
  * @extends HTMLElement
77
+ * @deprecated `<vaadin-overlay>` is deprecated and will be removed in Vaadin 26.
78
+ * Consider using `OverlayMixin` and `PositionMixin` instead.
76
79
  */
77
80
  class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
78
81
  static get is() {
@@ -94,6 +97,15 @@ class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjec
94
97
  </div>
95
98
  `;
96
99
  }
100
+
101
+ /** @protected */
102
+ firstUpdated() {
103
+ super.firstUpdated();
104
+
105
+ issueWarning(
106
+ '`<vaadin-overlay>` is deprecated and will be removed in Vaadin 26. Consider using `OverlayMixin` and `PositionMixin` instead.',
107
+ );
108
+ }
97
109
  }
98
110
 
99
111
  defineCustomElement(Overlay);