@vaadin/master-detail-layout 25.1.2 → 25.2.0-alpha10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +276 -64
- package/package.json +8 -8
- package/src/styles/vaadin-master-detail-layout-base-styles.js +204 -105
- package/src/vaadin-master-detail-layout-helpers.js +173 -0
- package/src/vaadin-master-detail-layout.d.ts +102 -66
- package/src/vaadin-master-detail-layout.js +446 -329
- package/web-types.json +56 -92
- package/web-types.lit.json +22 -22
- package/src/styles/vaadin-master-detail-layout-transition-base-styles.js +0 -147
|
@@ -7,189 +7,288 @@ import '@vaadin/component-base/src/styles/style-props.js';
|
|
|
7
7
|
import { css } from 'lit';
|
|
8
8
|
|
|
9
9
|
export const masterDetailLayoutStyles = css`
|
|
10
|
-
/*
|
|
11
|
-
|
|
10
|
+
/* stylelint-disable no-duplicate-selectors */
|
|
12
11
|
:host {
|
|
13
|
-
|
|
12
|
+
--_rtl-multiplier: 1;
|
|
13
|
+
--_transition-duration: 0s;
|
|
14
|
+
--_transition-easing: cubic-bezier(0.78, 0, 0.22, 1);
|
|
15
|
+
|
|
16
|
+
display: grid;
|
|
14
17
|
box-sizing: border-box;
|
|
15
18
|
height: 100%;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
position: relative; /* Keep the positioning context stable across all modes */
|
|
19
|
-
z-index: 0; /* Create a new stacking context, don't let "layout contained" detail element stack outside it */
|
|
20
|
-
overflow: hidden;
|
|
19
|
+
position: relative;
|
|
20
|
+
overflow: clip;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
:host([
|
|
24
|
-
|
|
23
|
+
:host:not([overlay-containment='page']) {
|
|
24
|
+
z-index: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:host([dir='rtl']) {
|
|
28
|
+
--_rtl-multiplier: -1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
:host([orientation='horizontal']) {
|
|
32
|
+
--_transition-offset: calc(30px * var(--_rtl-multiplier));
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
:host([orientation='vertical']) {
|
|
28
|
-
|
|
36
|
+
--_transition-offset: 0 30px;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
[
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/* Disable pointer events for the detail wrapper to allow clicks to pass through to the backdrop */
|
|
35
|
-
pointer-events: none;
|
|
39
|
+
:host([hidden]),
|
|
40
|
+
::slotted([hidden]) {
|
|
41
|
+
display: none !important;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
/* CSS grid template */
|
|
45
|
+
|
|
46
|
+
:host {
|
|
47
|
+
--_master-size: min(100%, 30rem);
|
|
48
|
+
--_master-extra: 0px;
|
|
49
|
+
--_detail-size: var(--_detail-cached-size);
|
|
50
|
+
--_detail-extra: 0px;
|
|
51
|
+
--_detail-cached-size: min-content;
|
|
52
|
+
|
|
53
|
+
/* prettier-ignore */
|
|
54
|
+
--_grid-template:
|
|
55
|
+
[master-start] var(--_master-size) [master-extra] var(--_master-extra)
|
|
56
|
+
[detail-start] var(--_detail-size) [detail-extra] var(--_detail-extra)
|
|
57
|
+
[detail-end];
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
:host([
|
|
44
|
-
|
|
60
|
+
:host([force-overlay]) {
|
|
61
|
+
/* prettier-ignore */
|
|
62
|
+
--_grid-template:
|
|
63
|
+
[master-start] var(--_master-size) [master-extra] var(--_master-extra)
|
|
64
|
+
[detail-start] 0px [detail-extra] 0px
|
|
65
|
+
[detail-end];
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
:host(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
position: absolute;
|
|
51
|
-
z-index: 1;
|
|
52
|
-
inset: 0;
|
|
53
|
-
overscroll-behavior: contain;
|
|
68
|
+
:host([orientation='horizontal']) {
|
|
69
|
+
grid-template-columns: var(--_grid-template);
|
|
70
|
+
grid-template-rows: 100%;
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
:host(
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
:host([orientation='vertical']) {
|
|
74
|
+
grid-template-columns: 100%;
|
|
75
|
+
grid-template-rows: var(--_grid-template);
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
/* CSS grid placement */
|
|
79
|
+
|
|
80
|
+
:host {
|
|
81
|
+
--_master-area: master-start / detail-start;
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
When the detail size isn't explicitly defined and the detail is set to expand,
|
|
85
|
+
the detail column template is 'min-content 1fr'. In this case, the detail area
|
|
86
|
+
should not span both columns initially (and when recalculating the detail size)
|
|
87
|
+
as spanning both would effectively collapse them into a single '1fr' column where
|
|
88
|
+
min-content resolves to 0, making it impossible to measure the detail's intrinsic
|
|
89
|
+
minimum width from JavaScript.
|
|
90
|
+
*/
|
|
91
|
+
--_detail-area: detail-start / detail-extra;
|
|
63
92
|
}
|
|
64
93
|
|
|
65
|
-
:host([
|
|
66
|
-
|
|
94
|
+
:host(:is([has-detail], [has-detail-placeholder]):not([recalculating-detail-size])) {
|
|
95
|
+
--_detail-area: detail-start / detail-end;
|
|
67
96
|
}
|
|
68
97
|
|
|
69
|
-
:host(
|
|
70
|
-
|
|
98
|
+
:host([orientation='horizontal']) #master {
|
|
99
|
+
grid-column: var(--_master-area);
|
|
100
|
+
grid-row: 1;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
|
-
:host(
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
:host([orientation='vertical']) #master {
|
|
104
|
+
grid-column: 1;
|
|
105
|
+
grid-row: var(--_master-area);
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
:host([
|
|
79
|
-
|
|
108
|
+
:host([orientation='horizontal']) :is(#detail, #detailPlaceholder, #detailOutgoing) {
|
|
109
|
+
grid-column: var(--_detail-area);
|
|
110
|
+
grid-row: 1;
|
|
80
111
|
}
|
|
81
112
|
|
|
82
|
-
:host([
|
|
83
|
-
|
|
113
|
+
:host([orientation='vertical']) :is(#detail, #detailPlaceholder, #detailOutgoing) {
|
|
114
|
+
grid-column: 1;
|
|
115
|
+
grid-row: var(--_detail-area);
|
|
84
116
|
}
|
|
85
117
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
/* Expand */
|
|
119
|
+
|
|
120
|
+
:host([expand-master]) {
|
|
121
|
+
--_master-extra: 1fr;
|
|
89
122
|
}
|
|
90
123
|
|
|
91
|
-
|
|
124
|
+
:host([expand-detail]) {
|
|
125
|
+
--_detail-extra: 1fr;
|
|
126
|
+
}
|
|
92
127
|
|
|
93
|
-
[
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
:host([keep-detail-column-offscreen]),
|
|
129
|
+
:host([has-detail-placeholder][overlay]:not([has-detail])),
|
|
130
|
+
:host(:not([has-detail-placeholder], [has-detail])) {
|
|
131
|
+
--_master-extra: calc(100% - var(--_master-size));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Backdrop base styles */
|
|
135
|
+
|
|
136
|
+
#backdrop {
|
|
137
|
+
--_transition-easing: linear;
|
|
138
|
+
|
|
139
|
+
position: absolute;
|
|
140
|
+
inset: 0;
|
|
141
|
+
z-index: 2;
|
|
142
|
+
opacity: 0;
|
|
143
|
+
pointer-events: none;
|
|
144
|
+
background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.2));
|
|
145
|
+
forced-color-adjust: none;
|
|
97
146
|
}
|
|
98
147
|
|
|
99
|
-
/*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
/* Master base styles */
|
|
149
|
+
|
|
150
|
+
#master {
|
|
151
|
+
opacity: 0;
|
|
152
|
+
pointer-events: none;
|
|
153
|
+
box-sizing: border-box;
|
|
104
154
|
}
|
|
105
155
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
flex-shrink: 0;
|
|
156
|
+
:host([has-master]) #master {
|
|
157
|
+
opacity: 1;
|
|
158
|
+
pointer-events: auto;
|
|
110
159
|
}
|
|
111
160
|
|
|
112
|
-
|
|
113
|
-
|
|
161
|
+
/* Detail base styles */
|
|
162
|
+
|
|
163
|
+
#detail {
|
|
164
|
+
translate: var(--_transition-offset);
|
|
165
|
+
opacity: 0;
|
|
166
|
+
z-index: 4;
|
|
114
167
|
}
|
|
115
168
|
|
|
116
|
-
:host([
|
|
117
|
-
|
|
169
|
+
:host([has-detail]) #detail {
|
|
170
|
+
translate: none;
|
|
171
|
+
opacity: 1;
|
|
118
172
|
}
|
|
119
173
|
|
|
120
|
-
|
|
121
|
-
|
|
174
|
+
#detailOutgoing {
|
|
175
|
+
position: absolute;
|
|
176
|
+
z-index: 3;
|
|
177
|
+
display: none;
|
|
122
178
|
}
|
|
123
179
|
|
|
124
|
-
:host([
|
|
125
|
-
|
|
180
|
+
:host([transition='replace']) #detailOutgoing {
|
|
181
|
+
display: block;
|
|
126
182
|
}
|
|
127
183
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
184
|
+
#detailPlaceholder {
|
|
185
|
+
z-index: 1;
|
|
186
|
+
opacity: 0;
|
|
187
|
+
pointer-events: none;
|
|
131
188
|
}
|
|
132
189
|
|
|
133
|
-
:host([has-
|
|
134
|
-
|
|
135
|
-
|
|
190
|
+
:host([has-detail-placeholder]:not([has-detail], [overlay])) #detailPlaceholder {
|
|
191
|
+
opacity: 1;
|
|
192
|
+
pointer-events: auto;
|
|
136
193
|
}
|
|
137
194
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
min-width: min(100%, var(--_master-min-size));
|
|
195
|
+
:is(#detail, #detailPlaceholder, #detailOutgoing) {
|
|
196
|
+
box-sizing: border-box;
|
|
141
197
|
}
|
|
142
198
|
|
|
143
|
-
|
|
144
|
-
|
|
199
|
+
/* Detail borders */
|
|
200
|
+
|
|
201
|
+
#detail,
|
|
202
|
+
#detailPlaceholder {
|
|
203
|
+
border-color: var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
|
|
204
|
+
border-width: var(--vaadin-master-detail-layout-border-width, 1px);
|
|
145
205
|
}
|
|
146
206
|
|
|
147
|
-
:host([orientation='horizontal']
|
|
148
|
-
|
|
207
|
+
:host([orientation='horizontal']) #detailPlaceholder,
|
|
208
|
+
:host([orientation='horizontal']:not([overlay])) #detail {
|
|
209
|
+
border-inline-start-style: solid;
|
|
149
210
|
}
|
|
150
211
|
|
|
151
|
-
:host([orientation='vertical']
|
|
152
|
-
|
|
212
|
+
:host([orientation='vertical']) #detailPlaceholder,
|
|
213
|
+
:host([orientation='vertical']:not([overlay])) #detail {
|
|
214
|
+
border-block-start-style: solid;
|
|
153
215
|
}
|
|
154
216
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
min-width: auto !important;
|
|
160
|
-
min-height: auto !important;
|
|
161
|
-
max-width: 100% !important;
|
|
162
|
-
max-height: 100% !important;
|
|
217
|
+
/* Overlay */
|
|
218
|
+
|
|
219
|
+
:host([overlay][orientation='horizontal']) {
|
|
220
|
+
--_transition-offset: calc((100% + 30px) * var(--_rtl-multiplier));
|
|
163
221
|
}
|
|
164
222
|
|
|
165
|
-
|
|
223
|
+
:host([overlay][orientation='vertical']) {
|
|
224
|
+
--_transition-offset: 0 calc(100% + 30px);
|
|
225
|
+
}
|
|
166
226
|
|
|
167
|
-
[
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
:host([has-detail][overlay]) #backdrop {
|
|
228
|
+
opacity: 1;
|
|
229
|
+
pointer-events: auto;
|
|
170
230
|
}
|
|
171
231
|
|
|
172
|
-
:host(
|
|
232
|
+
:host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
|
|
233
|
+
position: absolute;
|
|
173
234
|
background: var(--vaadin-master-detail-layout-detail-background, var(--vaadin-background-color));
|
|
174
235
|
box-shadow: var(--vaadin-master-detail-layout-detail-shadow, 0 0 20px 0 rgba(0, 0, 0, 0.3));
|
|
236
|
+
grid-column: none;
|
|
237
|
+
grid-row: none;
|
|
175
238
|
}
|
|
176
239
|
|
|
177
|
-
:host([orientation='horizontal']:
|
|
178
|
-
|
|
179
|
-
|
|
240
|
+
:host([has-detail][overlay][orientation='horizontal']) :is(#detail, #detailOutgoing) {
|
|
241
|
+
inset-block: 0;
|
|
242
|
+
inset-inline-end: 0;
|
|
243
|
+
width: var(--_overlay-size, var(--_detail-size));
|
|
244
|
+
max-width: 100%;
|
|
180
245
|
}
|
|
181
246
|
|
|
182
|
-
:host([orientation='vertical']:
|
|
183
|
-
|
|
184
|
-
|
|
247
|
+
:host([has-detail][overlay][orientation='vertical']) :is(#detail, #detailOutgoing) {
|
|
248
|
+
inset-inline: 0;
|
|
249
|
+
inset-block-end: 0;
|
|
250
|
+
height: var(--_overlay-size, var(--_detail-size));
|
|
251
|
+
max-height: 100%;
|
|
185
252
|
}
|
|
186
253
|
|
|
254
|
+
:host([has-detail][overlay][overlay-containment='page']) :is(#detail, #detailOutgoing, #backdrop) {
|
|
255
|
+
position: fixed;
|
|
256
|
+
padding-top: env(safe-area-inset-top);
|
|
257
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
258
|
+
padding-right: env(safe-area-inset-right);
|
|
259
|
+
--safe-area-inset-top: 0px;
|
|
260
|
+
--safe-area-inset-bottom: 0px;
|
|
261
|
+
--safe-area-inset-left: 0px;
|
|
262
|
+
--safe-area-inset-right: 0px;
|
|
263
|
+
--safe-area-inset-inline-start: 0px;
|
|
264
|
+
--safe-area-inset-inline-end: 0px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
:host([dir='rtl'][has-detail][overlay][overlay-containment='page']) :is(#detail, #detailOutgoing, #backdrop) {
|
|
268
|
+
padding-right: 0;
|
|
269
|
+
padding-left: env(safe-area-inset-left);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Transitions */
|
|
273
|
+
|
|
274
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
275
|
+
:host(:not([no-animation], [transition='replace'])) {
|
|
276
|
+
--_transition-duration: 200ms;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
:host([overlay]:not([no-animation])) {
|
|
280
|
+
--_transition-duration: 300ms;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Forced colors */
|
|
285
|
+
|
|
187
286
|
@media (forced-colors: active) {
|
|
188
|
-
:host(
|
|
287
|
+
:host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
|
|
189
288
|
outline: 3px solid !important;
|
|
190
289
|
}
|
|
191
290
|
|
|
192
|
-
|
|
291
|
+
:is(#detail, #detailPlaceholder, #detailOutgoing) {
|
|
193
292
|
background: Canvas !important;
|
|
194
293
|
}
|
|
195
294
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2025 - 2026 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ANIMATION_ID = 'vaadin-master-detail-layout';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reads CSS custom properties from the element that control
|
|
11
|
+
* animation keyframes and timing.
|
|
12
|
+
*
|
|
13
|
+
* @param {HTMLElement} element
|
|
14
|
+
* @return {{ offset: string, easing: string, duration: number }}
|
|
15
|
+
*/
|
|
16
|
+
function getAnimationParams(element) {
|
|
17
|
+
const computedStyle = getComputedStyle(element);
|
|
18
|
+
const offset = computedStyle.getPropertyValue('--_transition-offset');
|
|
19
|
+
const easing = computedStyle.getPropertyValue('--_transition-easing');
|
|
20
|
+
const durationStr = computedStyle.getPropertyValue('--_transition-duration');
|
|
21
|
+
const duration = durationStr.endsWith('ms') ? parseFloat(durationStr) : parseFloat(durationStr) * 1000;
|
|
22
|
+
return { offset, easing, duration };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns the currently running master-detail-layout animation on the
|
|
27
|
+
* element, if any. Matches by the shared animation ID and `'running'`
|
|
28
|
+
* play state.
|
|
29
|
+
*
|
|
30
|
+
* @param {HTMLElement} element
|
|
31
|
+
* @return {Animation | undefined}
|
|
32
|
+
*/
|
|
33
|
+
export function getCurrentAnimation(element) {
|
|
34
|
+
return element
|
|
35
|
+
.getAnimations()
|
|
36
|
+
.find((animation) => animation.id === ANIMATION_ID && animation.playState !== 'finished');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns the overall progress (0–1) of the current animation on the
|
|
41
|
+
* element, computed as `currentTime / duration`. Returns 0 when no
|
|
42
|
+
* animation is running.
|
|
43
|
+
*
|
|
44
|
+
* @param {HTMLElement} element
|
|
45
|
+
* @return {number}
|
|
46
|
+
*/
|
|
47
|
+
export function getCurrentAnimationProgress(element) {
|
|
48
|
+
const animation = getCurrentAnimation(element);
|
|
49
|
+
if (!animation) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
const currentTime = animation.currentTime;
|
|
53
|
+
if (currentTime == null) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
return currentTime / animation.effect.getTiming().duration;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Animates the element using the Web Animations API. Cancels any
|
|
61
|
+
* previous animation and resumes from the given progress for a
|
|
62
|
+
* smooth handoff. No-op when CSS params are missing or progress is 1.
|
|
63
|
+
*
|
|
64
|
+
* @param {HTMLElement} element
|
|
65
|
+
* @param {'in' | 'out'} direction
|
|
66
|
+
* @param {Array<'fade' | 'slide'>} effects
|
|
67
|
+
* @param {number} progress starting progress (0–1) for interrupted resumption
|
|
68
|
+
* @return {Promise<void>} resolves when the animation finishes
|
|
69
|
+
*/
|
|
70
|
+
function animate(element, direction, effects, progress) {
|
|
71
|
+
const { offset, easing, duration } = getAnimationParams(element);
|
|
72
|
+
if (!offset || !duration || progress === 1) {
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const oldAnimation = getCurrentAnimation(element);
|
|
77
|
+
if (oldAnimation) {
|
|
78
|
+
oldAnimation.cancel();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const keyframes = {};
|
|
82
|
+
if (effects.includes('fade')) {
|
|
83
|
+
keyframes.opacity = [0, 1];
|
|
84
|
+
}
|
|
85
|
+
if (effects.includes('slide')) {
|
|
86
|
+
keyframes.translate = [offset, 0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const newAnimation = element.animate(keyframes, { id: ANIMATION_ID, easing, duration });
|
|
90
|
+
newAnimation.pause();
|
|
91
|
+
newAnimation.currentTime = duration * progress;
|
|
92
|
+
newAnimation.playbackRate = direction === 'in' ? 1 : -1;
|
|
93
|
+
newAnimation.play();
|
|
94
|
+
return newAnimation.finished;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Runs an enter animation on the element.
|
|
99
|
+
*
|
|
100
|
+
* @param {HTMLElement} element
|
|
101
|
+
* @param {Array<'fade' | 'slide'>} effects
|
|
102
|
+
* @param {number} progress starting progress (0–1) for interrupted resumption
|
|
103
|
+
* @return {Promise<void>} resolves when the animation finishes
|
|
104
|
+
*/
|
|
105
|
+
export function animateIn(element, effects, progress) {
|
|
106
|
+
return animate(element, 'in', effects, progress);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Runs an exit animation on the element.
|
|
111
|
+
*
|
|
112
|
+
* @param {HTMLElement} element
|
|
113
|
+
* @param {Array<'fade' | 'slide'>} effects
|
|
114
|
+
* @param {number} progress starting progress (0–1) for interrupted resumption
|
|
115
|
+
* @return {Promise<void>} resolves when the animation finishes
|
|
116
|
+
*/
|
|
117
|
+
export function animateOut(element, effects, progress) {
|
|
118
|
+
return animate(element, 'out', effects, progress);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Cancels all running animations on the element that match the shared animation ID.
|
|
123
|
+
*
|
|
124
|
+
* @param {HTMLElement} element
|
|
125
|
+
*/
|
|
126
|
+
export function cancelAnimations(element) {
|
|
127
|
+
element.getAnimations({ subtree: true }).forEach((animation) => {
|
|
128
|
+
if (animation.id === ANIMATION_ID) {
|
|
129
|
+
animation.cancel();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parses a computed `gridTemplateColumns` / `gridTemplateRows` value
|
|
136
|
+
* into an array of track sizes in pixels. Line names (e.g. `[name]`)
|
|
137
|
+
* are stripped before parsing.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} gridTemplate computed grid template string (e.g. `"200px [gap] 10px 400px"`)
|
|
140
|
+
* @return {number[]} track sizes in pixels
|
|
141
|
+
*/
|
|
142
|
+
export function parseTrackSizes(gridTemplate) {
|
|
143
|
+
return gridTemplate
|
|
144
|
+
.replace(/\[[^\]]+\]/gu, '')
|
|
145
|
+
.replace(/\s+/gu, ' ')
|
|
146
|
+
.trim()
|
|
147
|
+
.split(' ')
|
|
148
|
+
.map(parseFloat);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Determines whether the detail area overflows the host element,
|
|
153
|
+
* meaning it should be shown as an overlay instead of side-by-side.
|
|
154
|
+
*
|
|
155
|
+
* Returns `false` when all tracks fit within the host, or when the
|
|
156
|
+
* master's extra space (flexible portion) is large enough to absorb
|
|
157
|
+
* the detail column.
|
|
158
|
+
*
|
|
159
|
+
* @param {number} hostSize the host element's width or height in pixels
|
|
160
|
+
* @param {number[]} trackSizes [masterSize, masterExtra, detailSize] in pixels
|
|
161
|
+
* @return {boolean} `true` if the detail overflows and should be overlaid
|
|
162
|
+
*/
|
|
163
|
+
export function detectOverflow(hostSize, trackSizes) {
|
|
164
|
+
const [masterSize, masterExtra, detailSize] = trackSizes;
|
|
165
|
+
|
|
166
|
+
if (Math.floor(masterSize + masterExtra + detailSize) <= Math.floor(hostSize)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (Math.floor(masterExtra) >= Math.floor(detailSize)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|