@everymatrix/general-input 1.44.0 → 1.45.2
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/dist/cjs/app-globals-3a1e7e63.js +5 -0
- package/dist/cjs/checkbox-group-input_10.cjs.entry.js +3873 -1756
- package/dist/cjs/general-input.cjs.entry.js +65 -63
- package/dist/cjs/general-input.cjs.js +17 -11
- package/dist/cjs/index-8cb018cb.js +1316 -0
- package/dist/cjs/index.cjs.js +13 -13
- package/dist/cjs/loader.cjs.js +7 -13
- package/dist/cjs/locale.utils-76c75c40.js +147 -0
- package/dist/cjs/toggle-checkbox-input.cjs.entry.js +77 -78
- package/dist/collection/collection-manifest.json +14 -14
- package/dist/collection/components/checkbox-group-input/checkbox-group-input.js +368 -353
- package/dist/collection/components/checkbox-input/checkbox-input.js +325 -315
- package/dist/collection/components/date-input/date-input.css +2 -2
- package/dist/collection/components/date-input/date-input.js +397 -376
- package/dist/collection/components/email-input/email-input.css +7 -11
- package/dist/collection/components/email-input/email-input.js +404 -385
- package/dist/collection/components/general-input/general-input.js +373 -368
- package/dist/collection/components/general-input/index.js +1 -0
- package/dist/collection/components/number-input/number-input.js +370 -352
- package/dist/collection/components/password-input/password-input.css +2 -4
- package/dist/collection/components/password-input/password-input.js +513 -540
- package/dist/collection/components/radio-input/radio-input.js +301 -286
- package/dist/collection/components/select-input/select-input.css +8 -9
- package/dist/collection/components/select-input/select-input.js +427 -414
- package/dist/collection/components/tel-input/tel-input.css +1 -1
- package/dist/collection/components/tel-input/tel-input.js +440 -422
- package/dist/collection/components/text-input/text-input.css +0 -1
- package/dist/collection/components/text-input/text-input.js +444 -429
- package/dist/collection/components/toggle-checkbox-input/toggle-checkbox-input.js +327 -340
- package/dist/collection/index.js +13 -13
- package/dist/collection/utils/locale.utils.js +133 -133
- package/dist/collection/utils/utils.js +3 -3
- package/dist/esm/app-globals-0f993ce5.js +3 -0
- package/dist/esm/checkbox-group-input_10.entry.js +3873 -1756
- package/dist/esm/general-input.entry.js +65 -63
- package/dist/esm/general-input.js +14 -11
- package/dist/esm/index-514fda47.js +1287 -0
- package/dist/esm/index.js +13 -13
- package/dist/esm/loader.js +7 -13
- package/dist/esm/locale.utils-ca41bf95.js +144 -0
- package/dist/esm/toggle-checkbox-input.entry.js +77 -78
- package/dist/general-input/general-input.esm.js +1 -1
- package/dist/general-input/p-03e81c11.js +2 -0
- package/dist/general-input/p-aec71434.js +1 -0
- package/dist/general-input/p-e1255160.js +1 -0
- package/dist/general-input/p-eb454344.entry.js +1 -0
- package/dist/general-input/p-ecdc294b.entry.js +5430 -0
- package/dist/general-input/p-f92ab852.entry.js +1 -0
- package/dist/stencil.config.dev.js +17 -0
- package/dist/stencil.config.js +14 -19
- package/dist/types/Users/adrian.pripon/Documents/Work/widgets-monorepo/packages/stencil/general-input/.stencil/packages/stencil/general-input/stencil.config.d.ts +2 -0
- package/dist/types/Users/adrian.pripon/Documents/Work/widgets-monorepo/packages/stencil/general-input/.stencil/packages/stencil/general-input/stencil.config.dev.d.ts +2 -0
- package/dist/types/components/checkbox-group-input/checkbox-group-input.d.ts +68 -68
- package/dist/types/components/checkbox-input/checkbox-input.d.ts +61 -62
- package/dist/types/components/date-input/date-input.d.ts +78 -78
- package/dist/types/components/email-input/email-input.d.ts +77 -77
- package/dist/types/components/general-input/general-input.d.ts +72 -72
- package/dist/types/components/general-input/index.d.ts +1 -0
- package/dist/types/components/number-input/number-input.d.ts +71 -71
- package/dist/types/components/password-input/password-input.d.ts +87 -92
- package/dist/types/components/radio-input/radio-input.d.ts +55 -55
- package/dist/types/components/select-input/select-input.d.ts +79 -79
- package/dist/types/components/tel-input/tel-input.d.ts +85 -85
- package/dist/types/components/text-input/text-input.d.ts +81 -81
- package/dist/types/components/toggle-checkbox-input/toggle-checkbox-input.d.ts +63 -68
- package/dist/types/components.d.ts +201 -39
- package/dist/types/stencil-public-runtime.d.ts +142 -33
- package/dist/types/utils/locale.utils.d.ts +8 -8
- package/dist/types/utils/types.d.ts +54 -54
- package/loader/cdn.js +1 -3
- package/loader/index.cjs.js +1 -3
- package/loader/index.d.ts +13 -1
- package/loader/index.es2017.js +1 -3
- package/loader/index.js +1 -3
- package/loader/package.json +1 -0
- package/package.json +8 -1
- package/dist/cjs/index-132a0774.js +0 -1327
- package/dist/cjs/locale.utils-2fa6f747.js +0 -147
- package/dist/components/active-mixin.js +0 -975
- package/dist/components/checkbox-group-input.d.ts +0 -11
- package/dist/components/checkbox-group-input.js +0 -6
- package/dist/components/checkbox-group-input2.js +0 -1078
- package/dist/components/checkbox-input.d.ts +0 -11
- package/dist/components/checkbox-input.js +0 -6
- package/dist/components/checkbox-input2.js +0 -132
- package/dist/components/date-input.d.ts +0 -11
- package/dist/components/date-input.js +0 -6
- package/dist/components/date-input2.js +0 -11556
- package/dist/components/email-input.d.ts +0 -11
- package/dist/components/email-input.js +0 -6
- package/dist/components/email-input2.js +0 -171
- package/dist/components/field-mixin.js +0 -12426
- package/dist/components/general-input.d.ts +0 -11
- package/dist/components/general-input.js +0 -6
- package/dist/components/general-input2.js +0 -350
- package/dist/components/index.d.ts +0 -26
- package/dist/components/index.js +0 -18
- package/dist/components/input-field-shared-styles.js +0 -1211
- package/dist/components/number-input.d.ts +0 -11
- package/dist/components/number-input.js +0 -6
- package/dist/components/number-input2.js +0 -158
- package/dist/components/password-input.d.ts +0 -11
- package/dist/components/password-input.js +0 -6
- package/dist/components/password-input2.js +0 -1059
- package/dist/components/radio-input.d.ts +0 -11
- package/dist/components/radio-input.js +0 -6
- package/dist/components/radio-input2.js +0 -114
- package/dist/components/select-input.d.ts +0 -11
- package/dist/components/select-input.js +0 -6
- package/dist/components/select-input2.js +0 -183
- package/dist/components/tel-input.d.ts +0 -11
- package/dist/components/tel-input.js +0 -6
- package/dist/components/tel-input2.js +0 -197
- package/dist/components/text-input.d.ts +0 -11
- package/dist/components/text-input.js +0 -6
- package/dist/components/text-input2.js +0 -199
- package/dist/components/toggle-checkbox-input.d.ts +0 -11
- package/dist/components/toggle-checkbox-input.js +0 -6
- package/dist/components/tooltipIcon.js +0 -146
- package/dist/components/vaadin-button.js +0 -490
- package/dist/components/vaadin-combo-box.js +0 -4512
- package/dist/components/virtual-keyboard-controller.js +0 -2001
- package/dist/esm/index-db76d5b5.js +0 -1299
- package/dist/esm/locale.utils-30fb5289.js +0 -144
- package/dist/esm/polyfills/core-js.js +0 -11
- package/dist/esm/polyfills/css-shim.js +0 -1
- package/dist/esm/polyfills/dom.js +0 -79
- package/dist/esm/polyfills/es5-html-element.js +0 -1
- package/dist/esm/polyfills/index.js +0 -34
- package/dist/esm/polyfills/system.js +0 -6
- package/dist/general-input/p-04d4b145.js +0 -1
- package/dist/general-input/p-a8221ce9.entry.js +0 -1
- package/dist/general-input/p-b408093e.js +0 -1
- package/dist/general-input/p-b64caafa.entry.js +0 -3646
- package/dist/general-input/p-c85c0e4d.entry.js +0 -1
- package/dist/types/Users/adrian.pripon/Documents/Work/widgets-stencil/packages/general-input/.stencil/packages/general-input/stencil.config.d.ts +0 -2
- /package/dist/cjs/{tooltipIcon-092a795f.js → tooltipIcon-7e9ee226.js} +0 -0
- /package/dist/esm/{tooltipIcon-99c1c7b7.js → tooltipIcon-0a5a06a2.js} +0 -0
- /package/dist/general-input/{p-f4f4ccda.js → p-2dccd0bf.js} +0 -0
|
@@ -1,2001 +0,0 @@
|
|
|
1
|
-
import { i, r as registerStyles, J as getDeepActiveElement, M as getFocusableElements, N as isElementFocused, H as ControllerMixin, O as getAncestorRootNodes } from './field-mixin.js';
|
|
2
|
-
import { b as isIOS } from './input-field-shared-styles.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @license
|
|
6
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
7
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const overlay = i`
|
|
11
|
-
:host {
|
|
12
|
-
top: var(--lumo-space-m);
|
|
13
|
-
right: var(--lumo-space-m);
|
|
14
|
-
bottom: var(--lumo-space-m);
|
|
15
|
-
left: var(--lumo-space-m);
|
|
16
|
-
/* Workaround for Edge issue (only on Surface), where an overflowing vaadin-list-box inside vaadin-select-overlay makes the overlay transparent */
|
|
17
|
-
/* stylelint-disable-next-line */
|
|
18
|
-
outline: 0px solid transparent;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
[part='overlay'] {
|
|
22
|
-
background-color: var(--lumo-base-color);
|
|
23
|
-
background-image: linear-gradient(var(--lumo-tint-5pct), var(--lumo-tint-5pct));
|
|
24
|
-
border-radius: var(--lumo-border-radius-m);
|
|
25
|
-
box-shadow: 0 0 0 1px var(--lumo-shade-5pct), var(--lumo-box-shadow-m);
|
|
26
|
-
color: var(--lumo-body-text-color);
|
|
27
|
-
font-family: var(--lumo-font-family);
|
|
28
|
-
font-size: var(--lumo-font-size-m);
|
|
29
|
-
font-weight: 400;
|
|
30
|
-
line-height: var(--lumo-line-height-m);
|
|
31
|
-
letter-spacing: 0;
|
|
32
|
-
text-transform: none;
|
|
33
|
-
-webkit-text-size-adjust: 100%;
|
|
34
|
-
-webkit-font-smoothing: antialiased;
|
|
35
|
-
-moz-osx-font-smoothing: grayscale;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
[part='content'] {
|
|
39
|
-
padding: var(--lumo-space-xs);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
[part='backdrop'] {
|
|
43
|
-
background-color: var(--lumo-shade-20pct);
|
|
44
|
-
animation: 0.2s lumo-overlay-backdrop-enter both;
|
|
45
|
-
will-change: opacity;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
@keyframes lumo-overlay-backdrop-enter {
|
|
49
|
-
0% {
|
|
50
|
-
opacity: 0;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
:host([closing]) [part='backdrop'] {
|
|
55
|
-
animation: 0.2s lumo-overlay-backdrop-exit both;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@keyframes lumo-overlay-backdrop-exit {
|
|
59
|
-
100% {
|
|
60
|
-
opacity: 0;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@keyframes lumo-overlay-dummy-animation {
|
|
65
|
-
0% {
|
|
66
|
-
opacity: 1;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
100% {
|
|
70
|
-
opacity: 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
registerStyles('', overlay, { moduleId: 'lumo-overlay' });
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @license
|
|
79
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
80
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
const menuOverlayCore = i`
|
|
84
|
-
:host([opening]),
|
|
85
|
-
:host([closing]) {
|
|
86
|
-
animation: 0.14s lumo-overlay-dummy-animation;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
[part='overlay'] {
|
|
90
|
-
will-change: opacity, transform;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
:host([opening]) [part='overlay'] {
|
|
94
|
-
animation: 0.1s lumo-menu-overlay-enter ease-out both;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
@keyframes lumo-menu-overlay-enter {
|
|
98
|
-
0% {
|
|
99
|
-
opacity: 0;
|
|
100
|
-
transform: translateY(-4px);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
:host([closing]) [part='overlay'] {
|
|
105
|
-
animation: 0.1s lumo-menu-overlay-exit both;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
@keyframes lumo-menu-overlay-exit {
|
|
109
|
-
100% {
|
|
110
|
-
opacity: 0;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
`;
|
|
114
|
-
|
|
115
|
-
registerStyles('', menuOverlayCore, { moduleId: 'lumo-menu-overlay-core' });
|
|
116
|
-
|
|
117
|
-
const menuOverlayExt = i`
|
|
118
|
-
/* Small viewport (bottom sheet) styles */
|
|
119
|
-
/* Use direct media queries instead of the state attributes ([phone] and [fullscreen]) provided by the elements */
|
|
120
|
-
@media (max-width: 420px), (max-height: 420px) {
|
|
121
|
-
:host {
|
|
122
|
-
top: 0 !important;
|
|
123
|
-
right: 0 !important;
|
|
124
|
-
bottom: var(--vaadin-overlay-viewport-bottom, 0) !important;
|
|
125
|
-
left: 0 !important;
|
|
126
|
-
align-items: stretch !important;
|
|
127
|
-
justify-content: flex-end !important;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
[part='overlay'] {
|
|
131
|
-
max-height: 50vh;
|
|
132
|
-
width: 100vw;
|
|
133
|
-
border-radius: 0;
|
|
134
|
-
box-shadow: var(--lumo-box-shadow-xl);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* The content part scrolls instead of the overlay part, because of the gradient fade-out */
|
|
138
|
-
[part='content'] {
|
|
139
|
-
padding: 30px var(--lumo-space-m);
|
|
140
|
-
max-height: inherit;
|
|
141
|
-
box-sizing: border-box;
|
|
142
|
-
-webkit-overflow-scrolling: touch;
|
|
143
|
-
overflow: auto;
|
|
144
|
-
-webkit-mask-image: linear-gradient(transparent, #000 40px, #000 calc(100% - 40px), transparent);
|
|
145
|
-
mask-image: linear-gradient(transparent, #000 40px, #000 calc(100% - 40px), transparent);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
[part='backdrop'] {
|
|
149
|
-
display: block;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* Animations */
|
|
153
|
-
|
|
154
|
-
:host([opening]) [part='overlay'] {
|
|
155
|
-
animation: 0.2s lumo-mobile-menu-overlay-enter cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
:host([closing]),
|
|
159
|
-
:host([closing]) [part='backdrop'] {
|
|
160
|
-
animation-delay: 0.14s;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
:host([closing]) [part='overlay'] {
|
|
164
|
-
animation: 0.14s 0.14s lumo-mobile-menu-overlay-exit cubic-bezier(0.55, 0.055, 0.675, 0.19) both;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@keyframes lumo-mobile-menu-overlay-enter {
|
|
169
|
-
0% {
|
|
170
|
-
transform: translateY(150%);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
@keyframes lumo-mobile-menu-overlay-exit {
|
|
175
|
-
100% {
|
|
176
|
-
transform: translateY(150%);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
`;
|
|
180
|
-
|
|
181
|
-
const menuOverlay = [overlay, menuOverlayCore, menuOverlayExt];
|
|
182
|
-
|
|
183
|
-
registerStyles('', menuOverlay, { moduleId: 'lumo-menu-overlay' });
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
@license
|
|
187
|
-
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
188
|
-
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
189
|
-
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
190
|
-
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
191
|
-
Code distributed by Google as part of the polymer project is also
|
|
192
|
-
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
193
|
-
*/
|
|
194
|
-
|
|
195
|
-
let scheduled = false;
|
|
196
|
-
let beforeRenderQueue = [];
|
|
197
|
-
let afterRenderQueue = [];
|
|
198
|
-
|
|
199
|
-
function schedule() {
|
|
200
|
-
scheduled = true;
|
|
201
|
-
// before next render
|
|
202
|
-
requestAnimationFrame(function() {
|
|
203
|
-
scheduled = false;
|
|
204
|
-
flushQueue(beforeRenderQueue);
|
|
205
|
-
// after the render
|
|
206
|
-
setTimeout(function() {
|
|
207
|
-
runQueue(afterRenderQueue);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function flushQueue(queue) {
|
|
213
|
-
while (queue.length) {
|
|
214
|
-
callMethod(queue.shift());
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function runQueue(queue) {
|
|
219
|
-
for (let i=0, l=queue.length; i < l; i++) {
|
|
220
|
-
callMethod(queue.shift());
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function callMethod(info) {
|
|
225
|
-
const context = info[0];
|
|
226
|
-
const callback = info[1];
|
|
227
|
-
const args = info[2];
|
|
228
|
-
try {
|
|
229
|
-
callback.apply(context, args);
|
|
230
|
-
} catch(e) {
|
|
231
|
-
setTimeout(() => {
|
|
232
|
-
throw e;
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Enqueues a callback which will be run after the next render, equivalent
|
|
239
|
-
* to one task (`setTimeout`) after the next `requestAnimationFrame`.
|
|
240
|
-
*
|
|
241
|
-
* This method is useful for tuning the first-render performance of an
|
|
242
|
-
* element or application by deferring non-critical work until after the
|
|
243
|
-
* first paint. Typical non-render-critical work may include adding UI
|
|
244
|
-
* event listeners and aria attributes.
|
|
245
|
-
*
|
|
246
|
-
* @param {*} context Context object the callback function will be bound to
|
|
247
|
-
* @param {function(...*):void} callback Callback function
|
|
248
|
-
* @param {!Array=} args An array of arguments to call the callback function with
|
|
249
|
-
* @return {void}
|
|
250
|
-
*/
|
|
251
|
-
function afterNextRender(context, callback, args) {
|
|
252
|
-
if (!scheduled) {
|
|
253
|
-
schedule();
|
|
254
|
-
}
|
|
255
|
-
afterRenderQueue.push([context, callback, args]);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* @license
|
|
260
|
-
* Copyright (c) 2017 Anton Korzunov
|
|
261
|
-
* SPDX-License-Identifier: MIT
|
|
262
|
-
*/
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* @fileoverview
|
|
266
|
-
*
|
|
267
|
-
* This module includes JS code copied from the `aria-hidden` package:
|
|
268
|
-
* https://github.com/theKashey/aria-hidden/blob/master/src/index.ts
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
|
-
/** @type {WeakMap<Element, number>} */
|
|
272
|
-
let counterMap = new WeakMap();
|
|
273
|
-
|
|
274
|
-
/** @type {WeakMap<Element, boolean>} */
|
|
275
|
-
let uncontrolledNodes = new WeakMap();
|
|
276
|
-
|
|
277
|
-
/** @type {Record<string, WeakMap<Element, number>>} */
|
|
278
|
-
let markerMap = {};
|
|
279
|
-
|
|
280
|
-
/** @type {number} */
|
|
281
|
-
let lockCount = 0;
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* @param {?Node} node
|
|
285
|
-
* @return {boolean}
|
|
286
|
-
*/
|
|
287
|
-
const isElement = (node) => node && node.nodeType === Node.ELEMENT_NODE;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @param {...unknown} args
|
|
291
|
-
*/
|
|
292
|
-
const logError = (...args) => {
|
|
293
|
-
console.error(`Error: ${args.join(' ')}. Skip setting aria-hidden.`);
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* @param {HTMLElement} parent
|
|
298
|
-
* @param {Element[]} targets
|
|
299
|
-
* @return {Element[]}
|
|
300
|
-
*/
|
|
301
|
-
const correctTargets = (parent, targets) => {
|
|
302
|
-
if (!isElement(parent)) {
|
|
303
|
-
logError(parent, 'is not a valid element');
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return targets
|
|
308
|
-
.map((target) => {
|
|
309
|
-
if (!isElement(target)) {
|
|
310
|
-
logError(target, 'is not a valid element');
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
let node = target;
|
|
315
|
-
while (node && node !== parent) {
|
|
316
|
-
if (parent.contains(node)) {
|
|
317
|
-
return target;
|
|
318
|
-
}
|
|
319
|
-
node = node.getRootNode().host;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
logError(target, 'is not contained inside', parent);
|
|
323
|
-
return null;
|
|
324
|
-
})
|
|
325
|
-
.filter((x) => Boolean(x));
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Marks everything except given node(or nodes) as aria-hidden
|
|
330
|
-
* @param {Element | Element[]} originalTarget - elements to keep on the page
|
|
331
|
-
* @param {HTMLElement} [parentNode] - top element, defaults to document.body
|
|
332
|
-
* @param {String} [markerName] - a special attribute to mark every node
|
|
333
|
-
* @param {String} [controlAttribute] - html Attribute to control
|
|
334
|
-
* @return {Function}
|
|
335
|
-
*/
|
|
336
|
-
const applyAttributeToOthers = (originalTarget, parentNode, markerName, controlAttribute) => {
|
|
337
|
-
const targets = correctTargets(parentNode, Array.isArray(originalTarget) ? originalTarget : [originalTarget]);
|
|
338
|
-
|
|
339
|
-
if (!markerMap[markerName]) {
|
|
340
|
-
markerMap[markerName] = new WeakMap();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const markerCounter = markerMap[markerName];
|
|
344
|
-
|
|
345
|
-
/** @type {Element[]} */
|
|
346
|
-
const hiddenNodes = [];
|
|
347
|
-
|
|
348
|
-
/** @type {Set<Node>} */
|
|
349
|
-
const elementsToKeep = new Set();
|
|
350
|
-
|
|
351
|
-
/** @type {Set<Node>} */
|
|
352
|
-
const elementsToStop = new Set(targets);
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* @param {?Node} el
|
|
356
|
-
*/
|
|
357
|
-
const keep = (el) => {
|
|
358
|
-
if (!el || elementsToKeep.has(el)) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
elementsToKeep.add(el);
|
|
363
|
-
|
|
364
|
-
const slot = el.assignedSlot;
|
|
365
|
-
if (slot) {
|
|
366
|
-
keep(slot);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
keep(el.parentNode || el.host);
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
targets.forEach(keep);
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* @param {?Node} el
|
|
376
|
-
*/
|
|
377
|
-
const deep = (parent) => {
|
|
378
|
-
if (!parent || elementsToStop.has(parent)) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const root = parent.shadowRoot;
|
|
383
|
-
const children = root ? [...parent.children, ...root.children] : [...parent.children];
|
|
384
|
-
children.forEach((node) => {
|
|
385
|
-
// Skip elements that don't need to be hidden
|
|
386
|
-
if (['template', 'script', 'style'].includes(node.localName)) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (elementsToKeep.has(node)) {
|
|
391
|
-
deep(node);
|
|
392
|
-
} else {
|
|
393
|
-
const attr = node.getAttribute(controlAttribute);
|
|
394
|
-
const alreadyHidden = attr !== null && attr !== 'false';
|
|
395
|
-
const counterValue = (counterMap.get(node) || 0) + 1;
|
|
396
|
-
const markerValue = (markerCounter.get(node) || 0) + 1;
|
|
397
|
-
|
|
398
|
-
counterMap.set(node, counterValue);
|
|
399
|
-
markerCounter.set(node, markerValue);
|
|
400
|
-
hiddenNodes.push(node);
|
|
401
|
-
|
|
402
|
-
if (counterValue === 1 && alreadyHidden) {
|
|
403
|
-
uncontrolledNodes.set(node, true);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (markerValue === 1) {
|
|
407
|
-
node.setAttribute(markerName, 'true');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (!alreadyHidden) {
|
|
411
|
-
node.setAttribute(controlAttribute, 'true');
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
deep(parentNode);
|
|
418
|
-
|
|
419
|
-
elementsToKeep.clear();
|
|
420
|
-
|
|
421
|
-
lockCount += 1;
|
|
422
|
-
|
|
423
|
-
return () => {
|
|
424
|
-
hiddenNodes.forEach((node) => {
|
|
425
|
-
const counterValue = counterMap.get(node) - 1;
|
|
426
|
-
const markerValue = markerCounter.get(node) - 1;
|
|
427
|
-
|
|
428
|
-
counterMap.set(node, counterValue);
|
|
429
|
-
markerCounter.set(node, markerValue);
|
|
430
|
-
|
|
431
|
-
if (!counterValue) {
|
|
432
|
-
if (uncontrolledNodes.has(node)) {
|
|
433
|
-
uncontrolledNodes.delete(node);
|
|
434
|
-
} else {
|
|
435
|
-
node.removeAttribute(controlAttribute);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (!markerValue) {
|
|
440
|
-
node.removeAttribute(markerName);
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
lockCount -= 1;
|
|
445
|
-
|
|
446
|
-
if (!lockCount) {
|
|
447
|
-
// clear
|
|
448
|
-
counterMap = new WeakMap();
|
|
449
|
-
counterMap = new WeakMap();
|
|
450
|
-
uncontrolledNodes = new WeakMap();
|
|
451
|
-
markerMap = {};
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Marks everything except given node(or nodes) as aria-hidden
|
|
458
|
-
* @param {Element | Element[]} originalTarget - elements to keep on the page
|
|
459
|
-
* @param {HTMLElement} [parentNode] - top element, defaults to document.body
|
|
460
|
-
* @param {String} [markerName] - a special attribute to mark every node
|
|
461
|
-
* @return {Function} undo command
|
|
462
|
-
*/
|
|
463
|
-
const hideOthers = (originalTarget, parentNode = document.body, markerName = 'data-aria-hidden') => {
|
|
464
|
-
const targets = Array.from(Array.isArray(originalTarget) ? originalTarget : [originalTarget]);
|
|
465
|
-
|
|
466
|
-
if (parentNode) {
|
|
467
|
-
// We should not hide ariaLive elements - https://github.com/theKashey/aria-hidden/issues/10
|
|
468
|
-
targets.push(...Array.from(parentNode.querySelectorAll('[aria-live]')));
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return applyAttributeToOthers(targets, parentNode, markerName, 'aria-hidden');
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* @license
|
|
476
|
-
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
477
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
478
|
-
*/
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* A controller for handling modal state on the elements with `dialog` and `alertdialog` role.
|
|
482
|
-
* See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-modal
|
|
483
|
-
*
|
|
484
|
-
* Note, the actual `role` and `aria-modal` attributes are supposed to be handled by the
|
|
485
|
-
* consumer web component. This is done in to ensure the controller only does one thing.
|
|
486
|
-
*/
|
|
487
|
-
class AriaModalController {
|
|
488
|
-
/**
|
|
489
|
-
* @param {HTMLElement} host
|
|
490
|
-
*/
|
|
491
|
-
constructor(host, callback) {
|
|
492
|
-
/**
|
|
493
|
-
* The controller host element.
|
|
494
|
-
*
|
|
495
|
-
* @type {HTMLElement}
|
|
496
|
-
*/
|
|
497
|
-
this.host = host;
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* The callback used to detect which element
|
|
501
|
-
* to use as a target. Defaults to the host.
|
|
502
|
-
*
|
|
503
|
-
* @type {Function}
|
|
504
|
-
*/
|
|
505
|
-
this.callback = typeof callback === 'function' ? callback : () => host;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Make the controller host modal by hiding other elements from screen readers
|
|
510
|
-
* using `aria-hidden` attribute (can be replaced with `inert` in the future).
|
|
511
|
-
*
|
|
512
|
-
* The method name is chosen to align with the one provided by native `<dialog>`:
|
|
513
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal
|
|
514
|
-
*/
|
|
515
|
-
showModal() {
|
|
516
|
-
const targets = this.callback();
|
|
517
|
-
this.__showOthers = hideOthers(targets);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Remove `aria-hidden` from other elements unless there are any other
|
|
522
|
-
* controller hosts on the page activated by using `showModal()` call.
|
|
523
|
-
*/
|
|
524
|
-
close() {
|
|
525
|
-
if (this.__showOthers) {
|
|
526
|
-
this.__showOthers();
|
|
527
|
-
this.__showOthers = null;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* @license
|
|
534
|
-
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
535
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
536
|
-
*/
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* A controller for saving a focused node and restoring focus to it later.
|
|
540
|
-
*/
|
|
541
|
-
class FocusRestorationController {
|
|
542
|
-
/**
|
|
543
|
-
* Saves the given node as a target for restoring focus to
|
|
544
|
-
* when `restoreFocus()` is called. If no node is provided,
|
|
545
|
-
* the currently focused node in the DOM is saved as a target.
|
|
546
|
-
*
|
|
547
|
-
* @param {Node | null | undefined} focusNode
|
|
548
|
-
*/
|
|
549
|
-
saveFocus(focusNode) {
|
|
550
|
-
this.focusNode = focusNode || getDeepActiveElement();
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Restores focus to the target node that was saved previously with `saveFocus()`.
|
|
555
|
-
*/
|
|
556
|
-
restoreFocus() {
|
|
557
|
-
const focusNode = this.focusNode;
|
|
558
|
-
if (!focusNode) {
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (getDeepActiveElement() === document.body) {
|
|
563
|
-
// In Firefox and Safari, focusing the node synchronously
|
|
564
|
-
// doesn't work as expected when the overlay is closing on outside click.
|
|
565
|
-
// These browsers force focus to move to the body element and retain it
|
|
566
|
-
// there until the next event loop iteration.
|
|
567
|
-
setTimeout(() => focusNode.focus());
|
|
568
|
-
} else {
|
|
569
|
-
focusNode.focus();
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
this.focusNode = null;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* @license
|
|
578
|
-
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
579
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
580
|
-
*/
|
|
581
|
-
|
|
582
|
-
const instances = [];
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* A controller for trapping focus within a DOM node.
|
|
586
|
-
*/
|
|
587
|
-
class FocusTrapController {
|
|
588
|
-
/**
|
|
589
|
-
* @param {HTMLElement} host
|
|
590
|
-
*/
|
|
591
|
-
constructor(host) {
|
|
592
|
-
/**
|
|
593
|
-
* The controller host element.
|
|
594
|
-
*
|
|
595
|
-
* @type {HTMLElement}
|
|
596
|
-
*/
|
|
597
|
-
this.host = host;
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* A node for trapping focus in.
|
|
601
|
-
*
|
|
602
|
-
* @type {HTMLElement | null}
|
|
603
|
-
* @private
|
|
604
|
-
*/
|
|
605
|
-
this.__trapNode = null;
|
|
606
|
-
|
|
607
|
-
this.__onKeyDown = this.__onKeyDown.bind(this);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* An array of tab-ordered focusable elements inside the trap node.
|
|
612
|
-
*
|
|
613
|
-
* @return {HTMLElement[]}
|
|
614
|
-
* @private
|
|
615
|
-
*/
|
|
616
|
-
get __focusableElements() {
|
|
617
|
-
return getFocusableElements(this.__trapNode);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* The index of the element inside the trap node that currently has focus.
|
|
622
|
-
*
|
|
623
|
-
* @return {HTMLElement | undefined}
|
|
624
|
-
* @private
|
|
625
|
-
*/
|
|
626
|
-
get __focusedElementIndex() {
|
|
627
|
-
const focusableElements = this.__focusableElements;
|
|
628
|
-
return focusableElements.indexOf(focusableElements.filter(isElementFocused).pop());
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
hostConnected() {
|
|
632
|
-
document.addEventListener('keydown', this.__onKeyDown);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
hostDisconnected() {
|
|
636
|
-
document.removeEventListener('keydown', this.__onKeyDown);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Activates a focus trap for a DOM node that will prevent focus from escaping the node.
|
|
641
|
-
* The trap can be deactivated with the `.releaseFocus()` method.
|
|
642
|
-
*
|
|
643
|
-
* If focus is initially outside the trap, the method will move focus inside,
|
|
644
|
-
* on the first focusable element of the trap in the tab order.
|
|
645
|
-
* The first focusable element can be the trap node itself if it is focusable
|
|
646
|
-
* and comes first in the tab order.
|
|
647
|
-
*
|
|
648
|
-
* If there are no focusable elements, the method will throw an exception
|
|
649
|
-
* and the trap will not be set.
|
|
650
|
-
*
|
|
651
|
-
* @param {HTMLElement} trapNode
|
|
652
|
-
*/
|
|
653
|
-
trapFocus(trapNode) {
|
|
654
|
-
this.__trapNode = trapNode;
|
|
655
|
-
|
|
656
|
-
if (this.__focusableElements.length === 0) {
|
|
657
|
-
this.__trapNode = null;
|
|
658
|
-
throw new Error('The trap node should have at least one focusable descendant or be focusable itself.');
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
instances.push(this);
|
|
662
|
-
|
|
663
|
-
if (this.__focusedElementIndex === -1) {
|
|
664
|
-
this.__focusableElements[0].focus();
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Deactivates the focus trap set with the `.trapFocus()` method
|
|
670
|
-
* so that it becomes possible to tab outside the trap node.
|
|
671
|
-
*/
|
|
672
|
-
releaseFocus() {
|
|
673
|
-
this.__trapNode = null;
|
|
674
|
-
|
|
675
|
-
instances.pop();
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* A `keydown` event handler that manages tabbing navigation when the trap is enabled.
|
|
680
|
-
*
|
|
681
|
-
* - Moves focus to the next focusable element of the trap on `Tab` press.
|
|
682
|
-
* When no next element to focus, the method moves focus to the first focusable element.
|
|
683
|
-
* - Moves focus to the prev focusable element of the trap on `Shift+Tab` press.
|
|
684
|
-
* When no prev element to focus, the method moves focus to the last focusable element.
|
|
685
|
-
*
|
|
686
|
-
* @param {KeyboardEvent} event
|
|
687
|
-
* @private
|
|
688
|
-
*/
|
|
689
|
-
__onKeyDown(event) {
|
|
690
|
-
if (!this.__trapNode) {
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Only handle events for the last instance
|
|
695
|
-
if (this !== Array.from(instances).pop()) {
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
if (event.key === 'Tab') {
|
|
700
|
-
event.preventDefault();
|
|
701
|
-
|
|
702
|
-
const backward = event.shiftKey;
|
|
703
|
-
this.__focusNextElement(backward);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* - Moves focus to the next focusable element if `backward === false`.
|
|
709
|
-
* When no next element to focus, the method moves focus to the first focusable element.
|
|
710
|
-
* - Moves focus to the prev focusable element if `backward === true`.
|
|
711
|
-
* When no prev element to focus the method moves focus to the last focusable element.
|
|
712
|
-
*
|
|
713
|
-
* If no focusable elements, the method returns immediately.
|
|
714
|
-
*
|
|
715
|
-
* @param {boolean} backward
|
|
716
|
-
* @private
|
|
717
|
-
*/
|
|
718
|
-
__focusNextElement(backward = false) {
|
|
719
|
-
const focusableElements = this.__focusableElements;
|
|
720
|
-
const step = backward ? -1 : 1;
|
|
721
|
-
const currentIndex = this.__focusedElementIndex;
|
|
722
|
-
const nextIndex = (focusableElements.length + currentIndex + step) % focusableElements.length;
|
|
723
|
-
const element = focusableElements[nextIndex];
|
|
724
|
-
element.focus();
|
|
725
|
-
if (element.localName === 'input') {
|
|
726
|
-
element.select();
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* @license
|
|
733
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
734
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
735
|
-
*/
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* @polymerMixin
|
|
739
|
-
* @mixes ControllerMixin
|
|
740
|
-
*/
|
|
741
|
-
const OverlayFocusMixin = (superClass) =>
|
|
742
|
-
class OverlayFocusMixin extends ControllerMixin(superClass) {
|
|
743
|
-
static get properties() {
|
|
744
|
-
return {
|
|
745
|
-
/**
|
|
746
|
-
* When true, opening the overlay moves focus to the first focusable child,
|
|
747
|
-
* or to the overlay part with tabindex if there are no focusable children.
|
|
748
|
-
* @attr {boolean} focus-trap
|
|
749
|
-
*/
|
|
750
|
-
focusTrap: {
|
|
751
|
-
type: Boolean,
|
|
752
|
-
value: false,
|
|
753
|
-
},
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* Set to true to enable restoring of focus when overlay is closed.
|
|
757
|
-
* @attr {boolean} restore-focus-on-close
|
|
758
|
-
*/
|
|
759
|
-
restoreFocusOnClose: {
|
|
760
|
-
type: Boolean,
|
|
761
|
-
value: false,
|
|
762
|
-
},
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Set to specify the element which should be focused on overlay close,
|
|
766
|
-
* if `restoreFocusOnClose` is set to true.
|
|
767
|
-
* @type {HTMLElement}
|
|
768
|
-
*/
|
|
769
|
-
restoreFocusNode: {
|
|
770
|
-
type: HTMLElement,
|
|
771
|
-
},
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
constructor() {
|
|
776
|
-
super();
|
|
777
|
-
|
|
778
|
-
this.__ariaModalController = new AriaModalController(this);
|
|
779
|
-
this.__focusTrapController = new FocusTrapController(this);
|
|
780
|
-
this.__focusRestorationController = new FocusRestorationController();
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/** @protected */
|
|
784
|
-
ready() {
|
|
785
|
-
super.ready();
|
|
786
|
-
|
|
787
|
-
this.addController(this.__ariaModalController);
|
|
788
|
-
this.addController(this.__focusTrapController);
|
|
789
|
-
this.addController(this.__focusRestorationController);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Release focus and restore focus after the overlay is closed.
|
|
794
|
-
*
|
|
795
|
-
* @protected
|
|
796
|
-
*/
|
|
797
|
-
_resetFocus() {
|
|
798
|
-
if (this.focusTrap) {
|
|
799
|
-
this.__ariaModalController.close();
|
|
800
|
-
this.__focusTrapController.releaseFocus();
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (this.restoreFocusOnClose && this._shouldRestoreFocus()) {
|
|
804
|
-
this.__focusRestorationController.restoreFocus();
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* Save the previously focused node when the overlay starts to open.
|
|
810
|
-
*
|
|
811
|
-
* @protected
|
|
812
|
-
*/
|
|
813
|
-
_saveFocus() {
|
|
814
|
-
if (this.restoreFocusOnClose) {
|
|
815
|
-
this.__focusRestorationController.saveFocus(this.restoreFocusNode);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Trap focus within the overlay after opening has completed.
|
|
821
|
-
*
|
|
822
|
-
* @protected
|
|
823
|
-
*/
|
|
824
|
-
_trapFocus() {
|
|
825
|
-
if (this.focusTrap) {
|
|
826
|
-
this.__ariaModalController.showModal();
|
|
827
|
-
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* Returns true if focus is still inside the overlay or on the body element,
|
|
833
|
-
* otherwise false.
|
|
834
|
-
*
|
|
835
|
-
* Focus shouldn't be restored if it's been moved elsewhere by another
|
|
836
|
-
* component or as a result of a user interaction e.g. the user clicked
|
|
837
|
-
* on a button outside the overlay while the overlay was open.
|
|
838
|
-
*
|
|
839
|
-
* @protected
|
|
840
|
-
* @return {boolean}
|
|
841
|
-
*/
|
|
842
|
-
_shouldRestoreFocus() {
|
|
843
|
-
const activeElement = getDeepActiveElement();
|
|
844
|
-
return activeElement === document.body || this._deepContains(activeElement);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Returns true if the overlay contains the given node,
|
|
849
|
-
* including those within shadow DOM trees.
|
|
850
|
-
*
|
|
851
|
-
* @param {Node} node
|
|
852
|
-
* @return {boolean}
|
|
853
|
-
* @protected
|
|
854
|
-
*/
|
|
855
|
-
_deepContains(node) {
|
|
856
|
-
if (this.contains(node)) {
|
|
857
|
-
return true;
|
|
858
|
-
}
|
|
859
|
-
let n = node;
|
|
860
|
-
const doc = node.ownerDocument;
|
|
861
|
-
// Walk from node to `this` or `document`
|
|
862
|
-
while (n && n !== doc && n !== this) {
|
|
863
|
-
n = n.parentNode || n.host;
|
|
864
|
-
}
|
|
865
|
-
return n === this;
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* @license
|
|
871
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
872
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
873
|
-
*/
|
|
874
|
-
|
|
875
|
-
/**
|
|
876
|
-
* Returns all attached overlays in visual stacking order.
|
|
877
|
-
* @private
|
|
878
|
-
*/
|
|
879
|
-
const getAttachedInstances = () =>
|
|
880
|
-
Array.from(document.body.children)
|
|
881
|
-
.filter((el) => el instanceof HTMLElement && el._hasOverlayStackMixin && !el.hasAttribute('closing'))
|
|
882
|
-
.sort((a, b) => a.__zIndex - b.__zIndex || 0);
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* Returns true if the overlay is the last one in the opened overlays stack.
|
|
886
|
-
* @param {HTMLElement} overlay
|
|
887
|
-
* @return {boolean}
|
|
888
|
-
* @protected
|
|
889
|
-
*/
|
|
890
|
-
const isLastOverlay = (overlay) => overlay === getAttachedInstances().pop();
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* @polymerMixin
|
|
894
|
-
*/
|
|
895
|
-
const OverlayStackMixin = (superClass) =>
|
|
896
|
-
class OverlayStackMixin extends superClass {
|
|
897
|
-
constructor() {
|
|
898
|
-
super();
|
|
899
|
-
|
|
900
|
-
this._hasOverlayStackMixin = true;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Returns true if this is the last one in the opened overlays stack.
|
|
905
|
-
*
|
|
906
|
-
* @return {boolean}
|
|
907
|
-
* @protected
|
|
908
|
-
*/
|
|
909
|
-
get _last() {
|
|
910
|
-
return isLastOverlay(this);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* Brings the overlay as visually the frontmost one.
|
|
915
|
-
*/
|
|
916
|
-
bringToFront() {
|
|
917
|
-
let zIndex = '';
|
|
918
|
-
const frontmost = getAttachedInstances()
|
|
919
|
-
.filter((o) => o !== this)
|
|
920
|
-
.pop();
|
|
921
|
-
if (frontmost) {
|
|
922
|
-
const frontmostZIndex = frontmost.__zIndex;
|
|
923
|
-
zIndex = frontmostZIndex + 1;
|
|
924
|
-
}
|
|
925
|
-
this.style.zIndex = zIndex;
|
|
926
|
-
this.__zIndex = zIndex || parseFloat(getComputedStyle(this).zIndex);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
/** @protected */
|
|
930
|
-
_enterModalState() {
|
|
931
|
-
if (document.body.style.pointerEvents !== 'none') {
|
|
932
|
-
// Set body pointer-events to 'none' to disable mouse interactions with
|
|
933
|
-
// other document nodes.
|
|
934
|
-
this._previousDocumentPointerEvents = document.body.style.pointerEvents;
|
|
935
|
-
document.body.style.pointerEvents = 'none';
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// Disable pointer events in other attached overlays
|
|
939
|
-
getAttachedInstances().forEach((el) => {
|
|
940
|
-
if (el !== this) {
|
|
941
|
-
el.$.overlay.style.pointerEvents = 'none';
|
|
942
|
-
}
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
/** @protected */
|
|
947
|
-
_exitModalState() {
|
|
948
|
-
if (this._previousDocumentPointerEvents !== undefined) {
|
|
949
|
-
// Restore body pointer-events
|
|
950
|
-
document.body.style.pointerEvents = this._previousDocumentPointerEvents;
|
|
951
|
-
delete this._previousDocumentPointerEvents;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Restore pointer events in the previous overlay(s)
|
|
955
|
-
const instances = getAttachedInstances();
|
|
956
|
-
|
|
957
|
-
let el;
|
|
958
|
-
// Use instances.pop() to ensure the reverse order
|
|
959
|
-
while ((el = instances.pop())) {
|
|
960
|
-
if (el === this) {
|
|
961
|
-
// Skip the current instance
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
el.$.overlay.style.removeProperty('pointer-events');
|
|
965
|
-
if (!el.modeless) {
|
|
966
|
-
// Stop after the last modal
|
|
967
|
-
break;
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* @license
|
|
975
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
976
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
977
|
-
*/
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* @polymerMixin
|
|
981
|
-
* @mixes OverlayFocusMixin
|
|
982
|
-
* @mixes OverlayStackMixin
|
|
983
|
-
*/
|
|
984
|
-
const OverlayMixin = (superClass) =>
|
|
985
|
-
class OverlayMixin extends OverlayFocusMixin(OverlayStackMixin(superClass)) {
|
|
986
|
-
static get properties() {
|
|
987
|
-
return {
|
|
988
|
-
/**
|
|
989
|
-
* When true, the overlay is visible and attached to body.
|
|
990
|
-
*/
|
|
991
|
-
opened: {
|
|
992
|
-
type: Boolean,
|
|
993
|
-
notify: true,
|
|
994
|
-
observer: '_openedChanged',
|
|
995
|
-
reflectToAttribute: true,
|
|
996
|
-
},
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
* Owner element passed with renderer function
|
|
1000
|
-
* @type {HTMLElement}
|
|
1001
|
-
*/
|
|
1002
|
-
owner: {
|
|
1003
|
-
type: Object,
|
|
1004
|
-
},
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Object with properties that is passed to `renderer` function
|
|
1008
|
-
*/
|
|
1009
|
-
model: {
|
|
1010
|
-
type: Object,
|
|
1011
|
-
},
|
|
1012
|
-
|
|
1013
|
-
/**
|
|
1014
|
-
* Custom function for rendering the content of the overlay.
|
|
1015
|
-
* Receives three arguments:
|
|
1016
|
-
*
|
|
1017
|
-
* - `root` The root container DOM element. Append your content to it.
|
|
1018
|
-
* - `owner` The host element of the renderer function.
|
|
1019
|
-
* - `model` The object with the properties related with rendering.
|
|
1020
|
-
* @type {OverlayRenderer | null | undefined}
|
|
1021
|
-
*/
|
|
1022
|
-
renderer: {
|
|
1023
|
-
type: Object,
|
|
1024
|
-
},
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* When true the overlay won't disable the main content, showing
|
|
1028
|
-
* it doesn't change the functionality of the user interface.
|
|
1029
|
-
* @type {boolean}
|
|
1030
|
-
*/
|
|
1031
|
-
modeless: {
|
|
1032
|
-
type: Boolean,
|
|
1033
|
-
value: false,
|
|
1034
|
-
reflectToAttribute: true,
|
|
1035
|
-
observer: '_modelessChanged',
|
|
1036
|
-
},
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* When set to true, the overlay is hidden. This also closes the overlay
|
|
1040
|
-
* immediately in case there is a closing animation in progress.
|
|
1041
|
-
* @type {boolean}
|
|
1042
|
-
*/
|
|
1043
|
-
hidden: {
|
|
1044
|
-
type: Boolean,
|
|
1045
|
-
reflectToAttribute: true,
|
|
1046
|
-
observer: '_hiddenChanged',
|
|
1047
|
-
},
|
|
1048
|
-
|
|
1049
|
-
/**
|
|
1050
|
-
* When true the overlay has backdrop on top of content when opened.
|
|
1051
|
-
* @type {boolean}
|
|
1052
|
-
*/
|
|
1053
|
-
withBackdrop: {
|
|
1054
|
-
type: Boolean,
|
|
1055
|
-
value: false,
|
|
1056
|
-
reflectToAttribute: true,
|
|
1057
|
-
},
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
static get observers() {
|
|
1062
|
-
return ['_rendererOrDataChanged(renderer, owner, model, opened)'];
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
constructor() {
|
|
1066
|
-
super();
|
|
1067
|
-
|
|
1068
|
-
this._boundMouseDownListener = this._mouseDownListener.bind(this);
|
|
1069
|
-
this._boundMouseUpListener = this._mouseUpListener.bind(this);
|
|
1070
|
-
this._boundOutsideClickListener = this._outsideClickListener.bind(this);
|
|
1071
|
-
this._boundKeydownListener = this._keydownListener.bind(this);
|
|
1072
|
-
|
|
1073
|
-
/* c8 ignore next 3 */
|
|
1074
|
-
if (isIOS) {
|
|
1075
|
-
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
/** @protected */
|
|
1080
|
-
ready() {
|
|
1081
|
-
super.ready();
|
|
1082
|
-
|
|
1083
|
-
// Need to add dummy click listeners to this and the backdrop or else
|
|
1084
|
-
// the document click event listener (_outsideClickListener) may never
|
|
1085
|
-
// get invoked on iOS Safari (reproducible in <vaadin-dialog>
|
|
1086
|
-
// and <vaadin-context-menu>).
|
|
1087
|
-
this.addEventListener('click', () => {});
|
|
1088
|
-
this.$.backdrop.addEventListener('click', () => {});
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
/** @protected */
|
|
1092
|
-
connectedCallback() {
|
|
1093
|
-
super.connectedCallback();
|
|
1094
|
-
|
|
1095
|
-
/* c8 ignore next 3 */
|
|
1096
|
-
if (this._boundIosResizeListener) {
|
|
1097
|
-
this._detectIosNavbar();
|
|
1098
|
-
window.addEventListener('resize', this._boundIosResizeListener);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
/** @protected */
|
|
1103
|
-
disconnectedCallback() {
|
|
1104
|
-
super.disconnectedCallback();
|
|
1105
|
-
|
|
1106
|
-
/* c8 ignore next 3 */
|
|
1107
|
-
if (this._boundIosResizeListener) {
|
|
1108
|
-
window.removeEventListener('resize', this._boundIosResizeListener);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
/**
|
|
1113
|
-
* Requests an update for the content of the overlay.
|
|
1114
|
-
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
1115
|
-
*
|
|
1116
|
-
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
1117
|
-
*/
|
|
1118
|
-
requestContentUpdate() {
|
|
1119
|
-
if (this.renderer) {
|
|
1120
|
-
this.renderer.call(this.owner, this, this.owner, this.model);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
/**
|
|
1125
|
-
* @param {Event=} sourceEvent
|
|
1126
|
-
*/
|
|
1127
|
-
close(sourceEvent) {
|
|
1128
|
-
const evt = new CustomEvent('vaadin-overlay-close', {
|
|
1129
|
-
bubbles: true,
|
|
1130
|
-
cancelable: true,
|
|
1131
|
-
detail: { sourceEvent },
|
|
1132
|
-
});
|
|
1133
|
-
this.dispatchEvent(evt);
|
|
1134
|
-
if (!evt.defaultPrevented) {
|
|
1135
|
-
this.opened = false;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
/** @private */
|
|
1140
|
-
_detectIosNavbar() {
|
|
1141
|
-
/* c8 ignore next 15 */
|
|
1142
|
-
if (!this.opened) {
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
const innerHeight = window.innerHeight;
|
|
1147
|
-
const innerWidth = window.innerWidth;
|
|
1148
|
-
|
|
1149
|
-
const landscape = innerWidth > innerHeight;
|
|
1150
|
-
|
|
1151
|
-
const clientHeight = document.documentElement.clientHeight;
|
|
1152
|
-
|
|
1153
|
-
if (landscape && clientHeight > innerHeight) {
|
|
1154
|
-
this.style.setProperty('--vaadin-overlay-viewport-bottom', `${clientHeight - innerHeight}px`);
|
|
1155
|
-
} else {
|
|
1156
|
-
this.style.setProperty('--vaadin-overlay-viewport-bottom', '0');
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
/** @private */
|
|
1161
|
-
_addGlobalListeners() {
|
|
1162
|
-
document.addEventListener('mousedown', this._boundMouseDownListener);
|
|
1163
|
-
document.addEventListener('mouseup', this._boundMouseUpListener);
|
|
1164
|
-
// Firefox leaks click to document on contextmenu even if prevented
|
|
1165
|
-
// https://bugzilla.mozilla.org/show_bug.cgi?id=990614
|
|
1166
|
-
document.documentElement.addEventListener('click', this._boundOutsideClickListener, true);
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
/** @private */
|
|
1170
|
-
_removeGlobalListeners() {
|
|
1171
|
-
document.removeEventListener('mousedown', this._boundMouseDownListener);
|
|
1172
|
-
document.removeEventListener('mouseup', this._boundMouseUpListener);
|
|
1173
|
-
document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/** @private */
|
|
1177
|
-
_rendererOrDataChanged(renderer, owner, model, opened) {
|
|
1178
|
-
const ownerOrModelChanged = this._oldOwner !== owner || this._oldModel !== model;
|
|
1179
|
-
this._oldModel = model;
|
|
1180
|
-
this._oldOwner = owner;
|
|
1181
|
-
|
|
1182
|
-
const rendererChanged = this._oldRenderer !== renderer;
|
|
1183
|
-
this._oldRenderer = renderer;
|
|
1184
|
-
|
|
1185
|
-
const openedChanged = this._oldOpened !== opened;
|
|
1186
|
-
this._oldOpened = opened;
|
|
1187
|
-
|
|
1188
|
-
if (rendererChanged) {
|
|
1189
|
-
this.innerHTML = '';
|
|
1190
|
-
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
1191
|
-
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
1192
|
-
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
1193
|
-
delete this._$litPart$;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
1197
|
-
this.requestContentUpdate();
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
/** @private */
|
|
1202
|
-
_modelessChanged(modeless) {
|
|
1203
|
-
if (!modeless) {
|
|
1204
|
-
if (this.opened) {
|
|
1205
|
-
this._addGlobalListeners();
|
|
1206
|
-
this._enterModalState();
|
|
1207
|
-
}
|
|
1208
|
-
} else {
|
|
1209
|
-
this._removeGlobalListeners();
|
|
1210
|
-
this._exitModalState();
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/** @private */
|
|
1215
|
-
_openedChanged(opened, wasOpened) {
|
|
1216
|
-
if (opened) {
|
|
1217
|
-
this._saveFocus();
|
|
1218
|
-
|
|
1219
|
-
this._animatedOpening();
|
|
1220
|
-
|
|
1221
|
-
afterNextRender(this, () => {
|
|
1222
|
-
this._trapFocus();
|
|
1223
|
-
|
|
1224
|
-
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
1225
|
-
this.dispatchEvent(evt);
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
document.addEventListener('keydown', this._boundKeydownListener);
|
|
1229
|
-
|
|
1230
|
-
if (!this.modeless) {
|
|
1231
|
-
this._addGlobalListeners();
|
|
1232
|
-
}
|
|
1233
|
-
} else if (wasOpened) {
|
|
1234
|
-
this._resetFocus();
|
|
1235
|
-
|
|
1236
|
-
this._animatedClosing();
|
|
1237
|
-
|
|
1238
|
-
document.removeEventListener('keydown', this._boundKeydownListener);
|
|
1239
|
-
|
|
1240
|
-
if (!this.modeless) {
|
|
1241
|
-
this._removeGlobalListeners();
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
/** @private */
|
|
1247
|
-
_hiddenChanged(hidden) {
|
|
1248
|
-
if (hidden && this.hasAttribute('closing')) {
|
|
1249
|
-
this._flushAnimation('closing');
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* @return {boolean}
|
|
1255
|
-
* @private
|
|
1256
|
-
*/
|
|
1257
|
-
_shouldAnimate() {
|
|
1258
|
-
const style = getComputedStyle(this);
|
|
1259
|
-
const name = style.getPropertyValue('animation-name');
|
|
1260
|
-
const hidden = style.getPropertyValue('display') === 'none';
|
|
1261
|
-
return !hidden && name && name !== 'none';
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* @param {string} type
|
|
1266
|
-
* @param {Function} callback
|
|
1267
|
-
* @private
|
|
1268
|
-
*/
|
|
1269
|
-
_enqueueAnimation(type, callback) {
|
|
1270
|
-
const handler = `__${type}Handler`;
|
|
1271
|
-
const listener = (event) => {
|
|
1272
|
-
if (event && event.target !== this) {
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
callback();
|
|
1276
|
-
this.removeEventListener('animationend', listener);
|
|
1277
|
-
delete this[handler];
|
|
1278
|
-
};
|
|
1279
|
-
this[handler] = listener;
|
|
1280
|
-
this.addEventListener('animationend', listener);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
/**
|
|
1284
|
-
* @param {string} type
|
|
1285
|
-
* @protected
|
|
1286
|
-
*/
|
|
1287
|
-
_flushAnimation(type) {
|
|
1288
|
-
const handler = `__${type}Handler`;
|
|
1289
|
-
if (typeof this[handler] === 'function') {
|
|
1290
|
-
this[handler]();
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
/** @private */
|
|
1295
|
-
_animatedOpening() {
|
|
1296
|
-
if (this.parentNode === document.body && this.hasAttribute('closing')) {
|
|
1297
|
-
this._flushAnimation('closing');
|
|
1298
|
-
}
|
|
1299
|
-
this._attachOverlay();
|
|
1300
|
-
if (!this.modeless) {
|
|
1301
|
-
this._enterModalState();
|
|
1302
|
-
}
|
|
1303
|
-
this.setAttribute('opening', '');
|
|
1304
|
-
|
|
1305
|
-
if (this._shouldAnimate()) {
|
|
1306
|
-
this._enqueueAnimation('opening', () => {
|
|
1307
|
-
this._finishOpening();
|
|
1308
|
-
});
|
|
1309
|
-
} else {
|
|
1310
|
-
this._finishOpening();
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
/** @private */
|
|
1315
|
-
_attachOverlay() {
|
|
1316
|
-
this._placeholder = document.createComment('vaadin-overlay-placeholder');
|
|
1317
|
-
this.parentNode.insertBefore(this._placeholder, this);
|
|
1318
|
-
document.body.appendChild(this);
|
|
1319
|
-
this.bringToFront();
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/** @private */
|
|
1323
|
-
_finishOpening() {
|
|
1324
|
-
this.removeAttribute('opening');
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
/** @private */
|
|
1328
|
-
_finishClosing() {
|
|
1329
|
-
this._detachOverlay();
|
|
1330
|
-
this.$.overlay.style.removeProperty('pointer-events');
|
|
1331
|
-
this.removeAttribute('closing');
|
|
1332
|
-
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
/** @private */
|
|
1336
|
-
_animatedClosing() {
|
|
1337
|
-
if (this.hasAttribute('opening')) {
|
|
1338
|
-
this._flushAnimation('opening');
|
|
1339
|
-
}
|
|
1340
|
-
if (this._placeholder) {
|
|
1341
|
-
this._exitModalState();
|
|
1342
|
-
this.setAttribute('closing', '');
|
|
1343
|
-
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
1344
|
-
|
|
1345
|
-
if (this._shouldAnimate()) {
|
|
1346
|
-
this._enqueueAnimation('closing', () => {
|
|
1347
|
-
this._finishClosing();
|
|
1348
|
-
});
|
|
1349
|
-
} else {
|
|
1350
|
-
this._finishClosing();
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
/** @private */
|
|
1356
|
-
_detachOverlay() {
|
|
1357
|
-
this._placeholder.parentNode.insertBefore(this, this._placeholder);
|
|
1358
|
-
this._placeholder.parentNode.removeChild(this._placeholder);
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
/** @private */
|
|
1362
|
-
_mouseDownListener(event) {
|
|
1363
|
-
this._mouseDownInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
/** @private */
|
|
1367
|
-
_mouseUpListener(event) {
|
|
1368
|
-
this._mouseUpInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
/**
|
|
1372
|
-
* Whether to close the overlay on outside click or not.
|
|
1373
|
-
* Override this method to customize the closing logic.
|
|
1374
|
-
*
|
|
1375
|
-
* @param {Event} _event
|
|
1376
|
-
* @return {boolean}
|
|
1377
|
-
* @protected
|
|
1378
|
-
*/
|
|
1379
|
-
_shouldCloseOnOutsideClick(_event) {
|
|
1380
|
-
return this._last;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/**
|
|
1384
|
-
* Outside click listener used in capture phase to close the overlay before
|
|
1385
|
-
* propagating the event to the listener on the element that triggered it.
|
|
1386
|
-
* Otherwise, calling `open()` would result in closing and re-opening.
|
|
1387
|
-
*
|
|
1388
|
-
* @private
|
|
1389
|
-
*/
|
|
1390
|
-
_outsideClickListener(event) {
|
|
1391
|
-
if (event.composedPath().includes(this.$.overlay) || this._mouseDownInside || this._mouseUpInside) {
|
|
1392
|
-
this._mouseDownInside = false;
|
|
1393
|
-
this._mouseUpInside = false;
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
if (!this._shouldCloseOnOutsideClick(event)) {
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
const evt = new CustomEvent('vaadin-overlay-outside-click', {
|
|
1402
|
-
bubbles: true,
|
|
1403
|
-
cancelable: true,
|
|
1404
|
-
detail: { sourceEvent: event },
|
|
1405
|
-
});
|
|
1406
|
-
this.dispatchEvent(evt);
|
|
1407
|
-
|
|
1408
|
-
if (this.opened && !evt.defaultPrevented) {
|
|
1409
|
-
this.close(event);
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
/**
|
|
1414
|
-
* Listener used to close whe overlay on Escape press, if it is the last one.
|
|
1415
|
-
* @private
|
|
1416
|
-
*/
|
|
1417
|
-
_keydownListener(event) {
|
|
1418
|
-
if (!this._last) {
|
|
1419
|
-
return;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// Only close modeless overlay on Esc press when it contains focus
|
|
1423
|
-
if (this.modeless && !event.composedPath().includes(this.$.overlay)) {
|
|
1424
|
-
return;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
if (event.key === 'Escape') {
|
|
1428
|
-
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
1429
|
-
bubbles: true,
|
|
1430
|
-
cancelable: true,
|
|
1431
|
-
detail: { sourceEvent: event },
|
|
1432
|
-
});
|
|
1433
|
-
this.dispatchEvent(evt);
|
|
1434
|
-
|
|
1435
|
-
if (this.opened && !evt.defaultPrevented) {
|
|
1436
|
-
this.close(event);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1441
|
-
|
|
1442
|
-
/**
|
|
1443
|
-
* @license
|
|
1444
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
1445
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1446
|
-
*/
|
|
1447
|
-
|
|
1448
|
-
const PROP_NAMES_VERTICAL = {
|
|
1449
|
-
start: 'top',
|
|
1450
|
-
end: 'bottom',
|
|
1451
|
-
};
|
|
1452
|
-
|
|
1453
|
-
const PROP_NAMES_HORIZONTAL = {
|
|
1454
|
-
start: 'left',
|
|
1455
|
-
end: 'right',
|
|
1456
|
-
};
|
|
1457
|
-
|
|
1458
|
-
const targetResizeObserver = new ResizeObserver((entries) => {
|
|
1459
|
-
setTimeout(() => {
|
|
1460
|
-
entries.forEach((entry) => {
|
|
1461
|
-
if (entry.target.__overlay) {
|
|
1462
|
-
entry.target.__overlay._updatePosition();
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1465
|
-
});
|
|
1466
|
-
});
|
|
1467
|
-
|
|
1468
|
-
/**
|
|
1469
|
-
* @polymerMixin
|
|
1470
|
-
*/
|
|
1471
|
-
const PositionMixin = (superClass) =>
|
|
1472
|
-
class PositionMixin extends superClass {
|
|
1473
|
-
static get properties() {
|
|
1474
|
-
return {
|
|
1475
|
-
/**
|
|
1476
|
-
* The element next to which this overlay should be aligned.
|
|
1477
|
-
* The position of the overlay relative to the positionTarget can be adjusted
|
|
1478
|
-
* with properties `horizontalAlign`, `verticalAlign`, `noHorizontalOverlap`
|
|
1479
|
-
* and `noVerticalOverlap`.
|
|
1480
|
-
*/
|
|
1481
|
-
positionTarget: {
|
|
1482
|
-
type: Object,
|
|
1483
|
-
value: null,
|
|
1484
|
-
},
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* When `positionTarget` is set, this property defines whether to align the overlay's
|
|
1488
|
-
* left or right side to the target element by default.
|
|
1489
|
-
* Possible values are `start` and `end`.
|
|
1490
|
-
* RTL is taken into account when interpreting the value.
|
|
1491
|
-
* The overlay is automatically flipped to the opposite side when it doesn't fit into
|
|
1492
|
-
* the default side defined by this property.
|
|
1493
|
-
*
|
|
1494
|
-
* @attr {start|end} horizontal-align
|
|
1495
|
-
*/
|
|
1496
|
-
horizontalAlign: {
|
|
1497
|
-
type: String,
|
|
1498
|
-
value: 'start',
|
|
1499
|
-
},
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* When `positionTarget` is set, this property defines whether to align the overlay's
|
|
1503
|
-
* top or bottom side to the target element by default.
|
|
1504
|
-
* Possible values are `top` and `bottom`.
|
|
1505
|
-
* The overlay is automatically flipped to the opposite side when it doesn't fit into
|
|
1506
|
-
* the default side defined by this property.
|
|
1507
|
-
*
|
|
1508
|
-
* @attr {top|bottom} vertical-align
|
|
1509
|
-
*/
|
|
1510
|
-
verticalAlign: {
|
|
1511
|
-
type: String,
|
|
1512
|
-
value: 'top',
|
|
1513
|
-
},
|
|
1514
|
-
|
|
1515
|
-
/**
|
|
1516
|
-
* When `positionTarget` is set, this property defines whether the overlay should overlap
|
|
1517
|
-
* the target element in the x-axis, or be positioned right next to it.
|
|
1518
|
-
*
|
|
1519
|
-
* @attr {boolean} no-horizontal-overlap
|
|
1520
|
-
*/
|
|
1521
|
-
noHorizontalOverlap: {
|
|
1522
|
-
type: Boolean,
|
|
1523
|
-
value: false,
|
|
1524
|
-
},
|
|
1525
|
-
|
|
1526
|
-
/**
|
|
1527
|
-
* When `positionTarget` is set, this property defines whether the overlay should overlap
|
|
1528
|
-
* the target element in the y-axis, or be positioned right above/below it.
|
|
1529
|
-
*
|
|
1530
|
-
* @attr {boolean} no-vertical-overlap
|
|
1531
|
-
*/
|
|
1532
|
-
noVerticalOverlap: {
|
|
1533
|
-
type: Boolean,
|
|
1534
|
-
value: false,
|
|
1535
|
-
},
|
|
1536
|
-
|
|
1537
|
-
/**
|
|
1538
|
-
* If the overlay content has no intrinsic height, this property can be used to set
|
|
1539
|
-
* the minimum vertical space (in pixels) required by the overlay. Setting a value to
|
|
1540
|
-
* the property effectively disables the content measurement in favor of using this
|
|
1541
|
-
* fixed value for determining the open direction.
|
|
1542
|
-
*
|
|
1543
|
-
* @attr {number} required-vertical-space
|
|
1544
|
-
*/
|
|
1545
|
-
requiredVerticalSpace: {
|
|
1546
|
-
type: Number,
|
|
1547
|
-
value: 0,
|
|
1548
|
-
},
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
static get observers() {
|
|
1553
|
-
return [
|
|
1554
|
-
'__positionSettingsChanged(horizontalAlign, verticalAlign, noHorizontalOverlap, noVerticalOverlap, requiredVerticalSpace)',
|
|
1555
|
-
'__overlayOpenedChanged(opened, positionTarget)',
|
|
1556
|
-
];
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
constructor() {
|
|
1560
|
-
super();
|
|
1561
|
-
|
|
1562
|
-
this.__onScroll = this.__onScroll.bind(this);
|
|
1563
|
-
this._updatePosition = this._updatePosition.bind(this);
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
/** @protected */
|
|
1567
|
-
connectedCallback() {
|
|
1568
|
-
super.connectedCallback();
|
|
1569
|
-
|
|
1570
|
-
if (this.opened) {
|
|
1571
|
-
this.__addUpdatePositionEventListeners();
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
/** @protected */
|
|
1576
|
-
disconnectedCallback() {
|
|
1577
|
-
super.disconnectedCallback();
|
|
1578
|
-
this.__removeUpdatePositionEventListeners();
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
/** @private */
|
|
1582
|
-
__addUpdatePositionEventListeners() {
|
|
1583
|
-
window.addEventListener('resize', this._updatePosition);
|
|
1584
|
-
|
|
1585
|
-
this.__positionTargetAncestorRootNodes = getAncestorRootNodes(this.positionTarget);
|
|
1586
|
-
this.__positionTargetAncestorRootNodes.forEach((node) => {
|
|
1587
|
-
node.addEventListener('scroll', this.__onScroll, true);
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
/** @private */
|
|
1592
|
-
__removeUpdatePositionEventListeners() {
|
|
1593
|
-
window.removeEventListener('resize', this._updatePosition);
|
|
1594
|
-
|
|
1595
|
-
if (this.__positionTargetAncestorRootNodes) {
|
|
1596
|
-
this.__positionTargetAncestorRootNodes.forEach((node) => {
|
|
1597
|
-
node.removeEventListener('scroll', this.__onScroll, true);
|
|
1598
|
-
});
|
|
1599
|
-
this.__positionTargetAncestorRootNodes = null;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
/** @private */
|
|
1604
|
-
__overlayOpenedChanged(opened, positionTarget) {
|
|
1605
|
-
this.__removeUpdatePositionEventListeners();
|
|
1606
|
-
|
|
1607
|
-
if (positionTarget) {
|
|
1608
|
-
positionTarget.__overlay = null;
|
|
1609
|
-
targetResizeObserver.unobserve(positionTarget);
|
|
1610
|
-
|
|
1611
|
-
if (opened) {
|
|
1612
|
-
this.__addUpdatePositionEventListeners();
|
|
1613
|
-
positionTarget.__overlay = this;
|
|
1614
|
-
targetResizeObserver.observe(positionTarget);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
if (opened) {
|
|
1619
|
-
const computedStyle = getComputedStyle(this);
|
|
1620
|
-
if (!this.__margins) {
|
|
1621
|
-
this.__margins = {};
|
|
1622
|
-
['top', 'bottom', 'left', 'right'].forEach((propName) => {
|
|
1623
|
-
this.__margins[propName] = parseInt(computedStyle[propName], 10);
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
this.setAttribute('dir', computedStyle.direction);
|
|
1627
|
-
|
|
1628
|
-
this._updatePosition();
|
|
1629
|
-
// Schedule another position update (to cover virtual keyboard opening for example)
|
|
1630
|
-
requestAnimationFrame(() => this._updatePosition());
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
__positionSettingsChanged() {
|
|
1635
|
-
this._updatePosition();
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
/** @private */
|
|
1639
|
-
__onScroll(e) {
|
|
1640
|
-
// If the scroll event occurred inside the overlay, ignore it.
|
|
1641
|
-
if (!this.contains(e.target)) {
|
|
1642
|
-
this._updatePosition();
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
_updatePosition() {
|
|
1647
|
-
if (!this.positionTarget || !this.opened) {
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
const targetRect = this.positionTarget.getBoundingClientRect();
|
|
1652
|
-
|
|
1653
|
-
// Detect the desired alignment and update the layout accordingly
|
|
1654
|
-
const shouldAlignStartVertically = this.__shouldAlignStartVertically(targetRect);
|
|
1655
|
-
this.style.justifyContent = shouldAlignStartVertically ? 'flex-start' : 'flex-end';
|
|
1656
|
-
|
|
1657
|
-
const isRTL = this.__isRTL;
|
|
1658
|
-
const shouldAlignStartHorizontally = this.__shouldAlignStartHorizontally(targetRect, isRTL);
|
|
1659
|
-
const flexStart = (!isRTL && shouldAlignStartHorizontally) || (isRTL && !shouldAlignStartHorizontally);
|
|
1660
|
-
this.style.alignItems = flexStart ? 'flex-start' : 'flex-end';
|
|
1661
|
-
|
|
1662
|
-
// Get the overlay rect after possible overlay alignment changes
|
|
1663
|
-
const overlayRect = this.getBoundingClientRect();
|
|
1664
|
-
|
|
1665
|
-
// Obtain vertical positioning properties
|
|
1666
|
-
const verticalProps = this.__calculatePositionInOneDimension(
|
|
1667
|
-
targetRect,
|
|
1668
|
-
overlayRect,
|
|
1669
|
-
this.noVerticalOverlap,
|
|
1670
|
-
PROP_NAMES_VERTICAL,
|
|
1671
|
-
this,
|
|
1672
|
-
shouldAlignStartVertically,
|
|
1673
|
-
);
|
|
1674
|
-
|
|
1675
|
-
// Obtain horizontal positioning properties
|
|
1676
|
-
const horizontalProps = this.__calculatePositionInOneDimension(
|
|
1677
|
-
targetRect,
|
|
1678
|
-
overlayRect,
|
|
1679
|
-
this.noHorizontalOverlap,
|
|
1680
|
-
PROP_NAMES_HORIZONTAL,
|
|
1681
|
-
this,
|
|
1682
|
-
shouldAlignStartHorizontally,
|
|
1683
|
-
);
|
|
1684
|
-
|
|
1685
|
-
// Apply the positioning properties to the overlay
|
|
1686
|
-
Object.assign(this.style, verticalProps, horizontalProps);
|
|
1687
|
-
|
|
1688
|
-
this.toggleAttribute('bottom-aligned', !shouldAlignStartVertically);
|
|
1689
|
-
this.toggleAttribute('top-aligned', shouldAlignStartVertically);
|
|
1690
|
-
|
|
1691
|
-
this.toggleAttribute('end-aligned', !flexStart);
|
|
1692
|
-
this.toggleAttribute('start-aligned', flexStart);
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
__shouldAlignStartHorizontally(targetRect, rtl) {
|
|
1696
|
-
// Using previous size to fix a case where window resize may cause the overlay to be squeezed
|
|
1697
|
-
// smaller than its current space before the fit-calculations.
|
|
1698
|
-
const contentWidth = Math.max(this.__oldContentWidth || 0, this.$.overlay.offsetWidth);
|
|
1699
|
-
this.__oldContentWidth = this.$.overlay.offsetWidth;
|
|
1700
|
-
|
|
1701
|
-
const viewportWidth = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1702
|
-
const defaultAlignLeft = (!rtl && this.horizontalAlign === 'start') || (rtl && this.horizontalAlign === 'end');
|
|
1703
|
-
|
|
1704
|
-
return this.__shouldAlignStart(
|
|
1705
|
-
targetRect,
|
|
1706
|
-
contentWidth,
|
|
1707
|
-
viewportWidth,
|
|
1708
|
-
this.__margins,
|
|
1709
|
-
defaultAlignLeft,
|
|
1710
|
-
this.noHorizontalOverlap,
|
|
1711
|
-
PROP_NAMES_HORIZONTAL,
|
|
1712
|
-
);
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
__shouldAlignStartVertically(targetRect) {
|
|
1716
|
-
// Using previous size to fix a case where window resize may cause the overlay to be squeezed
|
|
1717
|
-
// smaller than its current space before the fit-calculations.
|
|
1718
|
-
const contentHeight =
|
|
1719
|
-
this.requiredVerticalSpace || Math.max(this.__oldContentHeight || 0, this.$.overlay.offsetHeight);
|
|
1720
|
-
this.__oldContentHeight = this.$.overlay.offsetHeight;
|
|
1721
|
-
|
|
1722
|
-
const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight);
|
|
1723
|
-
const defaultAlignTop = this.verticalAlign === 'top';
|
|
1724
|
-
|
|
1725
|
-
return this.__shouldAlignStart(
|
|
1726
|
-
targetRect,
|
|
1727
|
-
contentHeight,
|
|
1728
|
-
viewportHeight,
|
|
1729
|
-
this.__margins,
|
|
1730
|
-
defaultAlignTop,
|
|
1731
|
-
this.noVerticalOverlap,
|
|
1732
|
-
PROP_NAMES_VERTICAL,
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
// eslint-disable-next-line max-params
|
|
1737
|
-
__shouldAlignStart(targetRect, contentSize, viewportSize, margins, defaultAlignStart, noOverlap, propNames) {
|
|
1738
|
-
const spaceForStartAlignment =
|
|
1739
|
-
viewportSize - targetRect[noOverlap ? propNames.end : propNames.start] - margins[propNames.end];
|
|
1740
|
-
const spaceForEndAlignment = targetRect[noOverlap ? propNames.start : propNames.end] - margins[propNames.start];
|
|
1741
|
-
|
|
1742
|
-
const spaceForDefaultAlignment = defaultAlignStart ? spaceForStartAlignment : spaceForEndAlignment;
|
|
1743
|
-
const spaceForOtherAlignment = defaultAlignStart ? spaceForEndAlignment : spaceForStartAlignment;
|
|
1744
|
-
|
|
1745
|
-
const shouldGoToDefaultSide =
|
|
1746
|
-
spaceForDefaultAlignment > spaceForOtherAlignment || spaceForDefaultAlignment > contentSize;
|
|
1747
|
-
|
|
1748
|
-
return defaultAlignStart === shouldGoToDefaultSide;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
/**
|
|
1752
|
-
* Returns an adjusted value after resizing the browser window,
|
|
1753
|
-
* to avoid wrong calculations when e.g. previously set `bottom`
|
|
1754
|
-
* CSS property value is larger than the updated viewport height.
|
|
1755
|
-
* See https://github.com/vaadin/web-components/issues/4604
|
|
1756
|
-
*/
|
|
1757
|
-
__adjustBottomProperty(cssPropNameToSet, propNames, currentValue) {
|
|
1758
|
-
let adjustedProp;
|
|
1759
|
-
|
|
1760
|
-
if (cssPropNameToSet === propNames.end) {
|
|
1761
|
-
// Adjust horizontally
|
|
1762
|
-
if (propNames.end === PROP_NAMES_VERTICAL.end) {
|
|
1763
|
-
const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight);
|
|
1764
|
-
|
|
1765
|
-
if (currentValue > viewportHeight && this.__oldViewportHeight) {
|
|
1766
|
-
const heightDiff = this.__oldViewportHeight - viewportHeight;
|
|
1767
|
-
adjustedProp = currentValue - heightDiff;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
this.__oldViewportHeight = viewportHeight;
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
// Adjust vertically
|
|
1774
|
-
if (propNames.end === PROP_NAMES_HORIZONTAL.end) {
|
|
1775
|
-
const viewportWidth = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1776
|
-
|
|
1777
|
-
if (currentValue > viewportWidth && this.__oldViewportWidth) {
|
|
1778
|
-
const widthDiff = this.__oldViewportWidth - viewportWidth;
|
|
1779
|
-
adjustedProp = currentValue - widthDiff;
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
this.__oldViewportWidth = viewportWidth;
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
return adjustedProp;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
/**
|
|
1790
|
-
* Returns an object with CSS position properties to set,
|
|
1791
|
-
* e.g. { top: "100px" }
|
|
1792
|
-
*/
|
|
1793
|
-
// eslint-disable-next-line max-params
|
|
1794
|
-
__calculatePositionInOneDimension(targetRect, overlayRect, noOverlap, propNames, overlay, shouldAlignStart) {
|
|
1795
|
-
const cssPropNameToSet = shouldAlignStart ? propNames.start : propNames.end;
|
|
1796
|
-
const cssPropNameToClear = shouldAlignStart ? propNames.end : propNames.start;
|
|
1797
|
-
|
|
1798
|
-
const currentValue = parseFloat(overlay.style[cssPropNameToSet] || getComputedStyle(overlay)[cssPropNameToSet]);
|
|
1799
|
-
const adjustedValue = this.__adjustBottomProperty(cssPropNameToSet, propNames, currentValue);
|
|
1800
|
-
|
|
1801
|
-
const diff =
|
|
1802
|
-
overlayRect[shouldAlignStart ? propNames.start : propNames.end] -
|
|
1803
|
-
targetRect[noOverlap === shouldAlignStart ? propNames.end : propNames.start];
|
|
1804
|
-
|
|
1805
|
-
const valueToSet = adjustedValue
|
|
1806
|
-
? `${adjustedValue}px`
|
|
1807
|
-
: `${currentValue + diff * (shouldAlignStart ? -1 : 1)}px`;
|
|
1808
|
-
|
|
1809
|
-
return {
|
|
1810
|
-
[cssPropNameToSet]: valueToSet,
|
|
1811
|
-
[cssPropNameToClear]: '',
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
};
|
|
1815
|
-
|
|
1816
|
-
/**
|
|
1817
|
-
* @license
|
|
1818
|
-
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
1819
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1820
|
-
*/
|
|
1821
|
-
|
|
1822
|
-
const overlayStyles = i`
|
|
1823
|
-
:host {
|
|
1824
|
-
z-index: 200;
|
|
1825
|
-
position: fixed;
|
|
1826
|
-
|
|
1827
|
-
/* Despite of what the names say, <vaadin-overlay> is just a container
|
|
1828
|
-
for position/sizing/alignment. The actual overlay is the overlay part. */
|
|
1829
|
-
|
|
1830
|
-
/* Default position constraints: the entire viewport. Note: themes can
|
|
1831
|
-
override this to introduce gaps between the overlay and the viewport. */
|
|
1832
|
-
inset: 0;
|
|
1833
|
-
bottom: var(--vaadin-overlay-viewport-bottom);
|
|
1834
|
-
|
|
1835
|
-
/* Use flexbox alignment for the overlay part. */
|
|
1836
|
-
display: flex;
|
|
1837
|
-
flex-direction: column; /* makes dropdowns sizing easier */
|
|
1838
|
-
/* Align to center by default. */
|
|
1839
|
-
align-items: center;
|
|
1840
|
-
justify-content: center;
|
|
1841
|
-
|
|
1842
|
-
/* Allow centering when max-width/max-height applies. */
|
|
1843
|
-
margin: auto;
|
|
1844
|
-
|
|
1845
|
-
/* The host is not clickable, only the overlay part is. */
|
|
1846
|
-
pointer-events: none;
|
|
1847
|
-
|
|
1848
|
-
/* Remove tap highlight on touch devices. */
|
|
1849
|
-
-webkit-tap-highlight-color: transparent;
|
|
1850
|
-
|
|
1851
|
-
/* CSS API for host */
|
|
1852
|
-
--vaadin-overlay-viewport-bottom: 0;
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
:host([hidden]),
|
|
1856
|
-
:host(:not([opened]):not([closing])) {
|
|
1857
|
-
display: none !important;
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
[part='overlay'] {
|
|
1861
|
-
-webkit-overflow-scrolling: touch;
|
|
1862
|
-
overflow: auto;
|
|
1863
|
-
pointer-events: auto;
|
|
1864
|
-
|
|
1865
|
-
/* Prevent overflowing the host */
|
|
1866
|
-
max-width: 100%;
|
|
1867
|
-
box-sizing: border-box;
|
|
1868
|
-
|
|
1869
|
-
-webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
[part='backdrop'] {
|
|
1873
|
-
z-index: -1;
|
|
1874
|
-
content: '';
|
|
1875
|
-
background: rgba(0, 0, 0, 0.5);
|
|
1876
|
-
position: fixed;
|
|
1877
|
-
inset: 0;
|
|
1878
|
-
pointer-events: auto;
|
|
1879
|
-
}
|
|
1880
|
-
`;
|
|
1881
|
-
|
|
1882
|
-
/**
|
|
1883
|
-
* @license
|
|
1884
|
-
* Copyright (c) 2023 Vaadin Ltd.
|
|
1885
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1886
|
-
*/
|
|
1887
|
-
|
|
1888
|
-
/**
|
|
1889
|
-
* A mixin that forwards CSS class names to the internal overlay element
|
|
1890
|
-
* by setting the `overlayClass` property or `overlay-class` attribute.
|
|
1891
|
-
*
|
|
1892
|
-
* @polymerMixin
|
|
1893
|
-
*/
|
|
1894
|
-
const OverlayClassMixin = (superclass) =>
|
|
1895
|
-
class OverlayClassMixinClass extends superclass {
|
|
1896
|
-
static get properties() {
|
|
1897
|
-
return {
|
|
1898
|
-
/**
|
|
1899
|
-
* A space-delimited list of CSS class names to set on the overlay element.
|
|
1900
|
-
* This property does not affect other CSS class names set manually via JS.
|
|
1901
|
-
*
|
|
1902
|
-
* Note, if the CSS class name was set with this property, clearing it will
|
|
1903
|
-
* remove it from the overlay, even if the same class name was also added
|
|
1904
|
-
* manually, e.g. by using `classList.add()` in the `renderer` function.
|
|
1905
|
-
*
|
|
1906
|
-
* @attr {string} overlay-class
|
|
1907
|
-
*/
|
|
1908
|
-
overlayClass: {
|
|
1909
|
-
type: String,
|
|
1910
|
-
},
|
|
1911
|
-
|
|
1912
|
-
/**
|
|
1913
|
-
* An overlay element on which CSS class names are set.
|
|
1914
|
-
*
|
|
1915
|
-
* @protected
|
|
1916
|
-
*/
|
|
1917
|
-
_overlayElement: {
|
|
1918
|
-
type: Object,
|
|
1919
|
-
},
|
|
1920
|
-
};
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
static get observers() {
|
|
1924
|
-
return ['__updateOverlayClassNames(overlayClass, _overlayElement)'];
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
/** @private */
|
|
1928
|
-
__updateOverlayClassNames(overlayClass, overlayElement) {
|
|
1929
|
-
if (!overlayElement) {
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
// Overlay is set but overlayClass is not set
|
|
1934
|
-
if (overlayClass === undefined) {
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
const { classList } = overlayElement;
|
|
1939
|
-
|
|
1940
|
-
if (!this.__initialClasses) {
|
|
1941
|
-
this.__initialClasses = new Set(classList);
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
if (Array.isArray(this.__previousClasses)) {
|
|
1945
|
-
// Remove old classes that no longer apply
|
|
1946
|
-
const classesToRemove = this.__previousClasses.filter((name) => !this.__initialClasses.has(name));
|
|
1947
|
-
if (classesToRemove.length > 0) {
|
|
1948
|
-
classList.remove(...classesToRemove);
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Add new classes based on the overlayClass
|
|
1953
|
-
const classesToAdd = typeof overlayClass === 'string' ? overlayClass.split(' ') : [];
|
|
1954
|
-
if (classesToAdd.length > 0) {
|
|
1955
|
-
classList.add(...classesToAdd);
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
this.__previousClasses = classesToAdd;
|
|
1959
|
-
}
|
|
1960
|
-
};
|
|
1961
|
-
|
|
1962
|
-
/**
|
|
1963
|
-
* @license
|
|
1964
|
-
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
1965
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1966
|
-
*/
|
|
1967
|
-
|
|
1968
|
-
/**
|
|
1969
|
-
* A controller which prevents the virtual keyboard from showing up on mobile devices
|
|
1970
|
-
* when the field's overlay is closed.
|
|
1971
|
-
*/
|
|
1972
|
-
class VirtualKeyboardController {
|
|
1973
|
-
/**
|
|
1974
|
-
* @param {{ inputElement?: HTMLElement; opened: boolean } & HTMLElement} host
|
|
1975
|
-
*/
|
|
1976
|
-
constructor(host) {
|
|
1977
|
-
this.host = host;
|
|
1978
|
-
|
|
1979
|
-
host.addEventListener('opened-changed', () => {
|
|
1980
|
-
if (!host.opened) {
|
|
1981
|
-
// Prevent opening the virtual keyboard when the input gets re-focused on dropdown close
|
|
1982
|
-
this.__setVirtualKeyboardEnabled(false);
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
1985
|
-
|
|
1986
|
-
// Re-enable virtual keyboard on blur, so it gets opened when the field is focused again
|
|
1987
|
-
host.addEventListener('blur', () => this.__setVirtualKeyboardEnabled(true));
|
|
1988
|
-
|
|
1989
|
-
// Re-enable the virtual keyboard whenever the field is touched
|
|
1990
|
-
host.addEventListener('touchstart', () => this.__setVirtualKeyboardEnabled(true));
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
/** @private */
|
|
1994
|
-
__setVirtualKeyboardEnabled(value) {
|
|
1995
|
-
if (this.host.inputElement) {
|
|
1996
|
-
this.host.inputElement.inputMode = value ? '' : 'none';
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
export { OverlayMixin as O, PositionMixin as P, VirtualKeyboardController as V, afterNextRender as a, OverlayClassMixin as b, overlay as c, menuOverlayCore as d, hideOthers as h, menuOverlay as m, overlayStyles as o };
|