@vaadin/master-detail-layout 25.2.0-alpha6 → 25.2.0-alpha7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/master-detail-layout",
3
- "version": "25.2.0-alpha6",
3
+ "version": "25.2.0-alpha7",
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-alpha6",
38
- "@vaadin/component-base": "25.2.0-alpha6",
39
- "@vaadin/vaadin-themable-mixin": "25.2.0-alpha6",
37
+ "@vaadin/a11y-base": "25.2.0-alpha7",
38
+ "@vaadin/component-base": "25.2.0-alpha7",
39
+ "@vaadin/vaadin-themable-mixin": "25.2.0-alpha7",
40
40
  "lit": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@vaadin/aura": "25.2.0-alpha6",
44
- "@vaadin/chai-plugins": "25.2.0-alpha6",
43
+ "@vaadin/aura": "25.2.0-alpha7",
44
+ "@vaadin/chai-plugins": "25.2.0-alpha7",
45
45
  "@vaadin/testing-helpers": "^2.0.0",
46
- "@vaadin/vaadin-lumo-styles": "25.2.0-alpha6",
46
+ "@vaadin/vaadin-lumo-styles": "25.2.0-alpha7",
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": "30f23c65765f27616f2db292406d5759a7e987c3"
54
+ "gitHead": "3f0862906d60205d107836d8eca84c6fde4a9129"
55
55
  }
@@ -51,17 +51,17 @@ export const masterDetailLayoutStyles = css`
51
51
  [detail-end];
52
52
  }
53
53
 
54
- :is(#master, #detail, #detail-placeholder, #outgoing) {
54
+ :is(#master, #detail, #detailPlaceholder, #detailOutgoing) {
55
55
  box-sizing: border-box;
56
56
  }
57
57
 
58
- #detail-placeholder {
58
+ #detailPlaceholder {
59
59
  z-index: 1;
60
60
  opacity: 0;
61
61
  pointer-events: none;
62
62
  }
63
63
 
64
- :host([has-detail-placeholder]:not([has-detail], [overlay])) #detail-placeholder {
64
+ :host([has-detail-placeholder]:not([has-detail], [overlay])) #detailPlaceholder {
65
65
  opacity: 1;
66
66
  pointer-events: auto;
67
67
  }
@@ -69,9 +69,16 @@ export const masterDetailLayoutStyles = css`
69
69
  #master {
70
70
  grid-column: master-start / detail-start;
71
71
  grid-row: 1;
72
+ opacity: 0;
73
+ pointer-events: none;
74
+ }
75
+
76
+ :host([has-master]) #master {
77
+ opacity: 1;
78
+ pointer-events: auto;
72
79
  }
73
80
 
74
- :is(#detail, #detail-placeholder, #outgoing) {
81
+ :is(#detail, #detailPlaceholder, #detailOutgoing) {
75
82
  grid-column: detail-start / detail-end;
76
83
  grid-row: 1;
77
84
  }
@@ -81,7 +88,7 @@ export const masterDetailLayoutStyles = css`
81
88
  grid-row: master-start / detail-start;
82
89
  }
83
90
 
84
- :host([orientation='vertical']) :is(#detail, #detail-placeholder, #outgoing) {
91
+ :host([orientation='vertical']) :is(#detail, #detailPlaceholder, #detailOutgoing) {
85
92
  grid-column: 1;
86
93
  grid-row: detail-start / detail-end;
87
94
  }
@@ -113,26 +120,31 @@ export const masterDetailLayoutStyles = css`
113
120
  }
114
121
 
115
122
  :host([keep-detail-column-offscreen]),
116
- :host([has-detail-placeholder][overlay]),
123
+ :host([has-detail-placeholder][overlay]:not([has-detail])),
117
124
  :host(:not([has-detail-placeholder], [has-detail])) {
118
125
  --_master-extra: calc(100% - var(--_master-size));
119
126
  }
120
127
 
121
- :host([orientation='horizontal']) #detail-placeholder,
128
+ :host([orientation='horizontal']) #detailPlaceholder,
122
129
  :host([orientation='horizontal']:not([overlay])) #detail {
123
130
  border-inline-start: var(--vaadin-master-detail-layout-border-width, 1px) solid
124
131
  var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
125
132
  }
126
133
 
127
- :host([orientation='vertical']) #detail-placeholder,
134
+ :host([orientation='vertical']) #detailPlaceholder,
128
135
  :host([orientation='vertical']:not([overlay])) #detail {
129
136
  border-top: var(--vaadin-master-detail-layout-border-width, 1px) solid
130
137
  var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
131
138
  }
132
139
 
