@vaadin/overlay 25.2.13 → 25.2.15

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.
@@ -33,7 +33,7 @@
33
33
  "type": {
34
34
  "text": "boolean"
35
35
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
37
37
  "attribute": "focus-trap"
38
38
  },
39
39
  {
@@ -63,7 +63,7 @@
63
63
  "type": {
64
64
  "text": "boolean"
65
65
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
67
67
  "fieldName": "focusTrap"
68
68
  },
69
69
  {
@@ -138,7 +138,7 @@
138
138
  "type": {
139
139
  "text": "boolean"
140
140
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
142
142
  "attribute": "focus-trap",
143
143
  "inheritedFrom": {
144
144
  "name": "OverlayFocusMixin",
@@ -289,7 +289,7 @@
289
289
  "type": {
290
290
  "text": "boolean"
291
291
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
293
293
  "fieldName": "focusTrap",
294
294
  "inheritedFrom": {
295
295
  "name": "OverlayFocusMixin",
@@ -682,7 +682,7 @@
682
682
  "type": {
683
683
  "text": "boolean"
684
684
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
686
686
  "attribute": "focus-trap",
687
687
  "inheritedFrom": {
688
688
  "name": "OverlayFocusMixin",
@@ -942,7 +942,7 @@
942
942
  "type": {
943
943
  "text": "boolean"
944
944
  },
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.",
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.\n\nAn element inside the overlay that has `autofocus` set, such as\n`<vaadin-text-field autofocus>`, receives focus on open instead.",
946
946
  "fieldName": "focusTrap",
947
947
  "inheritedFrom": {
948
948
  "name": "OverlayFocusMixin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/overlay",
3
- "version": "25.2.13",
3
+ "version": "25.2.15",
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.13",
38
- "@vaadin/component-base": "~25.2.13",
39
- "@vaadin/vaadin-themable-mixin": "~25.2.13",
37
+ "@vaadin/a11y-base": "~25.2.15",
38
+ "@vaadin/component-base": "~25.2.15",
39
+ "@vaadin/vaadin-themable-mixin": "~25.2.15",
40
40
  "lit": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@vaadin/aura": "~25.2.13",
44
- "@vaadin/chai-plugins": "~25.2.13",
45
- "@vaadin/test-runner-commands": "~25.2.13",
43
+ "@vaadin/aura": "~25.2.15",
44
+ "@vaadin/chai-plugins": "~25.2.15",
45
+ "@vaadin/test-runner-commands": "~25.2.15",
46
46
  "@vaadin/testing-helpers": "^2.0.0",
47
- "@vaadin/vaadin-lumo-styles": "~25.2.13",
47
+ "@vaadin/vaadin-lumo-styles": "~25.2.15",
48
48
  "sinon": "^22.0.0"
49
49
  },
50
50
  "customElements": "custom-elements.json",
51
- "gitHead": "601112f027fb53668eed85f9593fa6fe5222fa00"
51
+ "gitHead": "e0f3c1f28d7393af441fe3c9a790dba3e6ce3912"
52
52
  }
@@ -13,6 +13,9 @@ export declare class OverlayFocusMixinClass {
13
13
  /**
14
14
  * When true, opening the overlay moves focus to the first focusable child,
15
15
  * or to the overlay part with tabindex if there are no focusable children.
16
+ *
17
+ * An element inside the overlay that has `autofocus` set, such as
18
+ * `<vaadin-text-field autofocus>`, receives focus on open instead.
16
19
  * @attr {boolean} focus-trap
17
20
  */
18
21
  focusTrap: boolean;
@@ -5,7 +5,13 @@
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
+ getFocusableElements,
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 {
@@ -14,6 +20,9 @@ export const OverlayFocusMixin = (superClass) =>
14
20
  /**
15
21
  * When true, opening the overlay moves focus to the first focusable child,
16
22
  * or to the overlay part with tabindex if there are no focusable children.
23
+ *
24
+ * An element inside the overlay that has `autofocus` set, such as
25
+ * `<vaadin-text-field autofocus>`, receives focus on open instead.
17
26
  * @attr {boolean} focus-trap
18
27
  */
19
28
  focusTrap: {
@@ -41,11 +50,25 @@ export const OverlayFocusMixin = (superClass) =>
41
50
  };
42
51
  }
43
52
 
53
+ /**
54
+ * Whether the overlay moves, traps and restores focus. Override to return
55
+ * false in overlays that never do: focus properties are then ignored and
56
+ * the focus controllers are not created.
57
+ * @protected
58
+ */
59
+ static get manageFocus() {
60
+ return true;
61
+ }
62
+
44
63
  constructor() {
45
64
  super();
46
65
 
47
- this.__focusTrapController = new FocusTrapController(this);
48
- this.__focusRestorationController = new FocusRestorationController();
66
+ this.__manageFocus = this.constructor.manageFocus;
67
+
68
+ if (this.__manageFocus) {
69
+ this.__focusTrapController = new FocusTrapController(this);
70
+ this.__focusRestorationController = new FocusRestorationController();
71
+ }
49
72
  }
50
73
 
51
74
  /**
@@ -61,8 +84,10 @@ export const OverlayFocusMixin = (superClass) =>
61
84
  ready() {
62
85
  super.ready();
63
86
 
64
- this.addController(this.__focusTrapController);
65
- this.addController(this.__focusRestorationController);
87
+ if (this.__manageFocus) {
88
+ this.addController(this.__focusTrapController);
89
+ this.addController(this.__focusRestorationController);
90
+ }
66
91
  }
67
92
 
68
93
  /**
@@ -80,6 +105,10 @@ export const OverlayFocusMixin = (superClass) =>
80
105
  * @protected
81
106
  */
82
107
  _resetFocus() {
108
+ if (!this.__manageFocus) {
109
+ return;
110
+ }
111
+
83
112
  if (this.focusTrap) {
84
113
  this.__focusTrapController.releaseFocus();
85
114
  }
@@ -97,18 +126,34 @@ export const OverlayFocusMixin = (superClass) =>
97
126
  * @protected
98
127
  */
99
128
  _saveFocus() {
129
+ if (!this.__manageFocus) {
130
+ return;
131
+ }
132
+
100
133
  if (this.restoreFocusOnClose) {
101
134
  this.__focusRestorationController.saveFocus(this.restoreFocusNode);
102
135
  }
103
136
  }
104
137
 
105
138
  /**
106
- * Trap focus within the overlay after opening has completed.
139
+ * Trap focus within the overlay after opening has completed. Focus moves
140
+ * to the first element with `autofocus` inside the overlay, otherwise to
141
+ * the first focusable element.
107
142
  *
108
143
  * @protected
109
144
  */
110
145
  _trapFocus() {
146
+ if (!this.__manageFocus) {
147
+ return;
148
+ }
149
+
111
150
  if (this.focusTrap && !isElementHidden(this._focusTrapRoot)) {
151
+ const focusables = getFocusableElements(this._focusTrapRoot);
152
+ if (!focusables.some(isElementFocused)) {
153
+ const target = focusables.find((el) => this.#hasAutofocus(el));
154
+ target?.focus({ focusVisible: isKeyboardActive() });
155
+ }
156
+
112
157
  this.__focusTrapController.trapFocus(this._focusTrapRoot);
113
158
  }
114
159
  }
@@ -149,4 +194,27 @@ export const OverlayFocusMixin = (superClass) =>
149
194
  }
150
195
  return n === this._contentRoot;
151
196
  }
197
+
198
+ /**
199
+ * Returns true if the element has `autofocus`, or belongs to a custom
200
+ * element that has it (e.g. the input of `<vaadin-text-field autofocus>`).
201
+ * As with native `autofocus`, a plain `<div autofocus>` does not apply to
202
+ * its children. Stops at the focus trap root, whose `autofocus` refers to the overlay.
203
+ * Walks the flat tree, the same way focusable elements are collected from the
204
+ * focus trap root, so it never leaves the overlay when content is slotted from the owner.
205
+ *
206
+ * @param {HTMLElement} element
207
+ * @return {boolean}
208
+ */
209
+ #hasAutofocus(element) {
210
+ const focusTrapRoot = this._focusTrapRoot;
211
+ let node = element;
212
+ while (node && node !== focusTrapRoot && node !== this) {
213
+ if (node.autofocus && (node === element || customElements.get(node.localName))) {
214
+ return true;
215
+ }
216
+ node = node.assignedSlot || node.parentNode || node.host;
217
+ }
218
+ return false;
219
+ }
152
220
  };