Skip to content

Commit af376bf

Browse files
web-padawanclaude
andcommitted
refactor: replace view transitions with Web Animations in MDL
Replace the View Transitions API with the Web Animations API for master-detail-layout detail panel animations. This fixes two issues: - View transition styles don't work when MDL is inside a shadow root - [overflow] attribute eagerly removed when closing details Uses element.animate() with parameters read from CSS custom properties (--_mdl-detail-offscreen, --_mdl-transition-duration, --_mdl-easing). Animations are interruptible via cancel(). Replace transitions show old content sliding out simultaneously with new content sliding in, using a detail-outgoing slot that keeps old content in light DOM. Removes SlotStylesMixin dependency and document-level pseudo-elements. Uses preventScroll on focus to avoid scrolling the overflow:hidden host during overlay transitions. Duration defaults to 0s and is enabled via prefers-reduced-motion: no-preference media query. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 01703d4 commit af376bf

11 files changed

Lines changed: 448 additions & 425 deletions

packages/aura/src/components/dialog.css

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,8 @@ vaadin-confirm-dialog::part(overlay) {
1717
var(--vaadin-dialog-shadow, var(--vaadin-overlay-shadow, var(--aura-overlay-shadow)));
1818
--aura-surface-level: 2;
1919
--aura-surface-opacity: var(--aura-overlay-surface-opacity);
20-
21-
/* TODO probably should be in base styles */
22-
/* Keeps dialogs on top of MDL view transitions */
23-
view-transition-name: vaadin-dialog;
2420
}
2521

2622
vaadin-confirm-dialog::part(message) {
2723
color: var(--vaadin-text-color-secondary);
2824
}
29-
30-
/* TODO probably should be in base styles */
31-
::view-transition-group(vaadin-dialog) {
32-
border-radius: var(--vaadin-dialog-border-radius, var(--vaadin-radius-l));
33-
z-index: 1;
34-
}

packages/aura/src/components/master-detail-layout.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,3 @@ vaadin-master-detail-layout:not([overflow])::part(detail) {
3030
border-start-end-radius: var(--_app-layout-radius);
3131
border-end-end-radius: var(--_app-layout-radius);
3232
}
33-
34-
/* TODO these end up affecting all MDLs, not just the one directly inside the App Layout */
35-
::view-transition-group(vaadin-mdl-backdrop),
36-
::view-transition-group(vaadin-mdl-master),
37-
::view-transition-group(vaadin-mdl-detail) {
38-
border-radius: var(--_app-layout-radius);
39-
overflow: hidden;
40-
}

packages/aura/src/components/notification.css

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ vaadin-notification-card::part(overlay) {
1313
--aura-surface-level: 3.5;
1414
background: var(--vaadin-notification-background, var(--aura-surface-color));
1515
box-shadow: var(--aura-overlay-outline-shadow), var(--vaadin-notification-shadow, var(--aura-overlay-shadow));
16-
17-
/* TODO probably should be in base styles */
18-
/* Keeps notifications on top of MDL view transitions */
19-
view-transition-name: vaadin-notification;
2016
}
2117

