@vaadin/master-detail-layout 25.2.0-alpha1 → 25.2.0-alpha2

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.
@@ -23,7 +23,7 @@
23
23
  "declarations": [
24
24
  {
25
25
  "kind": "class",
26
- "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n----------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`overflow` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
26
+ "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Slots\n\nThe component has two main content areas: the master area (default slot)\nand the detail area (`detail` slot). When the detail doesn't fit next to\nthe master, it is shown as an overlay on top of the master area:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail\">Detail content</div>\n</vaadin-master-detail-layout>\n```\n\nThe component also supports a `detail-placeholder` slot for content shown\nin the detail area when no detail is selected. Unlike the `detail` slot,\nthe placeholder is simply hidden when it doesn't fit next to the master area,\nrather than shown as an overlay:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail-placeholder\">Select an item</div>\n</vaadin-master-detail-layout>\n```\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n----------------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n`detail-placeholder` | The detail placeholder area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`has-detail-placeholder` | Set when the detail placeholder content is provided.\n`overlay` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
27
27
  "name": "MasterDetailLayout",
28
28
  "members": [
29
29
  {
@@ -43,7 +43,7 @@
43
43
  "type": {
44
44
  "text": "string"
45
45
  },
46
- "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'both'`.",
46
+ "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'master'`.",
47
47
  "attribute": "expand"
48
48
  },
49
49
  {
@@ -95,16 +95,6 @@
95
95
  },
96
96
  "description": "Size (in CSS length units) for the detail area when shown as an\noverlay. When not set, falls back to `detailSize`. Set to `100%`\nto make the detail cover the full layout.",
97
97
  "attribute": "overlay-size"
98
- },
99
- {
100
- "kind": "field",
101
- "name": "slotStyles",
102
- "return": {
103
- "type": {
104
- "text": "!Array<!CSSResult>"
105
- }
106
- },
107
- "readonly": true
108
98
  }
109
99
  ],
110
100
  "events": [
@@ -137,7 +127,7 @@
137
127
  "type": {
138
128
  "text": "string"
139
129
  },
140
- "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'both'`.",
130
+ "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'master'`.",
141
131
  "fieldName": "expand"
142
132
  },
143
133
  {
@@ -182,10 +172,6 @@
182
172
  }
183
173
  ],
184
174
  "mixins": [
185
- {
186
- "name": "SlotStylesMixin",
187
- "package": "@vaadin/component-base/src/slot-styles-mixin.js"
188
- },
189
175
  {
190
176
  "name": "ElementMixin",
191
177
  "package": "@vaadin/component-base/src/element-mixin.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/master-detail-layout",
3
- "version": "25.2.0-alpha1",
3
+ "version": "25.2.0-alpha2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,16 +34,16 @@
34
34
  "web-component"
35
35
  ],
36
36
  "dependencies": {
37
- "@vaadin/a11y-base": "25.2.0-alpha1",
38
- "@vaadin/component-base": "25.2.0-alpha1",
39
- "@vaadin/vaadin-themable-mixin": "25.2.0-alpha1",
37
+ "@vaadin/a11y-base": "25.2.0-alpha2",
38
+ "@vaadin/component-base": "25.2.0-alpha2",
39
+ "@vaadin/vaadin-themable-mixin": "25.2.0-alpha2",
40
40
  "lit": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@vaadin/aura": "25.2.0-alpha1",
44
- "@vaadin/chai-plugins": "25.2.0-alpha1",
43
+ "@vaadin/aura": "25.2.0-alpha2",
44
+ "@vaadin/chai-plugins": "25.2.0-alpha2",
45
45
  "@vaadin/testing-helpers": "^2.0.0",
46
- "@vaadin/vaadin-lumo-styles": "25.2.0-alpha1",
46
+ "@vaadin/vaadin-lumo-styles": "25.2.0-alpha2",
47
47
  "sinon": "^21.0.2"
48
48
  },
49
49
  "customElements": "custom-elements.json",
@@ -51,5 +51,5 @@
51
51
  "web-types.json",
52
52
  "web-types.lit.json"
53
53
  ],
54
- "gitHead": "866f813f89655a351cbd25328eba1fcb317e267d"
54
+ "gitHead": "34c9b41017bd4896f6e4b250ba50d1dd8535a061"
55
55
  }
@@ -12,6 +12,10 @@ export const masterDetailLayoutStyles = css`
12
12
  --_detail-size: 15em;
13
13
  --_master-column: var(--_master-size) 0;
14
14
  --_detail-column: var(--_detail-size) 0;
15
+ --_transition-duration: 0s;
16
+ --_transition-easing: cubic-bezier(0.78, 0, 0.22, 1);
17
+ --_rtl-multiplier: 1;
18
+ --_detail-offscreen: calc(30px * var(--_rtl-multiplier));
15
19
 
16
20
  display: grid;
17
21
  box-sizing: border-box;
@@ -27,39 +31,55 @@ export const masterDetailLayoutStyles = css`
27
31
  display: none !important;
28
32
  }
29
33
 
34
+ :host([dir='rtl']) {
35
+ --_rtl-multiplier: -1;
36
+ }
37
+
30
38
  :host([orientation='vertical']) {
39
+ --_detail-offscreen: 0 30px;
40
+
31
41
  grid-template-columns: 100%;
32
42
  grid-template-rows: [master-start] var(--_master-column) [detail-start] var(--_detail-column) [detail-end];
33
43
  }
34
44
 
35
- [part~='master'],
36
- [part~='detail'] {
45
+ :is(#master, #detail, #detail-placeholder, #outgoing) {
37
46
  box-sizing: border-box;
38
47
  }
39
48
 
40
- [part~='master'] {
49
+ #detail-placeholder {
50
+ display: none;
51
+ }
52
+
53
+ :host([has-detail-placeholder]:not([has-detail], [overlay])) #detail-placeholder {
54
+ display: block;
55
+ }
56
+
57
+ #master {
41
58
  grid-column: master-start / detail-start;
59
+ grid-row: 1;
42
60
  }
43
61
 