133
- #outgoing {
140
+ #detailOutgoing {
134
141
  position: absolute;
135
142
  z-index: 3;
143
+ display: none;
144
+ }
145
+
146
+ :host([transition='replace']) #detailOutgoing {
147
+ display: block;
136
148
  }
137
149
 
138
150
  /* Detail transition: off-screen by default, on-screen when has-detail */
@@ -155,7 +167,7 @@ export const masterDetailLayoutStyles = css`
155
167
  --_transition-offset: 0 calc(100% + 30px);
156
168
  }
157
169
 
158
- :host([has-detail][overlay]) :is(#detail, #outgoing) {
170
+ :host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
159
171
  position: absolute;
160
172
  background: var(--vaadin-master-detail-layout-detail-background, var(--vaadin-background-color));
161
173
  box-shadow: var(--vaadin-master-detail-layout-detail-shadow, 0 0 20px 0 rgba(0, 0, 0, 0.3));
@@ -168,30 +180,30 @@ export const masterDetailLayoutStyles = css`
168
180
  pointer-events: auto;
169
181
  }
170
182
 
171
- :host([has-detail][overlay]:not([orientation='vertical'])) :is(#detail, #outgoing) {
183
+ :host([has-detail][overlay]:not([orientation='vertical'])) :is(#detail, #detailOutgoing) {
172
184
  inset-block: 0;
173
185
  inset-inline-end: 0;
174
186
  width: var(--_overlay-size, var(--_detail-size));
175
187
  max-width: 100%;
176
188
  }
177
189
 
178
- :host([has-detail][overlay][orientation='vertical']) :is(#detail, #outgoing) {
190
+ :host([has-detail][overlay][orientation='vertical']) :is(#detail, #detailOutgoing) {
179
191
  inset-inline: 0;
180
192
  inset-block-end: 0;
181
193
  height: var(--_overlay-size, var(--_detail-size));
182
194
  max-height: 100%;
183
195
  }
184
196
 
185
- :host([has-detail][overlay][overlay-containment='viewport']) :is(#detail, #outgoing, #backdrop) {
197
+ :host([has-detail][overlay][overlay-containment='viewport']) :is(#detail, #detailOutgoing, #backdrop) {
186
198
  position: fixed;
187
199
  }
188
200
 
189
201
  @media (forced-colors: active) {
190
- :host([has-detail][overlay]) :is(#detail, #outgoing) {
202
+ :host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
191
203
  outline: 3px solid !important;
192
204
  }
193
205
 
194
- :is(#detail, #detail-placeholder, #outgoing) {
206
+ :is(#detail, #detailPlaceholder, #detailOutgoing) {
195
207
  background: Canvas !important;
196
208
  }
197
209
  }
@@ -4,7 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { html, LitElement, nothing } from 'lit';
7
- import { getFocusableElements } from '@vaadin/a11y-base/src/focus-utils.js';
7
+ import { getFocusableElements, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
8
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
9
  import { getClosestElement } from '@vaadin/component-base/src/dom-utils.js';
10
10
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
@@ -202,12 +202,6 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
202
202
  reflectToAttribute: true,
203
203
  },
204
204
 
205
- /** @private */
206
- __replacing: {
207
- type: Boolean,
208
- sync: true,
209
- },
210
-
211
205
  /** @private */
212
206
  __detailCachedSize: {
213
207
  type: String,
@@ -232,7 +226,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
232
226
  <div id="master" part="master" ?inert="${isLayoutContained}">
233
227
  <slot @slotchange="${this.__onSlotChange}"></slot>
234
228
  </div>
235
- <div id="outgoing" inert ?hidden="${!this.__replacing}">
229
+ <div id="detailOutgoing" inert>
236
230
  <slot name="detail-outgoing"></slot>
237
231
  </div>
238
232
  <div
@@ -244,7 +238,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
244
238
  >
245
239
  <slot name="detail" @slotchange="${this.__onSlotChange}"></slot>
246
240
  </div>
247
- <div id="detail-placeholder" part="detail-placeholder">
241
+ <div id="detailPlaceholder" part="detail-placeholder">
248
242
  <slot name="detail-placeholder" @slotchange="${this.__onSlotChange}"></slot>
249
243
  </div>
250
244
  `;
@@ -254,6 +248,17 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
254
248
  connectedCallback() {
255
249
  super.connectedCallback();
256
250
  this.__initResizeObserver();
251
+
252
+ const ancestorLayouts = this.__ancestorLayouts;
253
+ if (ancestorLayouts.length > 0) {
254
+ ancestorLayouts.forEach((layout) => {
255
+ cancelAnimationFrame(layout.__initialRaf);
256
+ });
257
+
258
+ this.__initialRaf = requestAnimationFrame(() => {
259
+ this.recalculateLayout();
260
+ });
261
+ }
257
262
  }
258
263
 
259
264
  /** @protected */
@@ -261,6 +266,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
261
266
  super.disconnectedCallback();
262
267
  this.__resizeObserver.disconnect();
263
268
  cancelAnimationFrame(this.__resizeRaf);
269
+ cancelAnimationFrame(this.__initialRaf);
264
270
  cancelAnimations(this);
265
271
  }
266
272
 
@@ -318,9 +324,10 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
318
324
  this.__resizeObserver = this.__resizeObserver || new ResizeObserver(() => this.__onResize());
319
325
  this.__resizeObserver.disconnect();
320
326
 
321
- const children = this.querySelectorAll(':scope > [slot="detail"], :scope >:not([slot])');
322
- [this, this.$.master, this.$.detail, ...children].forEach((node) => {
323
- this.__resizeObserver.observe(node);
327
+ [this, this.$.master, this.$.detail, this.__slottedMaster, this.__slottedDetail].forEach((node) => {
328
+ if (node) {
329
+ this.__resizeObserver.observe(node);
330
+ }
324
331
  });
325
332
  }
326
333
 
@@ -344,12 +351,14 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
344
351
  __readLayoutState() {
345
352
  const isVertical = this.orientation === 'vertical';
346
353
 
347
- const detailContent = this.querySelector(':scope > [slot="detail"]');
348
- const detailPlaceholder = this.querySelector(':scope > [slot="detail-placeholder"]');
354
+ const slottedMaster = this.__slottedMaster;
355
+ const slottedDetail = this.__slottedDetail;
356
+ const slottedDetailPlaceholder = this.__slottedDetailPlaceholder;
349
357
 
358
+ const hasMaster = !!slottedMaster;
350
359
  const hadDetail = this.hasAttribute('has-detail');
351
- const hasDetail = detailContent != null && detailContent.checkVisibility();
352
- const hasDetailPlaceholder = !!detailPlaceholder;
360
+ const hasDetail = slottedDetail != null && slottedDetail.checkVisibility();
361
+ const hasDetailPlaceholder = !!slottedDetailPlaceholder;
353
362
 
354
363
  const computedStyle = getComputedStyle(this);
355
364
  const hostSizeProp = isVertical ? 'height' : 'width';
@@ -359,9 +368,10 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
359
368
  const trackSizes = parseTrackSizes(computedStyle[trackSizesProp]);
360
369
 
361
370
  const hasOverflow = (hasDetail || hasDetailPlaceholder) && detectOverflow(hostSize, trackSizes);
362
- const focusTarget = !hadDetail && hasDetail && hasOverflow ? getFocusableElements(detailContent)[0] : null;
371
+ const focusTarget = !hadDetail && hasDetail && hasOverflow ? getFocusableElements(slottedDetail)[0] : null;
363
372
 
364
373
  return {
374
+ hasMaster,
365
375
  hadDetail,
366
376
  hasDetail,
367
377
  hasDetailPlaceholder,
@@ -376,7 +386,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
376
386
  * Applies layout state to DOM attributes. Pure writes, no reads.
377
387
  * @private
378
388
  */
379
- __writeLayoutState({ hadDetail, hasDetail, hasDetailPlaceholder, hasOverflow, focusTarget, trackSizes }) {
389
+ __writeLayoutState({ hasMaster, hadDetail, hasDetail, hasDetailPlaceholder, hasOverflow, focusTarget, trackSizes }) {
380
390
  const [_masterSize, _masterExtra, detailSize] = trackSizes;
381
391
 
382
392
  // If no detailSize is explicitily set, cache the intrinsic size (min-content) of
@@ -398,6 +408,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
398
408
  }
399
409
 
400
410
  this.toggleAttribute('overlay', hasOverflow);
411
+ this.toggleAttribute('has-master', hasMaster);
401
412
  this.toggleAttribute('has-detail', hasDetail);
402
413
  this.toggleAttribute('has-detail-placeholder', hasDetailPlaceholder);
403
414
 
@@ -406,7 +417,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
406
417
  this.requestUpdate();
407
418
 
408
419
  if (focusTarget) {
409
- focusTarget.focus({ preventScroll: true });
420
+ focusTarget.focus({ preventScroll: true, focusVisible: isKeyboardActive() });
410
421
  }
411
422
  }
412
423
 
@@ -424,14 +435,14 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
424
435
  * synchronous DOM reads and writes.
425
436
  */
426
437
  recalculateLayout() {
427
- // Cancel any pending ResizeObserver rAF to prevent it from potentially
428
- // overriding the layout state with stale measurements.
429
- cancelAnimationFrame(this.__resizeRaf);
430
-
431
- const invalidatedLayouts = [...this.__ancestorLayouts.filter((layout) => layout.__isDetailAutoSized), this];
438
+ const invalidatedLayouts = [...this.__ancestorLayouts, this];
432
439
 
433
440
  // Write
434
441
  invalidatedLayouts.forEach((layout) => {
442
+ // Cancel any pending ResizeObserver rAF to prevent it from potentially
443
+ // overriding the layout state with stale measurements.
444
+ cancelAnimationFrame(layout.__resizeRaf);
445
+
435
446
  layout.__detailCachedSize = null;
436
447
 
437
448
  if (layout.__isDetailAutoSized) {
@@ -496,7 +507,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
496
507
  */
497
508
  async _setDetail(newDetail, skipTransition) {
498
509
  // Don't start a transition if detail didn't change
499
- const oldDetail = this.querySelector('[slot="detail"]');
510
+ const oldDetail = this.__slottedDetail;
500
511
  if (oldDetail === (newDetail || null)) {
501
512
  return;
502
513
  }
@@ -522,7 +533,7 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
522
533
  return;
523
534
  }
524
535
 
525
- const hasPlaceholder = !!this.querySelector('[slot="detail-placeholder"]');
536
+ const hasPlaceholder = !!this.__slottedDetailPlaceholder;
526
537
  if ((oldDetail && newDetail) || (hasPlaceholder && !this.hasAttribute('overlay'))) {
527
538
  await this._startTransition('replace', updateSlot);
528
539
  } else if (!oldDetail && newDetail) {
@@ -594,18 +605,27 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
594
605
 
595
606
  /** @private */
596
607
  async __replaceTransition(updateSlot) {
608
+ const oldDetail = this.__slottedDetail;
609
+ if (oldDetail) {
610
+ oldDetail.slot = 'detail-outgoing';
611
+ }
612
+
597
613
  try {
598
- this.__snapshotOutgoing();
614
+ this.$.detailOutgoing.style.width = this.__detailCachedSize;
599
615
 
600
616
  await updateSlot();
601
617
 
602
618
  const progress = getCurrentAnimationProgress(this.$.detail);
603
619
  await Promise.all([
604
620
  animateIn(this.$.detail, ['fade', 'slide'], progress),
605
- animateOut(this.$.outgoing, ['fade', 'slide'], progress),
621
+ animateOut(this.$.detailOutgoing, ['fade', 'slide'], progress),
606
622
  ]);
607
623
  } finally {
608
- this.__clearOutgoing();
624
+ // Skip removal if the slot was reassigned during the transition.
625
+ // The React component does this to let React handle the removal.
626
+ if (oldDetail && oldDetail.slot === 'detail-outgoing') {
627
+ oldDetail.remove();
628
+ }
609
629
  }
610
630
  }
611
631
 
@@ -620,30 +640,19 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
620
640
  await updateSlot();
621
641
  }
622
642
 
623
- /**
624
- * Moves the current detail content to the outgoing slot so it can
625
- * slide out while the new content slides in. Keeps the element in
626
- * light DOM so light DOM styles continue to apply.
627
- * @private
628
- */
629
- __snapshotOutgoing() {
630
- const currentDetail = this.querySelector('[slot="detail"]');
631
- if (!currentDetail) {
632
- return;
633
- }
634
- currentDetail.setAttribute('slot', 'detail-outgoing');
635
- this.$.outgoing.style.width = this.__detailCachedSize;
636
- this.__replacing = true;
643
+ /** @private */
644
+ get __slottedMaster() {
645
+ return this.querySelector(':scope > :is([slot=""], :not([slot]))');
637
646
  }
638
647
 
639
- /**
640
- * Clears the outgoing container after the replace transition completes.
641
- * @private
642
- */
643
- __clearOutgoing() {
644
- this.querySelectorAll('[slot="detail-outgoing"]').forEach((el) => el.remove());
645
- this.$.outgoing.style.width = '';
646
- this.__replacing = false;
648
+ /** @private */
649
+ get __slottedDetail() {
650
+ return this.querySelector(':scope > [slot="detail"]');
651
+ }
652
+
653
+ /** @private */
654
+ get __slottedDetailPlaceholder() {
655
+ return this.querySelector(':scope > [slot="detail-placeholder"]');
647
656
  }
648
657
 
649
658
  /**
package/web-types.json CHANGED
@@ -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-alpha6",
4
+ "version": "25.2.0-alpha7",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -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-alpha6",
4
+ "version": "25.2.0-alpha7",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {