@vaadin/app-layout 24.7.0-alpha1 → 24.7.0-alpha3
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 +11 -10
- package/src/vaadin-app-layout-mixin.d.ts +69 -0
- package/src/vaadin-app-layout-mixin.js +529 -0
- package/src/vaadin-app-layout-styles.d.ts +8 -0
- package/src/vaadin-app-layout-styles.js +166 -0
- package/src/vaadin-app-layout.d.ts +3 -57
- package/src/vaadin-app-layout.js +6 -674
- package/src/vaadin-lit-app-layout.d.ts +1 -0
- package/src/vaadin-lit-app-layout.js +59 -0
- package/src/vaadin-lit-drawer-toggle.d.ts +1 -0
- package/src/vaadin-lit-drawer-toggle.js +87 -0
- package/theme/lumo/vaadin-lit-app-layout.d.ts +2 -0
- package/theme/lumo/vaadin-lit-app-layout.js +2 -0
- package/theme/lumo/vaadin-lit-drawer-toggle.d.ts +2 -0
- package/theme/lumo/vaadin-lit-drawer-toggle.js +2 -0
- package/theme/material/vaadin-lit-app-layout.d.ts +2 -0
- package/theme/material/vaadin-lit-app-layout.js +2 -0
- package/theme/material/vaadin-lit-drawer-toggle.d.ts +2 -0
- package/theme/material/vaadin-lit-drawer-toggle.js +2 -0
- package/vaadin-lit-app-layout.d.ts +1 -0
- package/vaadin-lit-app-layout.js +2 -0
- package/vaadin-lit-drawer-toggle.d.ts +1 -0
- package/vaadin-lit-drawer-toggle.js +2 -0
- package/web-types.json +1 -1
- package/web-types.lit.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/app-layout",
|
|
3
|
-
"version": "24.7.0-
|
|
3
|
+
"version": "24.7.0-alpha3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -35,23 +35,24 @@
|
|
|
35
35
|
"polymer"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
38
39
|
"@polymer/polymer": "^3.0.0",
|
|
39
|
-
"@vaadin/a11y-base": "24.7.0-
|
|
40
|
-
"@vaadin/button": "24.7.0-
|
|
41
|
-
"@vaadin/component-base": "24.7.0-
|
|
42
|
-
"@vaadin/vaadin-lumo-styles": "24.7.0-
|
|
43
|
-
"@vaadin/vaadin-material-styles": "24.7.0-
|
|
44
|
-
"@vaadin/vaadin-themable-mixin": "24.7.0-
|
|
40
|
+
"@vaadin/a11y-base": "24.7.0-alpha3",
|
|
41
|
+
"@vaadin/button": "24.7.0-alpha3",
|
|
42
|
+
"@vaadin/component-base": "24.7.0-alpha3",
|
|
43
|
+
"@vaadin/vaadin-lumo-styles": "24.7.0-alpha3",
|
|
44
|
+
"@vaadin/vaadin-material-styles": "24.7.0-alpha3",
|
|
45
|
+
"@vaadin/vaadin-themable-mixin": "24.7.0-alpha3",
|
|
45
46
|
"lit": "^3.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@vaadin/chai-plugins": "24.7.0-
|
|
49
|
-
"@vaadin/testing-helpers": "^1.
|
|
49
|
+
"@vaadin/chai-plugins": "24.7.0-alpha3",
|
|
50
|
+
"@vaadin/testing-helpers": "^1.1.0",
|
|
50
51
|
"sinon": "^18.0.0"
|
|
51
52
|
},
|
|
52
53
|
"web-types": [
|
|
53
54
|
"web-types.json",
|
|
54
55
|
"web-types.lit.json"
|
|
55
56
|
],
|
|
56
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "dd5cfad6c9b54e676f5b10dffba2233775378f40"
|
|
57
58
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2024 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
|
+
|
|
8
|
+
export interface AppLayoutI18n {
|
|
9
|
+
drawer: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare function AppLayoutMixin<T extends Constructor<HTMLElement>>(
|
|
13
|
+
base: T,
|
|
14
|
+
): Constructor<AppLayoutMixinClass> & T;
|
|
15
|
+
|
|
16
|
+
export declare class AppLayoutMixinClass {
|
|
17
|
+
/**
|
|
18
|
+
* The object used to localize this component.
|
|
19
|
+
* To change the default localization, replace the entire
|
|
20
|
+
* `i18n` object with a custom one.
|
|
21
|
+
*
|
|
22
|
+
* To update individual properties, extend the existing i18n object as follows:
|
|
23
|
+
* ```js
|
|
24
|
+
* appLayout.i18n = {
|
|
25
|
+
* ...appLayout.i18n,
|
|
26
|
+
* drawer: 'Drawer'
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* The object has the following structure and default values:
|
|
31
|
+
* ```
|
|
32
|
+
* {
|
|
33
|
+
* drawer: 'Drawer'
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
i18n: AppLayoutI18n;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Defines whether navbar or drawer will come first visually.
|
|
41
|
+
* - By default (`primary-section="navbar"`), the navbar takes the full available width and moves the drawer down.
|
|
42
|
+
* - If `primary-section="drawer"` is set, then the drawer will move the navbar, taking the full available height.
|
|
43
|
+
* @attr {navbar|drawer} primary-section
|
|
44
|
+
*/
|
|
45
|
+
primarySection: 'drawer' | 'navbar';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Controls whether the drawer is opened (visible) or not.
|
|
49
|
+
* Its default value depends on the viewport:
|
|
50
|
+
* - `true`, for desktop size views
|
|
51
|
+
* - `false`, for mobile size views
|
|
52
|
+
* @attr {boolean} drawer-opened
|
|
53
|
+
*/
|
|
54
|
+
drawerOpened: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Drawer is an overlay on top of the content
|
|
58
|
+
* Controlled via CSS using `--vaadin-app-layout-drawer-overlay: true|false`;
|
|
59
|
+
*/
|
|
60
|
+
readonly overlay: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A global event that causes the drawer to close (be hidden) when it is in overlay mode.
|
|
64
|
+
* - The default is `vaadin-router-location-changed` dispatched by Vaadin Router
|
|
65
|
+
*
|
|
66
|
+
* @attr {string} close-drawer-on
|
|
67
|
+
*/
|
|
68
|
+
closeDrawerOn: string;
|
|
69
|
+
}
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2024 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { afterNextRender, beforeNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
7
|
+
import { AriaModalController } from '@vaadin/a11y-base/src/aria-modal-controller.js';
|
|
8
|
+
import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('./vaadin-app-layout.js').AppLayoutI18n} AppLayoutI18n
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @polymerMixin
|
|
16
|
+
*/
|
|
17
|
+
export const AppLayoutMixin = (superclass) =>
|
|
18
|
+
class AppLayoutMixinClass extends superclass {
|
|
19
|
+
static get properties() {
|
|
20
|
+
return {
|
|
21
|
+
/**
|
|
22
|
+
* The object used to localize this component.
|
|
23
|
+
* To change the default localization, replace the entire
|
|
24
|
+
* `i18n` object with a custom one.
|
|
25
|
+
*
|
|
26
|
+
* To update individual properties, extend the existing i18n object as follows:
|
|
27
|
+
* ```js
|
|
28
|
+
* appLayout.i18n = {
|
|
29
|
+
* ...appLayout.i18n,
|
|
30
|
+
* drawer: 'Drawer'
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* The object has the following structure and default values:
|
|
35
|
+
* ```
|
|
36
|
+
* {
|
|
37
|
+
* drawer: 'Drawer'
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @type {AppLayoutI18n}
|
|
42
|
+
* @default {English/US}
|
|
43
|
+
*/
|
|
44
|
+
i18n: {
|
|
45
|
+
type: Object,
|
|
46
|
+
observer: '__i18nChanged',
|
|
47
|
+
sync: true,
|
|
48
|
+
value: () => {
|
|
49
|
+
return {
|
|
50
|
+
drawer: 'Drawer',
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Defines whether navbar or drawer will come first visually.
|
|
57
|
+
* - By default (`primary-section="navbar"`), the navbar takes the full available width and moves the drawer down.
|
|
58
|
+
* - If `primary-section="drawer"` is set, then the drawer will move the navbar, taking the full available height.
|
|
59
|
+
* @attr {navbar|drawer} primary-section
|
|
60
|
+
* @type {!PrimarySection}
|
|
61
|
+
*/
|
|
62
|
+
primarySection: {
|
|
63
|
+
type: String,
|
|
64
|
+
value: 'navbar',
|
|
65
|
+
notify: true,
|
|
66
|
+
reflectToAttribute: true,
|
|
67
|
+
observer: '__primarySectionChanged',
|
|
68
|
+
sync: true,
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Controls whether the drawer is opened (visible) or not.
|
|
73
|
+
* Its default value depends on the viewport:
|
|
74
|
+
* - `true`, for desktop size views
|
|
75
|
+
* - `false`, for mobile size views
|
|
76
|
+
* @attr {boolean} drawer-opened
|
|
77
|
+
* @type {boolean}
|
|
78
|
+
*/
|
|
79
|
+
drawerOpened: {
|
|
80
|
+
type: Boolean,
|
|
81
|
+
notify: true,
|
|
82
|
+
value: true,
|
|
83
|
+
reflectToAttribute: true,
|
|
84
|
+
observer: '__drawerOpenedChanged',
|
|
85
|
+
sync: true,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Drawer is an overlay on top of the content
|
|
90
|
+
* Controlled via CSS using `--vaadin-app-layout-drawer-overlay: true|false`;
|
|
91
|
+
* @type {boolean}
|
|
92
|
+
*/
|
|
93
|
+
overlay: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
notify: true,
|
|
96
|
+
readOnly: true,
|
|
97
|
+
value: false,
|
|
98
|
+
reflectToAttribute: true,
|
|
99
|
+
sync: true,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A global event that causes the drawer to close (be hidden) when it is in overlay mode.
|
|
104
|
+
* - The default is `vaadin-router-location-changed` dispatched by Vaadin Router
|
|
105
|
+
*
|
|
106
|
+
* @attr {string} close-drawer-on
|
|
107
|
+
* @type {string}
|
|
108
|
+
*/
|
|
109
|
+
closeDrawerOn: {
|
|
110
|
+
type: String,
|
|
111
|
+
value: 'vaadin-router-location-changed',
|
|
112
|
+
observer: '_closeDrawerOnChanged',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Helper static method that dispatches a `close-overlay-drawer` event
|
|
119
|
+
*/
|
|
120
|
+
static dispatchCloseOverlayDrawerEvent() {
|
|
121
|
+
window.dispatchEvent(new CustomEvent('close-overlay-drawer'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
constructor() {
|
|
125
|
+
super();
|
|
126
|
+
// TODO(jouni): might want to debounce
|
|
127
|
+
this.__boundResizeListener = this._resize.bind(this);
|
|
128
|
+
this.__drawerToggleClickListener = this._drawerToggleClick.bind(this);
|
|
129
|
+
this.__onDrawerKeyDown = this.__onDrawerKeyDown.bind(this);
|
|
130
|
+
this.__closeOverlayDrawerListener = this.__closeOverlayDrawer.bind(this);
|
|
131
|
+
this.__trapFocusInDrawer = this.__trapFocusInDrawer.bind(this);
|
|
132
|
+
this.__releaseFocusFromDrawer = this.__releaseFocusFromDrawer.bind(this);
|
|
133
|
+
|
|
134
|
+
// Hide all the elements except the drawer toggle and drawer content
|
|
135
|
+
this.__ariaModalController = new AriaModalController(this, () => [
|
|
136
|
+
...this.querySelectorAll('vaadin-drawer-toggle, [slot="drawer"]'),
|
|
137
|
+
]);
|
|
138
|
+
this.__focusTrapController = new FocusTrapController(this);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @protected */
|
|
142
|
+
connectedCallback() {
|
|
143
|
+
super.connectedCallback();
|
|
144
|
+
|
|
145
|
+
this._blockAnimationUntilAfterNextRender();
|
|
146
|
+
|
|
147
|
+
window.addEventListener('resize', this.__boundResizeListener);
|
|
148
|
+
this.addEventListener('drawer-toggle-click', this.__drawerToggleClickListener);
|
|
149
|
+
|
|
150
|
+
beforeNextRender(this, this._afterFirstRender);
|
|
151
|
+
|
|
152
|
+
this._updateTouchOptimizedMode();
|
|
153
|
+
this._updateDrawerSize();
|
|
154
|
+
this._updateOverlayMode();
|
|
155
|
+
|
|
156
|
+
this._navbarSizeObserver = new ResizeObserver(() => {
|
|
157
|
+
requestAnimationFrame(() => {
|
|
158
|
+
// Prevent updating offset size multiple times
|
|
159
|
+
// during the drawer open / close transition.
|
|
160
|
+
if (this.__isDrawerAnimating) {
|
|
161
|
+
this.__updateOffsetSizePending = true;
|
|
162
|
+
} else {
|
|
163
|
+
this._updateOffsetSize();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
this._navbarSizeObserver.observe(this.$.navbarTop);
|
|
168
|
+
this._navbarSizeObserver.observe(this.$.navbarBottom);
|
|
169
|
+
|
|
170
|
+
window.addEventListener('close-overlay-drawer', this.__closeOverlayDrawerListener);
|
|
171
|
+
window.addEventListener('keydown', this.__onDrawerKeyDown);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @protected */
|
|
175
|
+
ready() {
|
|
176
|
+
super.ready();
|
|
177
|
+
this.addController(this.__focusTrapController);
|
|
178
|
+
this.__setAriaExpanded();
|
|
179
|
+
|
|
180
|
+
this.$.drawer.addEventListener('transitionstart', () => {
|
|
181
|
+
this.__isDrawerAnimating = true;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this.$.drawer.addEventListener('transitionend', () => {
|
|
185
|
+
// Update offset size after drawer animation.
|
|
186
|
+
if (this.__updateOffsetSizePending) {
|
|
187
|
+
this.__updateOffsetSizePending = false;
|
|
188
|
+
this._updateOffsetSize();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Delay resetting the flag until animation frame
|
|
192
|
+
// to avoid updating offset size again on resize.
|
|
193
|
+
requestAnimationFrame(() => {
|
|
194
|
+
this.__isDrawerAnimating = false;
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @protected */
|
|
200
|
+
disconnectedCallback() {
|
|
201
|
+
super.disconnectedCallback();
|
|
202
|
+
|
|
203
|
+
window.removeEventListener('resize', this.__boundResizeListener);
|
|
204
|
+
this.removeEventListener('drawer-toggle-click', this.__drawerToggleClickListener);
|
|
205
|
+
window.removeEventListener('close-overlay-drawer', this.__drawerToggleClickListener);
|
|
206
|
+
window.removeEventListener('keydown', this.__onDrawerKeyDown);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A callback for the `primarySection` property observer.
|
|
211
|
+
*
|
|
212
|
+
* Ensures the property is set to its default value `navbar`
|
|
213
|
+
* whenever the new value is not one of the valid values: `navbar`, `drawer`.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} value
|
|
216
|
+
* @private
|
|
217
|
+
*/
|
|
218
|
+
__primarySectionChanged(value) {
|
|
219
|
+
const isValid = ['navbar', 'drawer'].includes(value);
|
|
220
|
+
if (!isValid) {
|
|
221
|
+
this.primarySection = 'navbar';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A callback for the `drawerOpened` property observer.
|
|
227
|
+
*
|
|
228
|
+
* When the drawer opens, the method ensures the drawer has a proper height and sets focus on it.
|
|
229
|
+
* As long as the drawer is open, the focus is trapped within the drawer.
|
|
230
|
+
*
|
|
231
|
+
* When the drawer closes, the method releases focus from the drawer, setting focus on the drawer toggle.
|
|
232
|
+
*
|
|
233
|
+
* @param {boolean} drawerOpened
|
|
234
|
+
* @param {boolean} oldDrawerOpened
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
__drawerOpenedChanged(drawerOpened, oldDrawerOpened) {
|
|
238
|
+
if (this.overlay) {
|
|
239
|
+
if (drawerOpened) {
|
|
240
|
+
this.__trapFocusInDrawer();
|
|
241
|
+
} else if (oldDrawerOpened) {
|
|
242
|
+
this.__releaseFocusFromDrawer();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.__setAriaExpanded();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A callback for the `i18n` property observer.
|
|
251
|
+
*
|
|
252
|
+
* The method ensures the drawer has ARIA attributes updated
|
|
253
|
+
* once the `i18n` property changes.
|
|
254
|
+
*
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
__i18nChanged() {
|
|
258
|
+
this.__updateDrawerAriaAttributes();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** @protected */
|
|
262
|
+
_afterFirstRender() {
|
|
263
|
+
this._blockAnimationUntilAfterNextRender();
|
|
264
|
+
this._updateOffsetSize();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** @private */
|
|
268
|
+
_drawerToggleClick(e) {
|
|
269
|
+
e.stopPropagation();
|
|
270
|
+
this.drawerOpened = !this.drawerOpened;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** @private */
|
|
274
|
+
__closeOverlayDrawer() {
|
|
275
|
+
if (this.overlay) {
|
|
276
|
+
this.drawerOpened = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** @private */
|
|
281
|
+
__setAriaExpanded() {
|
|
282
|
+
const toggle = this.querySelector('vaadin-drawer-toggle');
|
|
283
|
+
if (toggle) {
|
|
284
|
+
toggle.setAttribute('aria-expanded', this.drawerOpened);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @protected */
|
|
289
|
+
_updateDrawerSize() {
|
|
290
|
+
const childCount = this.querySelectorAll('[slot=drawer]').length;
|
|
291
|
+
const drawer = this.$.drawer;
|
|
292
|
+
|
|
293
|
+
if (childCount === 0) {
|
|
294
|
+
drawer.setAttribute('hidden', '');
|
|
295
|
+
this.style.setProperty('--_vaadin-app-layout-drawer-width', 0);
|
|
296
|
+
} else {
|
|
297
|
+
drawer.removeAttribute('hidden');
|
|
298
|
+
this.style.removeProperty('--_vaadin-app-layout-drawer-width');
|
|
299
|
+
}
|
|
300
|
+
this._updateOffsetSize();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @private */
|
|
304
|
+
_resize() {
|
|
305
|
+
this._blockAnimationUntilAfterNextRender();
|
|
306
|
+
this._updateTouchOptimizedMode();
|
|
307
|
+
this._updateOverlayMode();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** @protected */
|
|
311
|
+
_updateOffsetSize() {
|
|
312
|
+
const navbar = this.$.navbarTop;
|
|
313
|
+
const navbarRect = navbar.getBoundingClientRect();
|
|
314
|
+
|
|
315
|
+
const navbarBottom = this.$.navbarBottom;
|
|
316
|
+
const navbarBottomRect = navbarBottom.getBoundingClientRect();
|
|
317
|
+
|
|
318
|
+
const drawer = this.$.drawer;
|
|
319
|
+
const drawerRect = drawer.getBoundingClientRect();
|
|
320
|
+
|
|
321
|
+
this.style.setProperty('--_vaadin-app-layout-navbar-offset-size', `${navbarRect.height}px`);
|
|
322
|
+
this.style.setProperty('--_vaadin-app-layout-navbar-offset-size-bottom', `${navbarBottomRect.height}px`);
|
|
323
|
+
this.style.setProperty('--_vaadin-app-layout-drawer-offset-size', `${drawerRect.width}px`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @protected */
|
|
327
|
+
_updateOverlayMode() {
|
|
328
|
+
const overlay = this._getCustomPropertyValue('--vaadin-app-layout-drawer-overlay') === 'true';
|
|
329
|
+
|
|
330
|
+
if (!this.overlay && overlay) {
|
|
331
|
+
// Changed from not overlay to overlay
|
|
332
|
+
this._drawerStateSaved = this.drawerOpened;
|
|
333
|
+
this.drawerOpened = false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this._setOverlay(overlay);
|
|
337
|
+
|
|
338
|
+
if (!this.overlay && this._drawerStateSaved) {
|
|
339
|
+
this.drawerOpened = this._drawerStateSaved;
|
|
340
|
+
this._drawerStateSaved = null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.__updateDrawerAriaAttributes();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Updates ARIA attributes on the drawer depending on the drawer mode.
|
|
348
|
+
*
|
|
349
|
+
* - In the overlay mode, the method marks the drawer with ARIA attributes as a dialog
|
|
350
|
+
* labelled with the `i18n.drawer` property.
|
|
351
|
+
* - In the normal mode, the method removes the ARIA attributes that has been set for the overlay mode.
|
|
352
|
+
*
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
__updateDrawerAriaAttributes() {
|
|
356
|
+
const drawer = this.$.drawer;
|
|
357
|
+
if (this.overlay) {
|
|
358
|
+
drawer.setAttribute('role', 'dialog');
|
|
359
|
+
drawer.setAttribute('aria-modal', 'true');
|
|
360
|
+
drawer.setAttribute('aria-label', this.i18n.drawer);
|
|
361
|
+
} else {
|
|
362
|
+
drawer.removeAttribute('role');
|
|
363
|
+
drawer.removeAttribute('aria-modal');
|
|
364
|
+
drawer.removeAttribute('aria-label');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns a promise that resolves when the drawer opening/closing CSS transition ends.
|
|
370
|
+
*
|
|
371
|
+
* The method relies on the `--vaadin-app-layout-transition` CSS variable to detect whether
|
|
372
|
+
* the drawer has a CSS transition that needs to be awaited. If the CSS variable equals `none`,
|
|
373
|
+
* the promise resolves immediately.
|
|
374
|
+
*
|
|
375
|
+
* @return {Promise}
|
|
376
|
+
* @private
|
|
377
|
+
*/
|
|
378
|
+
__drawerTransitionComplete() {
|
|
379
|
+
return new Promise((resolve) => {
|
|
380
|
+
if (this._getCustomPropertyValue('--vaadin-app-layout-transition') === 'none') {
|
|
381
|
+
resolve();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.$.drawer.addEventListener('transitionend', resolve, { once: true });
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** @private */
|
|
390
|
+
async __trapFocusInDrawer() {
|
|
391
|
+
// Wait for the drawer CSS transition before focusing the drawer
|
|
392
|
+
// in order for VoiceOver to have a proper outline.
|
|
393
|
+
await this.__drawerTransitionComplete();
|
|
394
|
+
|
|
395
|
+
if (!this.drawerOpened) {
|
|
396
|
+
// The drawer has been closed during the CSS transition.
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.$.drawer.setAttribute('tabindex', '0');
|
|
401
|
+
|
|
402
|
+
this.__ariaModalController.showModal();
|
|
403
|
+
this.__focusTrapController.trapFocus(this.$.drawer);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** @private */
|
|
407
|
+
async __releaseFocusFromDrawer() {
|
|
408
|
+
// Wait for the drawer CSS transition in order to restore focus to the toggle
|
|
409
|
+
// only after `visibility` becomes `hidden`, that is, the drawer becomes inaccessible by the tabbing navigation.
|
|
410
|
+
await this.__drawerTransitionComplete();
|
|
411
|
+
|
|
412
|
+
if (this.drawerOpened) {
|
|
413
|
+
// The drawer has been opened during the CSS transition.
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.__ariaModalController.close();
|
|
418
|
+
this.__focusTrapController.releaseFocus();
|
|
419
|
+
this.$.drawer.removeAttribute('tabindex');
|
|
420
|
+
|
|
421
|
+
// Move focus to the drawer toggle when closing the drawer.
|
|
422
|
+
const toggle = this.querySelector('vaadin-drawer-toggle');
|
|
423
|
+
if (toggle) {
|
|
424
|
+
toggle.focus();
|
|
425
|
+
toggle.setAttribute('focus-ring', 'focus');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Closes the drawer on Escape press if it has been opened in the overlay mode.
|
|
431
|
+
*
|
|
432
|
+
* @param {KeyboardEvent} event
|
|
433
|
+
* @private
|
|
434
|
+
*/
|
|
435
|
+
__onDrawerKeyDown(event) {
|
|
436
|
+
if (event.key === 'Escape' && this.overlay) {
|
|
437
|
+
this.drawerOpened = false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** @private */
|
|
442
|
+
_closeDrawerOnChanged(closeDrawerOn, oldCloseDrawerOn) {
|
|
443
|
+
if (oldCloseDrawerOn) {
|
|
444
|
+
window.removeEventListener(oldCloseDrawerOn, this.__closeOverlayDrawerListener);
|
|
445
|
+
}
|
|
446
|
+
if (closeDrawerOn) {
|
|
447
|
+
window.addEventListener(closeDrawerOn, this.__closeOverlayDrawerListener);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** @private */
|
|
452
|
+
_onBackdropClick() {
|
|
453
|
+
this._close();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** @private */
|
|
457
|
+
_onBackdropTouchend(event) {
|
|
458
|
+
// Prevent the click event from being fired
|
|
459
|
+
// on clickable element behind the backdrop
|
|
460
|
+
event.preventDefault();
|
|
461
|
+
|
|
462
|
+
this._close();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** @protected */
|
|
466
|
+
_close() {
|
|
467
|
+
this.drawerOpened = false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** @private */
|
|
471
|
+
_getCustomPropertyValue(customProperty) {
|
|
472
|
+
const customPropertyValue = getComputedStyle(this).getPropertyValue(customProperty);
|
|
473
|
+
return (customPropertyValue || '').trim().toLowerCase();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** @protected */
|
|
477
|
+
_updateTouchOptimizedMode() {
|
|
478
|
+
const touchOptimized = this._getCustomPropertyValue('--vaadin-app-layout-touch-optimized') === 'true';
|
|
479
|
+
|
|
480
|
+
const navbarItems = this.querySelectorAll('[slot*="navbar"]');
|
|
481
|
+
|
|
482
|
+
if (navbarItems.length > 0) {
|
|
483
|
+
Array.from(navbarItems).forEach((navbar) => {
|
|
484
|
+
if (navbar.getAttribute('slot').indexOf('touch-optimized') > -1) {
|
|
485
|
+
navbar.__touchOptimized = true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (touchOptimized && navbar.__touchOptimized) {
|
|
489
|
+
navbar.setAttribute('slot', 'navbar-bottom');
|
|
490
|
+
} else {
|
|
491
|
+
navbar.setAttribute('slot', 'navbar');
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (this.$.navbarTop.querySelector('[name=navbar]').assignedNodes().length === 0) {
|
|
497
|
+
this.$.navbarTop.setAttribute('hidden', '');
|
|
498
|
+
} else {
|
|
499
|
+
this.$.navbarTop.removeAttribute('hidden');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (this.$.navbarBottom.querySelector('[name=navbar-bottom]').assignedNodes().length === 0) {
|
|
503
|
+
this.$.navbarBottom.setAttribute('hidden', '');
|
|
504
|
+
} else {
|
|
505
|
+
this.$.navbarBottom.removeAttribute('hidden');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this._updateOffsetSize();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** @protected */
|
|
512
|
+
_blockAnimationUntilAfterNextRender() {
|
|
513
|
+
this.setAttribute('no-anim', '');
|
|
514
|
+
afterNextRender(this, () => {
|
|
515
|
+
this.removeAttribute('no-anim');
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* App Layout listens to `close-overlay-drawer` on the window level.
|
|
521
|
+
* A custom event can be dispatched and the App Layout will close the drawer in overlay.
|
|
522
|
+
*
|
|
523
|
+
* That can be used, for instance, when a navigation occurs when user clicks in a menu item inside the drawer.
|
|
524
|
+
*
|
|
525
|
+
* See `dispatchCloseOverlayDrawerEvent()` helper method.
|
|
526
|
+
*
|
|
527
|
+
* @event close-overlay-drawer
|
|
528
|
+
*/
|
|
529
|
+
};
|