44
- [part~='detail'] {
62
+ :is(#detail, #detail-placeholder, #outgoing) {
45
63
  grid-column: detail-start / detail-end;
64
+ grid-row: 1;
46
65
  }
47
66
 
48
- :host([orientation='vertical']) [part~='master'] {
49
- grid-column: auto;
67
+ :host([orientation='vertical']) #master {
68
+ grid-column: 1;
50
69
  grid-row: master-start / detail-start;
51
70
  }
52
71
 
53
- :host([orientation='vertical']) [part~='detail'] {
54
- grid-column: auto;
72
+ :host([orientation='vertical']) :is(#detail, #detail-placeholder, #outgoing) {
73
+ grid-column: 1;
55
74
  grid-row: detail-start / detail-end;
56
75
  }
57
76
 
58
- [part~='backdrop'] {
77
+ #backdrop {
59
78
  position: absolute;
60
79
  inset: 0;
61
80
  z-index: 1;
62
- display: none;
81
+ opacity: 0;
82
+ pointer-events: none;
63
83
  background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.2));
64
84
  forced-color-adjust: none;
65
85
  }
@@ -69,8 +89,9 @@ export const masterDetailLayoutStyles = css`
69
89
  --_master-column: var(--_master-size) 1fr;
70
90
  }
71
91
 
72
- :host(:not([has-detail])),
73
- :host([keep-detail-column-offscreen]) {
92
+ :host([keep-detail-column-offscreen]),
93
+ :host([has-detail-placeholder][overlay]),
94
+ :host(:not([has-detail-placeholder], [has-detail])) {
74
95
  --_master-column: var(--_master-size) calc(100% - var(--_master-size));
75
96
  }
76
97
 
@@ -79,54 +100,87 @@ export const masterDetailLayoutStyles = css`
79
100
  --_detail-column: var(--_detail-size) 1fr;
80
101
  }
81
102
 
82
- :host([orientation='horizontal'][has-detail]:not([overflow])) [part~='detail'] {
103
+ :host([orientation='horizontal']) #detail-placeholder,
104
+ :host([orientation='horizontal'][has-detail]:not([overlay])) #detail {
83
105
  border-inline-start: var(--vaadin-master-detail-layout-border-width, 1px) solid
84
106
  var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
85
107
  }
86
108
 
87
- :host([orientation='vertical'][has-detail]:not([overflow])) [part~='detail'] {
109
+ :host([orientation='vertical']) #detail-placeholder,
110
+ :host([orientation='vertical'][has-detail]:not([overlay])) #detail {
88
111
  border-top: var(--vaadin-master-detail-layout-border-width, 1px) solid
89
112
  var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
90
113
  }
91
114
 
92
- :host([overflow]) [part~='detail'] {
115
+ /* Detail transition: off-screen by default, on-screen when has-detail */
116
+ #detail {
117
+ translate: var(--_detail-offscreen);
118
+ }
119
+
120
+ :host([has-detail]) #detail {
121
+ translate: none;
122
+ }
123
+
124
+ #outgoing:not([hidden]) {
125
+ z-index: 1;
126
+ }
127
+
128
+ :host([overlay]) {
129
+ --_detail-offscreen: calc((100% + 30px) * var(--_rtl-multiplier));
130
+ }
131
+
132
+ :host([overlay][orientation='vertical']) {
133
+ --_detail-offscreen: 0 calc(100% + 30px);
134
+ }
135
+
136
+ :host([has-detail][overlay]) :is(#detail, #outgoing) {
93
137
  position: absolute;
94
138
  z-index: 2;
95
139
  background: var(--vaadin-master-detail-layout-detail-background, var(--vaadin-background-color));
96
140
  box-shadow: var(--vaadin-master-detail-layout-detail-shadow, 0 0 20px 0 rgba(0, 0, 0, 0.3));
97
141
  grid-column: none;
142
+ grid-row: none;
98
143
  }
99
144
 
100
- :host([overflow]) [part~='backdrop'] {
101
- display: block;
145
+ :host([has-detail][overlay]) #backdrop {
146
+ opacity: 1;
147
+ pointer-events: auto;
102
148
  }
103
149
 