2218
vaadin-notification-card:is(
@@ -44,21 +40,6 @@ vaadin-notification-card:is(
4440
var(--vaadin-notification-shadow, var(--aura-shadow-m));
4541
}
4642

47-
::view-transition-group(vaadin-notification) {
48-
/* Keep on top of MDL view-transition elements */
49-
z-index: 1;
50-
/* The backdrop-filter from vaadin-notification-card::part(overlay) is copied here, so we need to clip it with the same border radius */
51-
border-radius: var(--vaadin-notification-border-radius, var(--vaadin-radius-l));
52-
}
53-
54-
/* In Safari, the backdrop-filter is copied to transition-group pseudo element but also retained in the new/old pseudo elements */
55-
/* Removing it from the transition-group makes it look better */
56-
@supports (background: -webkit-named-image(i)) {
57-
::view-transition-group(vaadin-notification) {
58-
backdrop-filter: none;
59-
}
60-
}
61-
6243
vaadin-notification-card vaadin-card {
6344
--vaadin-card-border-width: 0px;
6445
--vaadin-card-gap: var(--vaadin-gap-xs) var(--vaadin-gap-s);

packages/master-detail-layout/ARCHITECTURE.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,21 @@ Layout detection is split into two methods to avoid forced reflows:
6262
- ResizeObserver callback: calls `__computeLayoutState()` (read), cancels any pending rAF via `cancelAnimationFrame`, then defers `__applyLayoutState()` (write) via `requestAnimationFrame`. Cancelling ensures the write phase always uses the latest state when multiple callbacks fire per frame.
6363
- **Property observers** (`masterSize`/`detailSize`) only update CSS custom properties — ResizeObserver picks up the resulting size changes automatically
6464

65-
### View transitions
65+
### Web Animations
6666

67-
`_finishTransition()` uses `queueMicrotask` to call both `__computeLayoutState()` + `__applyLayoutState()` synchronously. The microtask runs before the Promise resolution propagates to `startViewTransition`, ensuring the "new" snapshot captures the correct overlay state (backdrop, absolute positioning). The `getComputedStyle` read in the microtask does cause a forced reflow, but this is unavoidable for correct transition snapshots.
67+
Detail panel slide animations use the Web Animations API (`element.animate()`). Animation parameters are read from CSS custom properties:
68+
69+
- `--_mdl-detail-offscreen` — off-screen translate value (horizontal or vertical depending on orientation)
70+
- `--_mdl-transition-duration` — defaults to `0s`, enabled to `400ms` via `@media (prefers-reduced-motion: no-preference)` + `:host(:not([no-animation]))`
71+
- `--_mdl-easing` — cubic-bezier easing
72+
73+
CSS still handles resting states: default `translate: var(--_mdl-detail-offscreen)` on `[part~='detail']`, overridden to `translate: none` by `:host([has-detail])`.
74+
75+
- **Add**: DOM updated, `_finishTransition()` sets `has-detail`, then `__animate()` slides detail from off-screen to on-screen
76+
- **Remove**: `__animate()` slides detail from on-screen to off-screen, element removed on `animation.finished`
77+
- **Replace**: old content reassigned to `slot="detail-outgoing"` (stays in light DOM for style continuity), old slides out while new slides in simultaneously via two `__animate()` calls
78+
- **Interruptible**: `__endTransition()` cancels in-progress animations; version counter prevents stale callbacks
79+
- RTL support via `--_mdl-dir-multiplier` CSS variable with `:host([dir='rtl'])`
6880

6981
## Overlay Modes
7082

@@ -100,15 +112,17 @@ When no detail is present, master's extra track is set to `calc(100% - masterSiz
100112

101113
Set when detail first appears with overflow, cleared when detail is removed or overflow resolves.
102114

103-
## View Transitions
115+
## Detail Animations
104116

105-
Uses the CSS View Transitions API (`document.startViewTransition`):
117+
Uses the Web Animations API (`element.animate()`) for detail panel transitions:
106118

107119
- `_setDetail(element, skipTransition)` — adds/replaces/removes detail with animation
108-
- `_startTransition(transitionType, updateCallback)` — starts a named transition
109-
- `_finishTransition()` — calls `__computeLayoutState()` + `__applyLayoutState()` via `queueMicrotask` (see read/write separation above)
110-
- `noAnimation` property disables transitions
111-
- Styles injected via `SlotStylesMixin`
120+
- `_startTransition(transitionType, updateCallback)` — reads CSS custom properties, calls `__animate()`, manages lifecycle via `__endTransition()`
121+
- `_finishTransition()` — computes and applies layout state (`has-detail`, `overflow`, etc.)
122+
- `__animate(element, offscreen, duration, easing, slideOut)` — creates a Web Animation on `translate`, returns `animation.finished` promise
123+
- `noAnimation` property (reflected to attribute) disables animations; also disabled via CSS when `--_mdl-transition-duration: 0s`
124+
- `#detail-outgoing` container with `<slot name="detail-outgoing">` for simultaneous replace animations (old content stays in light DOM via slot reassignment)
125+
- Animations work inside shadow roots (unlike View Transitions API)
112126

113127
## Test Patterns
114128

packages/master-detail-layout/src/styles/vaadin-master-detail-layout-base-styles.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export const masterDetailLayoutStyles = css`
1212
--_detail-size: 15em;
1313
--_master-column: var(--_master-size) 0;
1414
--_detail-column: var(--_detail-size) 0;
15+
--_mdl-transition-duration: 0s;
16+
--_mdl-easing: cubic-bezier(0.78, 0, 0.22, 1);
17+
--_mdl-dir-multiplier: 1;
18+
--_mdl-detail-offscreen: calc((100% + 30px) * var(--_mdl-dir-multiplier));
1519
1620
display: grid;
1721
box-sizing: border-box;
@@ -27,7 +31,13 @@ export const masterDetailLayoutStyles = css`
2731
display: none !important;
2832
}
2933
34+
:host([dir='rtl']) {
35+
--_mdl-dir-multiplier: -1;
36+
}
37+
3038
:host([orientation='vertical']) {
39+
--_mdl-detail-offscreen: 0 calc(100% + 30px);
40+
3141
grid-template-columns: 100%;
3242
grid-template-rows: [master-start] var(--_master-column) [detail-start] var(--_detail-column) [detail-end];
3343
}
@@ -89,6 +99,27 @@ export const masterDetailLayoutStyles = css`
8999
var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
90100
}
91101
102+
/* Detail transition: off-screen by default, on-screen when has-detail */
103+
[part~='detail']:not([part~='detail-outgoing']) {
104+
translate: var(--_mdl-detail-offscreen);
105+
}
106+
107+
:host([has-detail]) [part~='detail']:not([part~='detail-outgoing']) {
108+
translate: none;
109+
}
110+
111+
/* During replace, the outgoing detail needs an opaque background for
112+
the slide-out to be visible in split mode (no background by default).
113+
The outgoing also needs grid-row to overlap with the incoming detail
114+
(otherwise auto-placed in an implicit row with 0 height). */
115+
:host([transition='replace']) [part~='detail'] {
116+
background: var(--vaadin-master-detail-layout-detail-background, var(--vaadin-background-color));
117+
}
118+
119+
[part~='detail-outgoing']:not([hidden]) {
120+
grid-row: 1 / -1;
121+
}
122+
92123
:host([overflow]) [part~='detail'] {
93124
position: absolute;
94125
z-index: 2;
@@ -129,4 +160,11 @@ export const masterDetailLayoutStyles = css`
129160
background: Canvas !important;
130161
}
131162
}
163+
164+
/* Enable transitions when motion is allowed */
165+
@media (prefers-reduced-motion: no-preference) {
166+
:host(:not([no-animation])) {
167+
--_mdl-transition-duration: 400ms;
168+
}
169+
}
132170
`;

packages/master-detail-layout/src/styles/vaadin-master-detail-layout-transition-base-styles.js

Lines changed: 0 additions & 107 deletions
This file was deleted.

packages/master-detail-layout/src/vaadin-master-detail-layout.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
66
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7-
import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
87
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
98

109
export interface MasterDetailLayoutCustomEventMap {
@@ -55,7 +54,7 @@ export interface MasterDetailLayoutEventMap extends HTMLElementEventMap, MasterD
5554
* @fires {CustomEvent} backdrop-click - Fired when the user clicks the backdrop in the overlay mode.
5655
* @fires {CustomEvent} detail-escape-press - Fired when the user presses Escape in the detail area.
5756
*/
58-
declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMixin(HTMLElement))) {
57+
declare class MasterDetailLayout extends ThemableMixin(ElementMixin(HTMLElement)) {
5958
/**
6059
* Size (in CSS length units) to be set on the detail area in
6160
* the CSS grid layout. If there is not enough space to show

0 commit comments

Comments
 (0)