104
- :host([overflow]:not([orientation='vertical'])) [part~='detail'] {
150
+ :host([has-detail][overlay]:not([orientation='vertical'])) :is(#detail, #outgoing) {
105
151
  inset-block: 0;
106
- width: var(--_overlay-size, var(--_detail-size, min-content));
107
152
  inset-inline-end: 0;
153
+ width: var(--_overlay-size, var(--_detail-size, min-content));
108
154
  }
109
155
 
110
- :host([overflow][orientation='vertical']) [part~='detail'] {
111
- grid-column: auto;
112
- grid-row: none;
156
+ :host([has-detail][overlay][orientation='vertical']) :is(#detail, #outgoing) {
113
157
  inset-inline: 0;
114
- height: var(--_overlay-size, var(--_detail-size, min-content));
115
158
  inset-block-end: 0;
159
+ height: var(--_overlay-size, var(--_detail-size, min-content));
116
160
  }
117
161
 
118
- :host([overflow][overlay-containment='viewport']) [part~='detail'],
119
- :host([overflow][overlay-containment='viewport']) [part~='backdrop'] {
162
+ :host([has-detail][overlay][overlay-containment='viewport']) :is(#detail, #outgoing, #backdrop) {
120
163
  position: fixed;
121
164
  }
122
165
 
123
166
  @media (forced-colors: active) {
124
- :host([overflow]) [part~='detail'] {
167
+ :host([has-detail][overlay]) :is(#detail, #outgoing) {
125
168
  outline: 3px solid !important;
126
169
  }
127
170
 
128
- [part~='detail'] {
171
+ :is(#detail, #detail-placeholder, #outgoing) {
129
172
  background: Canvas !important;
130
173
  }
131
174
  }
175
+
176
+ /* Enable transitions when motion is allowed */
177
+ @media (prefers-reduced-motion: no-preference) {
178
+ :host(:not([no-animation], [transition='replace'])) {
179
+ --_transition-duration: 200ms;
180
+ }
181
+
182
+ :host([overlay]:not([no-animation])) {
183
+ --_transition-duration: 300ms;
184
+ }
185
+ }
132
186
  `;
@@ -4,7 +4,6 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
- import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
8
7
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
8
 
10
9
  export interface MasterDetailLayoutCustomEventMap {
@@ -20,25 +19,52 @@ export interface MasterDetailLayoutEventMap extends HTMLElementEventMap, MasterD
20
19
  * (or primary) area and a detail (or secondary) area that is displayed next to, or
21
20
  * overlaid on top of, the master area, depending on configuration and viewport size.
22
21
  *
22
+ * ### Slots
23
+ *
24
+ * The component has two main content areas: the master area (default slot)
25
+ * and the detail area (`detail` slot). When the detail doesn't fit next to
26
+ * the master, it is shown as an overlay on top of the master area:
27
+ *
28
+ * ```html
29
+ * <vaadin-master-detail-layout>
30
+ * <div>Master content</div>
31
+ * <div slot="detail">Detail content</div>
32
+ * </vaadin-master-detail-layout>
33
+ * ```
34
+ *
35
+ * The component also supports a `detail-placeholder` slot for content shown
36
+ * in the detail area when no detail is selected. Unlike the `detail` slot,
37
+ * the placeholder is simply hidden when it doesn't fit next to the master area,
38
+ * rather than shown as an overlay:
39
+ *
40
+ * ```html
41
+ * <vaadin-master-detail-layout>
42
+ * <div>Master content</div>
43
+ * <div slot="detail-placeholder">Select an item</div>
44
+ * </vaadin-master-detail-layout>
45
+ * ```
46
+ *
23
47
  * ### Styling
24
48
  *
25
49
  * The following shadow DOM parts are available for styling:
26
50
  *
27
- * Part name | Description
28
- * ---------------|----------------------
29
- * `backdrop` | Backdrop covering the master area in the overlay mode
30
- * `master` | The master area
31
- * `detail` | The detail area
51
+ * Part name | Description
52
+ * ----------------------|----------------------
53
+ * `backdrop` | Backdrop covering the master area in the overlay mode
54
+ * `master` | The master area
55
+ * `detail` | The detail area
56
+ * `detail-placeholder` | The detail placeholder area
32
57
  *
33
58
  * The following state attributes are available for styling:
34
59
  *
35
- * Attribute | Description
36
- * ----------------------|----------------------
37
- * `expand` | Set to `master`, `detail`, or `both`.
38
- * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
39
- * `has-detail` | Set when the detail content is provided and visible.
40
- * `overflow` | Set when columns don't fit and the detail is shown as an overlay.
41
- * `overlay-containment` | Set to `layout` or `viewport`.
60
+ * Attribute | Description
61
+ * --------------------------|----------------------
62
+ * `expand` | Set to `master`, `detail`, or `both`.
63
+ * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
64
+ * `has-detail` | Set when the detail content is provided and visible.
65
+ * `has-detail-placeholder` | Set when the detail placeholder content is provided.
66
+ * `overlay` | Set when columns don't fit and the detail is shown as an overlay.
67
+ * `overlay-containment` | Set to `layout` or `viewport`.
42
68
  *
43
69
  * The following custom CSS properties are available for styling:
44
70
  *
@@ -55,7 +81,7 @@ export interface MasterDetailLayoutEventMap extends HTMLElementEventMap, MasterD
55
81
  * @fires {CustomEvent} backdrop-click - Fired when the user clicks the backdrop in the overlay mode.
56
82
  * @fires {CustomEvent} detail-escape-press - Fired when the user presses Escape in the detail area.
57
83
  */
58
- declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMixin(HTMLElement))) {
84
+ declare class MasterDetailLayout extends ThemableMixin(ElementMixin(HTMLElement)) {
59
85
  /**
60
86
  * Size (in CSS length units) to be set on the detail area in
61
87
  * the CSS grid layout. If there is not enough space to show
@@ -106,7 +132,7 @@ declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMi
106
132
  /**
107
133
  * Controls which column(s) expand to fill available space.
108
134
  * Possible values: `'master'`, `'detail'`, `'both'`.
109
- * Defaults to `'both'`.
135
+ * Defaults to `'master'`.
110
136
  */
111
137
  expand: 'master' | 'detail' | 'both';
112
138
 
@@ -8,10 +8,8 @@ import { getFocusableElements } from '@vaadin/a11y-base/src/focus-utils.js';
8
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
9
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
10
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
11
- import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
12
11
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
12
  import { masterDetailLayoutStyles } from './styles/vaadin-master-detail-layout-base-styles.js';
14
- import { masterDetailLayoutTransitionStyles } from './styles/vaadin-master-detail-layout-transition-base-styles.js';
15
13
 
16
14
  function parseTrackSizes(gridTemplate) {
17
15
  return gridTemplate
@@ -27,25 +25,52 @@ function parseTrackSizes(gridTemplate) {
27
25
  * (or primary) area and a detail (or secondary) area that is displayed next to, or
28
26
  * overlaid on top of, the master area, depending on configuration and viewport size.
29
27
  *
28
+ * ### Slots
29
+ *
30
+ * The component has two main content areas: the master area (default slot)
31
+ * and the detail area (`detail` slot). When the detail doesn't fit next to
32
+ * the master, it is shown as an overlay on top of the master area:
33
+ *
34
+ * ```html
35
+ * <vaadin-master-detail-layout>
36
+ * <div>Master content</div>
37
+ * <div slot="detail">Detail content</div>
38
+ * </vaadin-master-detail-layout>
39
+ * ```
40
+ *
41
+ * The component also supports a `detail-placeholder` slot for content shown
42
+ * in the detail area when no detail is selected. Unlike the `detail` slot,
43
+ * the placeholder is simply hidden when it doesn't fit next to the master area,
44
+ * rather than shown as an overlay:
45
+ *
46
+ * ```html
47
+ * <vaadin-master-detail-layout>
48
+ * <div>Master content</div>
49
+ * <div slot="detail-placeholder">Select an item</div>
50
+ * </vaadin-master-detail-layout>
51
+ * ```
52
+ *
30
53
  * ### Styling
31
54
  *
32
55
  * The following shadow DOM parts are available for styling:
33
56
  *
34
- * Part name | Description
35
- * ---------------|----------------------
36
- * `backdrop` | Backdrop covering the master area in the overlay mode
37
- * `master` | The master area
38
- * `detail` | The detail area
57
+ * Part name | Description
58
+ * ----------------------|----------------------
59
+ * `backdrop` | Backdrop covering the master area in the overlay mode
60
+ * `master` | The master area
61
+ * `detail` | The detail area
62
+ * `detail-placeholder` | The detail placeholder area
39
63
  *
40
64
  * The following state attributes are available for styling:
41
65
  *
42
- * Attribute | Description
43
- * ----------------------|----------------------
44
- * `expand` | Set to `master`, `detail`, or `both`.
45
- * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
46
- * `has-detail` | Set when the detail content is provided and visible.
47
- * `overflow` | Set when columns don't fit and the detail is shown as an overlay.
48
- * `overlay-containment` | Set to `layout` or `viewport`.
66
+ * Attribute | Description
67
+ * --------------------------|----------------------
68
+ * `expand` | Set to `master`, `detail`, or `both`.
69
+ * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
70
+ * `has-detail` | Set when the detail content is provided and visible.
71
+ * `has-detail-placeholder` | Set when the detail placeholder content is provided.
72
+ * `overlay` | Set when columns don't fit and the detail is shown as an overlay.
73
+ * `overlay-containment` | Set to `layout` or `viewport`.
49
74
  *
50
75
  * The following custom CSS properties are available for styling:
51
76
  *
@@ -66,9 +91,8 @@ function parseTrackSizes(gridTemplate) {
66
91
  * @extends HTMLElement
67
92
  * @mixes ThemableMixin
68
93
  * @mixes ElementMixin
69
- * @mixes SlotStylesMixin
70
94
  */
71
- class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
95
+ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
72
96
  static get is() {
73
97
  return 'vaadin-master-detail-layout';
74
98
  }
@@ -151,11 +175,11 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
151
175
  /**
152
176
  * Controls which column(s) expand to fill available space.
153
177
  * Possible values: `'master'`, `'detail'`, `'both'`.
154
- * Defaults to `'both'`.
178
+ * Defaults to `'master'`.
155
179
  */
156
180
  expand: {
157
181
  type: String,
158
- value: 'both',
182
+ value: 'master',
159
183
  reflectToAttribute: true,
160
184
  sync: true,
161
185
  },
@@ -168,6 +192,13 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
168
192
  noAnimation: {
169
193
  type: Boolean,
170
194
  value: false,
195
+ reflectToAttribute: true,
196
+ },
197
+
198
+ /** @private */
199
+ __replacing: {
200
+ type: Boolean,
201
+ sync: true,
171
202
  },
172
203
  };
173
204
  }
@@ -176,22 +207,20 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
176
207
  return true;
177
208
  }
178
209
 
179
- /** @return {!Array<!CSSResult>} */
180
- get slotStyles() {
181
- return [masterDetailLayoutTransitionStyles];
182
- }
183
-
184
210
  /** @protected */
185
211
  render() {
186
- const isOverlay = this.hasAttribute('has-detail') && this.hasAttribute('overflow');
212
+ const isOverlay = this.hasAttribute('has-detail') && this.hasAttribute('overlay');
187
213
  const isViewport = isOverlay && this.overlayContainment === 'viewport';
188
214
  const isLayoutContained = isOverlay && !isViewport;
189
215
 
190
216
  return html`
191
- <div part="backdrop" @click="${this.__onBackdropClick}"></div>
217
+ <div id="backdrop" part="backdrop" @click="${this.__onBackdropClick}"></div>
192
218
  <div id="master" part="master" ?inert="${isLayoutContained}">
193
219
  <slot @slotchange="${this.__onSlotChange}"></slot>
194
220
  </div>
221
+ <div id="outgoing" inert ?hidden="${!this.__replacing}">
222
+ <slot name="detail-outgoing"></slot>
223
+ </div>
195
224
  <div
196
225
  id="detail"
197
226
  part="detail"
@@ -201,6 +230,9 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
201
230
  >
202
231
  <slot name="detail" @slotchange="${this.__onSlotChange}"></slot>
203
232
  </div>
233
+ <div id="detail-placeholder" part="detail-placeholder">
234
+ <slot name="detail-placeholder" @slotchange="${this.__onSlotChange}"></slot>
235
+ </div>
204
236
  `;
205
237
  }
206
238
 
@@ -215,6 +247,7 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
215
247
  super.disconnectedCallback();
216
248
  this.__resizeObserver.disconnect();
217
249
  cancelAnimationFrame(this.__resizeRaf);
250
+ this.__endTransition();
218
251
  }
219
252
 
220
253
  /** @private */
@@ -275,20 +308,24 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
275
308
  * @private
276
309
  */
277
310
  __computeLayoutState() {
278
- const detailContent = this.querySelector('[slot="detail"]');
311
+ const detailContent = this.querySelector(':scope > [slot="detail"]');
312
+ const detailPlaceholder = this.querySelector(':scope > [slot="detail-placeholder"]');
313
+
279
314
  const hadDetail = this.hasAttribute('has-detail');
280
315
  const hasDetail = detailContent != null && detailContent.checkVisibility();
281
- const hasOverflow = hasDetail && this.__checkOverflow();
316
+ const hasDetailPlaceholder = !!detailPlaceholder;
317
+ const hasOverflow = (hasDetail || hasDetailPlaceholder) && this.__checkOverflow();
318
+
282
319
  const focusTarget = !hadDetail && hasDetail && hasOverflow ? getFocusableElements(detailContent)[0] : null;
283
- return { hadDetail, hasDetail, hasOverflow, focusTarget };
320
+ return { hadDetail, hasDetail, hasDetailPlaceholder, hasOverflow, focusTarget };
284
321
  }
285
322
 
286
323
  /**
287
324
  * Applies layout state to DOM attributes. Pure writes, no reads.
288
325
  * @private
289
326
  */
290
- __applyLayoutState({ hadDetail, hasDetail, hasOverflow, focusTarget }) {
291
- // Set keep-detail-column-offscreen when detail first appears with overflow
327
+ __applyLayoutState({ hadDetail, hasDetail, hasDetailPlaceholder, hasOverflow, focusTarget }) {
328
+ // Set keep-detail-column-offscreen when detail first appears with overlay
292
329
  // to prevent master width from jumping.
293
330
  if (!hadDetail && hasDetail && hasOverflow) {
294
331
  this.setAttribute('keep-detail-column-offscreen', '');
@@ -296,15 +333,16 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
296
333
  this.removeAttribute('keep-detail-column-offscreen');
297
334
  }
298
335
 
336
+ this.toggleAttribute('overlay', hasOverflow);
299
337
  this.toggleAttribute('has-detail', hasDetail);
300
- this.toggleAttribute('overflow', hasOverflow);
338
+ this.toggleAttribute('has-detail-placeholder', hasDetailPlaceholder);
301
339
 
302
340
  // Re-render to update ARIA attributes (role, aria-modal, inert)
303
- // which depend on has-detail and overflow state.
341
+ // which depend on has-detail and overlay state.
304
342
  this.requestUpdate();
305
343
 
306
344
  if (focusTarget) {
307
- focusTarget.focus();
345
+ focusTarget.focus({ preventScroll: true });
308
346
  }
309
347
  }
310
348
 
@@ -342,19 +380,18 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
342
380
  }
343
381
 
344
382
  /**
345
- * Sets the detail element to be displayed in the detail area and starts a
346
- * view transition that animates adding, replacing or removing the detail
347
- * area. During the view transition, the element is added to the DOM and
348
- * assigned to the `detail` slot. Any previous detail element is removed.
349
- * When passing null as the element, the current detail element is removed.
383
+ * Sets the detail element to be displayed in the detail area and starts an
384
+ * animated transition for adding, replacing or removing the detail area.
385
+ * The element is added to the DOM and assigned to the `detail` slot. Any
386
+ * previous detail element is removed. When passing null as the element,
387
+ * the current detail element is removed.
350
388
  *
351
- * If the browser does not support view transitions, the respective updates
352
- * are applied immediately without starting a transition. The transition can
353
- * also be skipped using the `skipTransition` parameter.
389
+ * The transition can be skipped using the `skipTransition` parameter or
390
+ * the `noAnimation` property.
354
391
  *
355
392
  * @param element the new detail element, or null to remove the current detail
356
393
  * @param skipTransition whether to skip the transition
357
- * @returns {Promise<void>}
394
+ * @return {Promise<void>}
358
395
  * @protected
359
396
  */
360
397
  _setDetail(element, skipTransition) {
@@ -374,13 +411,17 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
374
411
  }
375
412
  };
376
413
 
377
- if (skipTransition) {
414
+ if (skipTransition || this.noAnimation) {
378
415
  updateSlot();
416
+ queueMicrotask(() => {
417
+ const state = this.__computeLayoutState();
418
+ this.__applyLayoutState(state);
419
+ });
379
420
  return Promise.resolve();
380
421
  }
381
422
 
382
- const hasDetail = !!currentDetail;
383
- const transitionType = hasDetail && element ? 'replace' : hasDetail ? 'remove' : 'add';
423
+ const transitionType = this.__getTransitionType(currentDetail, element);
424
+
384
425
  return this._startTransition(transitionType, () => {
385
426
  // Update the DOM
386
427
  updateSlot();
@@ -390,69 +431,273 @@ class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(Poly
390
431
  }
391
432
 
392
433
  /**
393
- * Starts a view transition that animates adding, replacing or removing the
394
- * detail area. Once the transition is ready and the browser has taken a
395
- * snapshot of the current layout, the provided update callback is called.
396
- * The callback should update the DOM, which can happen asynchronously.
397
- * Once the DOM is updated, the caller must call `_finishTransition`,
398
- * which results in the browser taking a snapshot of the new layout and
399
- * animating the transition.
434
+ * Determines the transition type for a detail change.
435
+ *
436
+ * Returns 'replace' in two cases:
437
+ * - Swapping one detail for another (standard replace).
438
+ * - Swapping between placeholder and detail in split mode,
439
+ * so the swap appears instant (replace has 0ms duration in split).
440
+ * In overlay mode, placeholder doesn't participate in transitions,
441
+ * so standard 'add'/'remove' are used instead.
442
+ *
443
+ * @param {Element | null} currentDetail
444
+ * @param {Element | null} newDetail
445
+ * @return {string}
446
+ * @private
447
+ */
448
+ __getTransitionType(currentDetail, newDetail) {
449
+ if (currentDetail && newDetail) {
450
+ return 'replace';
451
+ }
452
+
453
+ const hasPlaceholder = !!this.querySelector('[slot="detail-placeholder"]');
454
+ if (hasPlaceholder && !this.hasAttribute('overlay')) {
455
+ return 'replace';
456
+ }
457
+
458
+ return currentDetail ? 'remove' : 'add';
459
+ }
460
+
461
+ /**
462
+ * Starts an animated transition for adding, replacing or removing the
463
+ * detail area using the Web Animations API.
464
+ *
465
+ * For 'remove', the DOM update is deferred until the slide-out completes.
466
+ * For 'add'/'replace', the DOM is updated immediately and the slide-in
467
+ * plays on the new content.
400
468
  *
401
- * If the browser does not support view transitions, or the `noAnimation`
402
- * property is set, the update callback is called immediately without
403
- * starting a transition.
469
+ * Animations are interruptible: starting a new transition cancels any
470
+ * in-progress animation and the new animation picks up from the
471
+ * interrupted position (see `__captureDetailState`).
404
472
  *
405
473
  * @param transitionType
406
474
  * @param updateCallback
407
- * @returns {Promise<void>}
475
+ * @return {Promise<void>}
408
476
  * @protected
409
477
  */
410
478
  _startTransition(transitionType, updateCallback) {
411
- const useTransition = typeof document.startViewTransition === 'function' && !this.noAnimation;
412
- if (!useTransition) {
479
+ if (this.noAnimation) {
413
480
  updateCallback();
414
481
  return Promise.resolve();
415
482
  }
416
483
 
484
+ // Capture mid-flight state before cancelling active animations
485
+ const interrupted = this.__captureDetailState();
486
+
487
+ this.__endTransition();
488
+
489
+ if (transitionType === 'replace') {
490
+ this.__snapshotOutgoing();
491
+ }
492
+
417
493
  this.setAttribute('transition', transitionType);
418
- this.__transition = document.startViewTransition(() => {
419
- // Return a promise that can be resolved once the DOM is updated
420
- return new Promise((resolve) => {
421
- this.__resolveUpdateCallback = resolve;
422
- // Notify the caller that the transition is ready, so that they can
423
- // update the DOM
424
- updateCallback();
425
- });
494
+
495
+ if (transitionType !== 'remove') {
496
+ updateCallback();
497
+ }
498
+
499
+ const opts = this.__getAnimationParams();
500
+ opts.interrupted = interrupted;
501
+ opts.overlay = this.hasAttribute('overlay');
502
+
503
+ return this.__animateTransition(transitionType, opts, updateCallback);
504
+ }
505
+
506
+ /**
507
+ * Creates slide animation(s) for the given transition type and returns
508
+ * a promise that resolves when the primary animation completes.
509
+ * A version counter prevents stale callbacks from executing after
510
+ * a newer transition has started.
511
+ *
512
+ * @param {string} transitionType
513
+ * @param {{ offscreen: string, duration: number, easing: string, interrupted?: { translate: string, opacity: string }, overlay?: boolean }} opts
514
+ * @param {Function} updateCallback
515
+ * @return {Promise<void>}
516
+ * @private
517
+ */
518
+ __animateTransition(transitionType, opts, updateCallback) {
519
+ const version = (this.__transitionVersion = (this.__transitionVersion || 0) + 1);
520
+
521
+ return new Promise((resolve) => {
522
+ this.__transitionResolve = resolve;
523
+
524
+ const onFinish = (callback) => {
525
+ if (this.__transitionVersion === version) {
526
+ if (callback) {
527
+ callback();
528
+ }
529
+ this.__endTransition();
530
+ }
531
+ };
532
+
533
+ if (transitionType === 'remove') {
534
+ this.__slide(this.$.detail, false, opts).then(() => onFinish(updateCallback));
535
+ } else if (transitionType === 'replace') {
536
+ // Outgoing slides out on top (z-index), revealing incoming underneath.
537
+ // In overlay mode, the incoming also slides in simultaneously.
538
+ this.__slide(this.$.outgoing, false, opts).then(() => onFinish());
539
+ if (opts.overlay) {
540
+ this.__slide(this.$.detail, true, { ...opts, interrupted: null });
541
+ }
542
+ } else {
543
+ this.__slide(this.$.detail, true, opts).then(() => onFinish());
544
+ }
545
+
546
+ // Fade backdrop in/out for overlay add/remove (not replace — backdrop stays visible)
547
+ if (opts.overlay && transitionType !== 'replace') {
548
+ const fadeIn = transitionType !== 'remove';
549
+ this.__animate(this.$.backdrop, [{ opacity: fadeIn ? 0 : 1 }, { opacity: fadeIn ? 1 : 0 }], {
550
+ duration: opts.duration,
551
+ easing: 'linear',
552
+ });
553
+ }
426
554
  });
427
- return this.__transition.finished;
428
555
  }
429
556
 
430
557
  /**
431
- * Finishes the current view transition, if any. This method should be called
432
- * after the DOM has been updated to finish the transition and animate the
433
- * change in the layout.
558
+ * Finishes the current transition by detecting and applying the layout
559
+ * state. This method should be called after the DOM has been updated.
434
560
  *
435
- * @returns {Promise<void>}
436
561
  * @protected
437
562
  */
438
- async _finishTransition() {
439
- // Detect layout mode before resolving the transition, so the browser's
440
- // "new" snapshot includes the correct overlay state. The microtask runs
441
- // before the Promise resolution propagates to startViewTransition.
442
- queueMicrotask(() => {
443
- const state = this.__computeLayoutState();
444
- this.__applyLayoutState(state);
445
- });
563
+ _finishTransition() {
564
+ const state = this.__computeLayoutState();
565
+ this.__applyLayoutState(state);
566
+ }
567
+
568
+ /**
569
+ * Captures the detail panel's current animated state (translate and
570
+ * opacity). Must be called BEFORE `animation.cancel()`, because
571
+ * cancel removes the animation effect and the element reverts to
572
+ * its CSS resting state.
573
+ *
574
+ * Returns null when there is no active animation.
575
+ *
576
+ * @return {{ translate: string, opacity: string } | null}
577
+ * @private
578
+ */
579
+ __captureDetailState() {
580
+ if (!this.__activeAnimations || this.__activeAnimations.length === 0) {
581
+ return null;
582
+ }
583
+ const { translate, opacity } = getComputedStyle(this.$.detail);
584
+ return { translate, opacity };
585
+ }
586
+
587
+ /**
588
+ * Reads animation parameters from CSS custom properties. Called once
589
+ * per transition so that animating stays free of layout reads.
590
+ *
591
+ * @return {{ offscreen: string, duration: number, easing: string }}
592
+ * @private
593
+ */
594
+ __getAnimationParams() {
595
+ const cs = getComputedStyle(this);
596
+ const offscreen = cs.getPropertyValue('--_detail-offscreen').trim();
597
+ const durationStr = cs.getPropertyValue('--_transition-duration').trim();
598
+ const duration = durationStr.endsWith('ms') ? parseFloat(durationStr) : parseFloat(durationStr) * 1000;
599
+ const easing = cs.getPropertyValue('--_transition-easing').trim();
600
+ return { offscreen, duration, easing };
601
+ }
446
602
 
447
- if (!this.__transition) {
603
+ /**
604
+ * Creates a slide animation on the element's `translate` property
605
+ * using the Web Animations API. Returns a promise that resolves when
606
+ * the animation finishes, or immediately if the duration is 0.
607
+ *
608
+ * @param {HTMLElement} element - The element to animate
609
+ * @param {boolean} slideIn - If true, slide in (off-screen → on-screen);
610
+ * otherwise slide out (on-screen → off-screen)
611
+ * @param {{ offscreen: string, duration: number, easing: string, interrupted?: { translate: string, opacity: string }, overlay?: boolean }} opts
612
+ * Animation parameters. `interrupted` overrides the default starting
613
+ * keyframe for interrupted animations (captured mid-flight before cancel).
614
+ * @return {Promise<void>}
615
+ * @private
616
+ */
617
+ __slide(element, slideIn, { offscreen, duration, easing, interrupted, overlay }) {
618
+ if (!offscreen || duration <= 0) {
448
619
  return Promise.resolve();
449
620
  }
450
- // Resolve the update callback to finish the transition
451
- this.__resolveUpdateCallback();
452
- await this.__transition.finished;
621
+
622
+ const defaultTranslate = slideIn ? offscreen : 'none';
623
+ const defaultOpacity = !overlay && slideIn ? 0 : 1;
624
+
625
+ const start = interrupted ? interrupted.translate : defaultTranslate;
626
+ const end = slideIn ? 'none' : offscreen;
627
+
628
+ const opacityStart = interrupted ? Number(interrupted.opacity) : defaultOpacity;
629
+ const opacityEnd = !overlay && !slideIn ? 0 : 1;
630
+
631
+ return this.__animate(
632
+ element,
633
+ [
634
+ { translate: start, opacity: opacityStart },
635
+ { translate: end, opacity: opacityEnd },
636
+ ],
637
+ { duration, easing },
638
+ );
639
+ }
640
+
641
+ /**
642
+ * Runs a Web Animation on the given element, tracks it for cancellation,
643
+ * and returns a promise that resolves when finished (or swallows the
644
+ * rejection if cancelled).
645
+ *
646
+ * @param {HTMLElement} element
647
+ * @param {Keyframe[]} keyframes
648
+ * @param {KeyframeAnimationOptions} options
649
+ * @return {Promise<void>}
650
+ * @private
651
+ */
652
+ __animate(element, keyframes, options) {
653
+ const animation = element.animate(keyframes, options);
654
+
655
+ this.__activeAnimations = this.__activeAnimations || [];
656
+ this.__activeAnimations.push(animation);
657
+
658
+ return animation.finished.catch(() => {});
659
+ }
660
+
661
+ /**
662
+ * Cancels in-progress animations, cleans up state, and resolves the
663
+ * pending transition promise.
664
+ * @private
665
+ */
666
+ __endTransition() {
667
+ if (this.__activeAnimations) {
668
+ this.__activeAnimations.forEach((a) => a.cancel());
669
+ this.__activeAnimations = null;
670
+ }
453
671
  this.removeAttribute('transition');
454
- this.__transition = null;
455
- this.__resolveUpdateCallback = null;
672
+ this.__clearOutgoing();
673
+ if (this.__transitionResolve) {
674
+ this.__transitionResolve();
675
+ this.__transitionResolve = null;
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Moves the current detail content to the outgoing slot so it can
681
+ * slide out while the new content slides in. Keeps the element in
682
+ * light DOM so light DOM styles continue to apply.
683
+ * @private
684
+ */
685
+ __snapshotOutgoing() {
686
+ const currentDetail = this.querySelector('[slot="detail"]');
687
+ if (!currentDetail) {
688
+ return;
689
+ }
690
+ currentDetail.setAttribute('slot', 'detail-outgoing');
691
+ this.__replacing = true;
692
+ }
693
+
694
+ /**
695
+ * Clears the outgoing container after the replace transition completes.
696
+ * @private
697
+ */
698
+ __clearOutgoing() {
699
+ this.querySelectorAll('[slot="detail-outgoing"]').forEach((el) => el.remove());
700
+ this.__replacing = false;
456
701
  }
457
702
 
458
703
  /**
package/web-types.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/master-detail-layout",
4
- "version": "25.2.0-alpha1",
4
+ "version": "25.2.0-alpha2",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
8
8
  "elements": [
9
9
  {
10
10
  "name": "vaadin-master-detail-layout",
11
- "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n----------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`overflow` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
11
+ "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Slots\n\nThe component has two main content areas: the master area (default slot)\nand the detail area (`detail` slot). When the detail doesn't fit next to\nthe master, it is shown as an overlay on top of the master area:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail\">Detail content</div>\n</vaadin-master-detail-layout>\n```\n\nThe component also supports a `detail-placeholder` slot for content shown\nin the detail area when no detail is selected. Unlike the `detail` slot,\nthe placeholder is simply hidden when it doesn't fit next to the master area,\nrather than shown as an overlay:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail-placeholder\">Select an item</div>\n</vaadin-master-detail-layout>\n```\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n----------------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n`detail-placeholder` | The detail placeholder area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`has-detail-placeholder` | Set when the detail placeholder content is provided.\n`overlay` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
12
12
  "attributes": [
13
13
  {
14
14
  "name": "detail-size",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  {
25
25
  "name": "expand",
26
- "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'both'`.",
26
+ "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'master'`.",
27
27
  "value": {
28
28
  "type": [
29
29
  "string",
@@ -114,7 +114,7 @@
114
114
  },
115
115
  {
116
116
  "name": "expand",
117
- "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'both'`.",
117
+ "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'master'`.",
118
118
  "value": {
119
119
  "type": [
120
120
  "string",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/master-detail-layout",
4
- "version": "25.2.0-alpha1",
4
+ "version": "25.2.0-alpha2",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -16,7 +16,7 @@
16
16
  "elements": [
17
17
  {
18
18
  "name": "vaadin-master-detail-layout",
19
- "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n----------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`overflow` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
19
+ "description": "`<vaadin-master-detail-layout>` is a web component for building UIs with a master\n(or primary) area and a detail (or secondary) area that is displayed next to, or\noverlaid on top of, the master area, depending on configuration and viewport size.\n\n### Slots\n\nThe component has two main content areas: the master area (default slot)\nand the detail area (`detail` slot). When the detail doesn't fit next to\nthe master, it is shown as an overlay on top of the master area:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail\">Detail content</div>\n</vaadin-master-detail-layout>\n```\n\nThe component also supports a `detail-placeholder` slot for content shown\nin the detail area when no detail is selected. Unlike the `detail` slot,\nthe placeholder is simply hidden when it doesn't fit next to the master area,\nrather than shown as an overlay:\n\n```html\n<vaadin-master-detail-layout>\n <div>Master content</div>\n <div slot=\"detail-placeholder\">Select an item</div>\n</vaadin-master-detail-layout>\n```\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n----------------------|----------------------\n`backdrop` | Backdrop covering the master area in the overlay mode\n`master` | The master area\n`detail` | The detail area\n`detail-placeholder` | The detail placeholder area\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------------|----------------------\n`expand` | Set to `master`, `detail`, or `both`.\n`orientation` | Set to `horizontal` or `vertical` depending on the orientation.\n`has-detail` | Set when the detail content is provided and visible.\n`has-detail-placeholder` | Set when the detail placeholder content is provided.\n`overlay` | Set when columns don't fit and the detail is shown as an overlay.\n`overlay-containment` | Set to `layout` or `viewport`.\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:----------------------------------------------------|\n| `--vaadin-master-detail-layout-border-color` |\n| `--vaadin-master-detail-layout-border-width` |\n| `--vaadin-master-detail-layout-detail-background` |\n| `--vaadin-master-detail-layout-detail-shadow` |\n| `--vaadin-overlay-backdrop-background` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
20
20
  "extension": true,
21
21
  "attributes": [
22
22
  {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  {
37
37
  "name": ".expand",
38
- "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'both'`.",
38
+ "description": "Controls which column(s) expand to fill available space.\nPossible values: `'master'`, `'detail'`, `'both'`.\nDefaults to `'master'`.",
39
39
  "value": {
40
40
  "kind": "expression"
41
41
  }
@@ -1,107 +0,0 @@
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
- import { css } from 'lit';
7
-
8
- export const masterDetailLayoutTransitionStyles = css`
9
- @media (prefers-reduced-motion: no-preference) {
10
- html {
11
- --_vaadin-mdl-dir-multiplier: 1;
12
- --_vaadin-mdl-easing: cubic-bezier(0.78, 0, 0.22, 1);
13
- }
14
-
15
- html[dir='rtl'] {
16
- --_vaadin-mdl-dir-multiplier: -1;
17
- }
18
-
19
- ::view-transition-group(vaadin-mdl-backdrop),
20
- ::view-transition-group(vaadin-mdl-master),
21
- ::view-transition-group(vaadin-mdl-detail) {
22
- animation-duration: 0.4s;
23
- }
24
-
25
- ::view-transition-group(vaadin-mdl-master),
26
- ::view-transition-group(vaadin-mdl-detail) {
27
- animation-timing-function: var(--_vaadin-mdl-easing);
28
- }
29
-
30
- ::view-transition-image-pair(vaadin-mdl-master),
31
- ::view-transition-image-pair(vaadin-mdl-detail),
32
- ::view-transition-new(vaadin-mdl-master),
33
- ::view-transition-new(vaadin-mdl-detail),
34
- ::view-transition-old(vaadin-mdl-master),
35
- ::view-transition-old(vaadin-mdl-detail) {
36
- animation-timing-function: inherit;
37
- }
38
-
39
- /* Needed to promote the backdrop on top the master during the transition */
40
- vaadin-master-detail-layout[transition]::part(backdrop) {
41
- view-transition-name: vaadin-mdl-backdrop;
42
- }
43
-
44
- vaadin-master-detail-layout[transition][has-detail]:not([transition='replace']):not([overflow])::part(detail),
45
- vaadin-master-detail-layout[transition][has-detail][overflow]::part(detail) {
46
- view-transition-name: vaadin-mdl-detail;
47
- }
48
-
49
- ::view-transition-group(vaadin-mdl-detail) {
50
- clip-path: inset(0);
51
- }
52
-
53
- ::view-transition-new(vaadin-mdl-detail),
54
- ::view-transition-old(vaadin-mdl-detail) {
55
- animation-name: vaadin-mdl-detail-slide-in;
56
- }
57
-
58
- ::view-transition-old(vaadin-mdl-detail) {
59
- animation-direction: reverse;
60
- }
61
-
62
- @keyframes vaadin-mdl-detail-slide-in {
63
- 0% {
64
- translate: calc((100% + 30px) * var(--_vaadin-mdl-dir-multiplier));
65
- }
66
- }
67
-
68
- vaadin-master-detail-layout[transition]::part(master) {
69
- view-transition-name: vaadin-mdl-master;
70
- }
71
-
72
- ::view-transition-new(vaadin-mdl-master),
73
- ::view-transition-old(vaadin-mdl-master) {
74
- object-fit: none;
75
- object-position: 0% 0;
76
- width: 100%;
77
- height: 100%;
78
- }
79
-
80
- :dir(rtl)::view-transition-new(vaadin-mdl-master),
81
- :dir(rtl)::view-transition-old(vaadin-mdl-master) {
82
- object-position: 100% 0;
83
- }
84
-
85
- /* prettier-ignore */
86
- vaadin-master-detail-layout[orientation='vertical'][has-detail]:not([overflow])[transition]:not([transition='replace'])::part(detail),
87
- vaadin-master-detail-layout[orientation='vertical'][has-detail][overflow][transition]::part(detail) {
88
- view-transition-name: vaadin-mdl-detail;
89
- view-transition-class: vertical;
90
- }
91
-
92
- ::view-transition-new(vaadin-mdl-detail.vertical),
93
- ::view-transition-old(vaadin-mdl-detail.vertical) {
94
- animation-name: vaadin-mdl-vertical-detail-slide-in;
95
- }
96
-
97
- ::view-transition-old(vaadin-mdl-detail.vertical) {
98
- animation-direction: reverse;
99
- }
100
-
101
- @keyframes vaadin-mdl-vertical-detail-slide-in {
102
- 0% {
103
- transform: translateY(calc(100% + 30px));
104
- }
105
- }
106
- }
107
- `;