From 1470d9cb4331c2f384c000c2e2ec29d64923524a Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Thu, 13 Feb 2025 11:14:14 +0200 Subject: [PATCH] feat: add middle and end slots to horizontal layout (#8515) --- dev/horizontal-layout.html | 74 ++++++++ .../src/vaadin-horizontal-layout-mixin.d.ts | 8 + .../src/vaadin-horizontal-layout-mixin.js | 85 +++++++++ .../src/vaadin-horizontal-layout-styles.js | 16 ++ .../src/vaadin-horizontal-layout.d.ts | 13 +- .../src/vaadin-horizontal-layout.js | 20 +- .../src/vaadin-lit-horizontal-layout.js | 9 +- .../test/horizontal-layout.test.js | 174 ++++++++++++++++++ .../visual/lumo/horizontal-layout.test.js | 74 ++++++++ .../horizontal-layout/baseline/slots-all.png | Bin 0 -> 1601 bytes .../baseline/slots-without-end.png | Bin 0 -> 1190 bytes .../baseline/slots-without-middle.png | Bin 0 -> 1025 bytes .../baseline/slots-without-start.png | Bin 0 -> 1109 bytes .../visual/material/horizontal-layout.test.js | 74 ++++++++ .../horizontal-layout/baseline/slots-all.png | Bin 0 -> 1601 bytes .../baseline/slots-without-end.png | Bin 0 -> 1190 bytes .../baseline/slots-without-middle.png | Bin 0 -> 1025 bytes .../baseline/slots-without-start.png | Bin 0 -> 1109 bytes 18 files changed, 542 insertions(+), 5 deletions(-) create mode 100644 dev/horizontal-layout.html create mode 100644 packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.d.ts create mode 100644 packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js create mode 100644 packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-all.png create mode 100644 packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-end.png create mode 100644 packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-middle.png create mode 100644 packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-start.png create mode 100644 packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-all.png create mode 100644 packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-end.png create mode 100644 packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-middle.png create mode 100644 packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-start.png diff --git a/dev/horizontal-layout.html b/dev/horizontal-layout.html new file mode 100644 index 00000000000..2d7e8c4216f --- /dev/null +++ b/dev/horizontal-layout.html @@ -0,0 +1,74 @@ + + + + + + + Horizontal layout + + + + + + + +
Start
+
Start
+
Middle
+
End
+
+ +
+ + +
Start
+
Start
+
Middle
+
End
+
+ +
+ + +
Start
+
Start
+
Middle
+
End
+
+ +
+ + +
Start
+
Start
+
Middle
+
+ +
+ + +
Start
+
Start
+
Middle
+
+ + +
+ + +
Middle
+
End
+
End
+
+ +
+ + +
Middle
+
End
+
End
+
+ + diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.d.ts b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.d.ts new file mode 100644 index 00000000000..f4275e1fa6f --- /dev/null +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.d.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright (c) 2017 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import type { Constructor } from '@open-wc/dedupe-mixin'; + +export declare function HorizontalLayoutMixin>(base: T): T; diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js new file mode 100644 index 00000000000..f66fc4a948b --- /dev/null +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js @@ -0,0 +1,85 @@ +/** + * @license + * Copyright (c) 2017 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { isEmptyTextNode } from '@vaadin/component-base/src/dom-utils.js'; +import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js'; + +/** + * @polymerMixin + */ +export const HorizontalLayoutMixin = (superClass) => + class extends superClass { + /** @protected */ + ready() { + super.ready(); + + const startSlot = this.shadowRoot.querySelector('slot:not([name])'); + this.__startSlotObserver = new SlotObserver(startSlot, ({ currentNodes, removedNodes }) => { + if (removedNodes.length) { + this.__clearAttribute(removedNodes, 'last-start-child'); + } + + const children = currentNodes.filter((node) => node.nodeType === Node.ELEMENT_NODE); + this.__updateAttributes(children, 'start', false, true); + + const nodes = currentNodes.filter((node) => !isEmptyTextNode(node)); + this.toggleAttribute('has-start', nodes.length > 0); + }); + + const endSlot = this.shadowRoot.querySelector('[name="end"]'); + this.__endSlotObserver = new SlotObserver(endSlot, ({ currentNodes, removedNodes }) => { + if (removedNodes.length) { + this.__clearAttribute(removedNodes, 'first-end-child'); + } + + this.__updateAttributes(currentNodes, 'end', true, false); + + this.toggleAttribute('has-end', currentNodes.length > 0); + }); + + const middleSlot = this.shadowRoot.querySelector('[name="middle"]'); + this.__middleSlotObserver = new SlotObserver(middleSlot, ({ currentNodes, removedNodes }) => { + if (removedNodes.length) { + this.__clearAttribute(removedNodes, 'first-middle-child'); + this.__clearAttribute(removedNodes, 'last-middle-child'); + } + + this.__updateAttributes(currentNodes, 'middle', true, true); + + this.toggleAttribute('has-middle', currentNodes.length > 0); + }); + } + + /** @private */ + __clearAttribute(nodes, attr) { + const el = nodes.find((node) => node.nodeType === Node.ELEMENT_NODE && node.hasAttribute(attr)); + if (el) { + el.removeAttribute(attr); + } + } + + /** @private */ + __updateAttributes(nodes, slot, setFirst, setLast) { + nodes.forEach((child, idx) => { + if (setFirst) { + const attr = `first-${slot}-child`; + if (idx === 0) { + child.setAttribute(attr, ''); + } else if (child.hasAttribute(attr)) { + child.removeAttribute(attr); + } + } + + if (setLast) { + const attr = `last-${slot}-child`; + if (idx === nodes.length - 1) { + child.setAttribute(attr, ''); + } else if (child.hasAttribute(attr)) { + child.removeAttribute(attr); + } + } + }); + } + }; diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout-styles.js b/packages/horizontal-layout/src/vaadin-horizontal-layout-styles.js index e70405bcd55..2e87f6bd54c 100644 --- a/packages/horizontal-layout/src/vaadin-horizontal-layout-styles.js +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout-styles.js @@ -27,6 +27,22 @@ export const baseStyles = css` :host([theme~='spacing']) { gap: 1em; } + + :host([has-end]:not([has-middle])) ::slotted([last-start-child]) { + margin-inline-end: auto; + } + + ::slotted([first-middle-child]) { + margin-inline-start: auto; + } + + ::slotted([last-middle-child]) { + margin-inline-end: auto; + } + + :host([has-start]:not([has-middle])) ::slotted([first-end-child]) { + margin-inline-start: auto; + } `; // Layout improvements are part of a feature for Flow users where children that have been configured to use full size diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout.d.ts b/packages/horizontal-layout/src/vaadin-horizontal-layout.d.ts index 9beab0b178d..5bfb0e91232 100644 --- a/packages/horizontal-layout/src/vaadin-horizontal-layout.d.ts +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout.d.ts @@ -5,6 +5,7 @@ */ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { HorizontalLayoutMixin } from './vaadin-horizontal-layout-mixin.js'; /** * `` provides a simple way to horizontally align your HTML elements. @@ -26,8 +27,18 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix * `theme="padding"` | Applies the default amount of CSS padding for the host element (specified by the theme) * `theme="spacing"` | Applies the default amount of CSS margin between items (specified by the theme) * `theme="wrap"` | Items wrap to the next row when they exceed the layout width + * + * ### Component's slots + * + * The following slots are available to be set: + * + * Slot name | Description + * -------------------|--------------- + * no name | Default slot + * `middle` | Slot for the content placed in the middle + * `end` | Slot for the content placed at the end */ -declare class HorizontalLayout extends ThemableMixin(ElementMixin(HTMLElement)) {} +declare class HorizontalLayout extends HorizontalLayoutMixin(ThemableMixin(ElementMixin(HTMLElement))) {} declare global { interface HTMLElementTagNameMap { diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout.js b/packages/horizontal-layout/src/vaadin-horizontal-layout.js index 8cc85759a8d..0a5eaf5ccff 100644 --- a/packages/horizontal-layout/src/vaadin-horizontal-layout.js +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout.js @@ -7,6 +7,7 @@ import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { HorizontalLayoutMixin } from './vaadin-horizontal-layout-mixin.js'; import { horizontalLayoutStyles } from './vaadin-horizontal-layout-styles.js'; registerStyles('vaadin-horizontal-layout', horizontalLayoutStyles, { moduleId: 'vaadin-horizontal-layout-styles' }); @@ -32,14 +33,29 @@ registerStyles('vaadin-horizontal-layout', horizontalLayoutStyles, { moduleId: ' * `theme="spacing"` | Applies the default amount of CSS margin between items (specified by the theme) * `theme="wrap"` | Items wrap to the next row when they exceed the layout width * + * ### Component's slots + * + * The following slots are available to be set: + * + * Slot name | Description + * -------------------|--------------- + * no name | Default slot + * `middle` | Slot for the content placed in the middle + * `end` | Slot for the content placed at the end + * * @customElement * @extends HTMLElement * @mixes ThemableMixin * @mixes ElementMixin + * @mixes HorizontalLayoutMixin */ -class HorizontalLayout extends ElementMixin(ThemableMixin(PolymerElement)) { +class HorizontalLayout extends HorizontalLayoutMixin(ElementMixin(ThemableMixin(PolymerElement))) { static get template() { - return html``; + return html` + + + + `; } static get is() { diff --git a/packages/horizontal-layout/src/vaadin-lit-horizontal-layout.js b/packages/horizontal-layout/src/vaadin-lit-horizontal-layout.js index 47645c1ff44..b7a445e7680 100644 --- a/packages/horizontal-layout/src/vaadin-lit-horizontal-layout.js +++ b/packages/horizontal-layout/src/vaadin-lit-horizontal-layout.js @@ -8,6 +8,7 @@ import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { HorizontalLayoutMixin } from './vaadin-horizontal-layout-mixin.js'; import { horizontalLayoutStyles } from './vaadin-horizontal-layout-styles.js'; /** @@ -19,7 +20,7 @@ import { horizontalLayoutStyles } from './vaadin-horizontal-layout-styles.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -class HorizontalLayout extends ThemableMixin(ElementMixin(PolylitMixin(LitElement))) { +class HorizontalLayout extends HorizontalLayoutMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-horizontal-layout'; } @@ -30,7 +31,11 @@ class HorizontalLayout extends ThemableMixin(ElementMixin(PolylitMixin(LitElemen /** @protected */ render() { - return html``; + return html` + + + + `; } } diff --git a/packages/horizontal-layout/test/horizontal-layout.test.js b/packages/horizontal-layout/test/horizontal-layout.test.js index 1e0dae3878a..e2af9b44873 100644 --- a/packages/horizontal-layout/test/horizontal-layout.test.js +++ b/packages/horizontal-layout/test/horizontal-layout.test.js @@ -159,4 +159,178 @@ describe('vaadin-horizontal-layout', () => { }); }); }); + + describe('slots', () => { + let layout; + + beforeEach(() => { + layout = fixtureSync(''); + }); + + describe('start', () => { + it('should set has-start attribute when element added to default slot', async () => { + const div = document.createElement('div'); + layout.appendChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-start')).to.be.true; + }); + + it('should remove has-start attribute when element removed from default slot', async () => { + const div = document.createElement('div'); + layout.appendChild(div); + await nextFrame(); + + layout.removeChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-start')).to.be.false; + }); + + it('should set last-start-child attribute on last element in the default slot', async () => { + const div = document.createElement('div'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('last-start-child')).to.be.true; + + const other = document.createElement('div'); + layout.appendChild(other); + await nextFrame(); + expect(div.hasAttribute('last-start-child')).to.be.false; + expect(other.hasAttribute('last-start-child')).to.be.true; + }); + + it('should remove last-start-child attribute when element is removed', async () => { + const div = document.createElement('div'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('last-start-child')).to.be.true; + + layout.removeChild(div); + await nextFrame(); + expect(div.hasAttribute('last-start-child')).to.be.false; + }); + }); + + describe('middle', () => { + it('should set has-middle attribute when element added to middle slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-middle')).to.be.true; + }); + + it('should remove has-middle attribute when element removed from middle slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + + layout.removeChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-middle')).to.be.false; + }); + + it('should set first-middle-child attribute on first element in the middle slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('first-middle-child')).to.be.true; + + const other = document.createElement('div'); + other.setAttribute('slot', 'middle'); + layout.insertBefore(other, div); + await nextFrame(); + expect(div.hasAttribute('first-middle-child')).to.be.false; + expect(other.hasAttribute('first-middle-child')).to.be.true; + }); + + it('should set last-middle-child attribute on last element in the middle slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('last-middle-child')).to.be.true; + + const other = document.createElement('div'); + other.setAttribute('slot', 'middle'); + layout.appendChild(other); + await nextFrame(); + expect(div.hasAttribute('last-middle-child')).to.be.false; + expect(other.hasAttribute('last-middle-child')).to.be.true; + }); + + it('should remove first-middle-child attribute when element is removed', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('first-middle-child')).to.be.true; + + layout.removeChild(div); + await nextFrame(); + expect(div.hasAttribute('first-middle-child')).to.be.false; + }); + + it('should remove last-middle-child attribute when element is removed', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'middle'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('last-middle-child')).to.be.true; + + layout.removeChild(div); + await nextFrame(); + expect(div.hasAttribute('last-middle-child')).to.be.false; + }); + }); + + describe('end', () => { + it('should set has-end attribute when element added to end slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'end'); + layout.appendChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-end')).to.be.true; + }); + + it('should remove has-end attribute when element removed from end slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'end'); + layout.appendChild(div); + await nextFrame(); + + layout.removeChild(div); + await nextFrame(); + expect(layout.hasAttribute('has-end')).to.be.false; + }); + + it('should set first-end-child attribute on first element in the end slot', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'end'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('first-end-child')).to.be.true; + + const other = document.createElement('div'); + other.setAttribute('slot', 'end'); + layout.insertBefore(other, div); + await nextFrame(); + expect(div.hasAttribute('first-end-child')).to.be.false; + expect(other.hasAttribute('first-end-child')).to.be.true; + }); + + it('should remove first-end-child attribute when element is removed', async () => { + const div = document.createElement('div'); + div.setAttribute('slot', 'end'); + layout.appendChild(div); + await nextFrame(); + expect(div.hasAttribute('first-end-child')).to.be.true; + + layout.removeChild(div); + await nextFrame(); + expect(div.hasAttribute('first-end-child')).to.be.false; + }); + }); + }); }); diff --git a/packages/horizontal-layout/test/visual/lumo/horizontal-layout.test.js b/packages/horizontal-layout/test/visual/lumo/horizontal-layout.test.js index 4af9483dcef..539716c3d49 100644 --- a/packages/horizontal-layout/test/visual/lumo/horizontal-layout.test.js +++ b/packages/horizontal-layout/test/visual/lumo/horizontal-layout.test.js @@ -78,4 +78,78 @@ describe('horizontal-layout', () => { element.style.width = '100px'; await visualDiff(div, 'theme-wrap'); }); + + describe('slots', () => { + describe('all', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
Start
+
Middle
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-all'); + }); + }); + + describe('without end', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
Start
+
Middle
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-end'); + }); + }); + + describe('without start', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Middle
+
End
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-start'); + }); + }); + + describe('without middle', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-middle'); + }); + }); + }); }); diff --git a/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-all.png b/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-all.png new file mode 100644 index 0000000000000000000000000000000000000000..c1192941c321e29666e911850eeadb0965f3760f GIT binary patch literal 1601 zcmV-H2EO@;P)Px)`$=qQ9m+#+=_1B#{&2GXg!>Ucc*6cP2A%qakB!N%}Aw*j{Q}`j5xDkx50bB@& z{BJ)oaJd{!ZQAJ}eJff)IyR2)F~HS(EW7`)f1lw?tN`%C4g>oPU&8wx-m}#eXKk>+ zWdDoGW8=i!wf>6uKcp99n*V8y9alzgf8E!4%%j9Nv>^i7!JZRtUF*pQ!jvA2G8W3% zEs$G^sF$}q>#khmVyt(r&sdcW*AU0({Eh#{isVd8fuvW^GKt z%nQrSh?2H>_bEX@jM2-3Qy(lUX=cAdGKlLk6_RwaT0tO+5?YnXp>` zu#EWibx&EHl%c2UJL+D-SjwGbGhboSx1zPodo7D%2()HC*n!8Ks`f7d0%q4C`pPytS zUun{}qQy8^a55DpT~?NkgPYzawn{YG!S>>Av)j9Z+$db!plA%qDA8t5IGlHlD9Gymmt8AnKLP{!-f5y{~E4w-5HVV^&SG zYO%$Gid|Msvu?Ce*eoQ=I9&RU60t?M0zox^XI*zWfL>Q7q}&KbKTvhG7jlUkQNkg8 z#KK-8Vv9bB+g%vyskIF0ThVIzS#aVDb90_Qn@pZ>^cf11F{ByAjp@K3 zn4vCw2l(bLqqnn~XEAoypWMAR*aHWPvG9+hx3hhn>~3G*fYYBSL*?TWU3Py)H}7Dr zUalIxm@M|MbpMEjxS`sjTV|QVrLTM7!kAB{RgNZoD_V>>DExLGAQr}3mB!r9;X-L^ za~baC(?I3H1)>yf5qH7%=YKaUo_ee`^y1*S_Gq@CxA z(QY^CXLQa?EA8nWX;!QMcV-~WC6)@`1syIu6B=`~6?jr3i^H70`~F=$qk8_iSJWVV zD_T-NGvHf>B)<1*JR?!=+qtrhv!!9*QRUT~{ISDf@$>P2ZM*mvx_bN0y{t|=Wt44| z1<2E`Fd|%fR(5H&*e7aUic^Ly`p&wpt1>{ThxDyzNjLAo9K&H|UiMULualME>L{=F zm^yQ#MFVv9UF(6}jr&L6`#Wbn&(rc-iS42|HM*)RM&X||mw2#B@ZDYji}C>gdx=CG z;Q6mPfcUec(;XGXyz3rMNJ0ll--_0<`Fs9{ZZ|Wq0yoPZjjsx9seOPC-7uF=he@w7 z1yep|lSh_U9IBrMCl|*{Nvbi)muZ%R?dxG)@Bl?uZ{HP+Au~hS8T$x^Qpn~Hs>Pv6ZYdHBY~daYePz$+sva^ccs!xiRk$!Uv^DbxfD2Px(U`a$lRCt{2+_7sLcNDrTEgQ+kPI7OS*-ir&!?vc_q(Te;tXgagb;pk4baZosKfA$#m^Uqn!oms<3DjN||bNt3k!GDx(eTQzE$)uA|}tMi3*& zT)Ef&6Xt^6g#FfL@ubJZ&^IXlAMjhCvLvkxx z#^UQV3jjN1j?V4g-)0AROtZ!`aGmWM@$22wmV=~w9ld?LxIkj}(&HaJcfqP3W{E9M z1po-#o7XR5ZFB0Dt`B$AW82zlTxOU)Cb<>ea*tt`r(Gq|3Cn+wo8j(P1OELV7pRDR$tSb?d<80CD%ZL*xHTc*-k$ZBOOs{+UNrs=g~)PvSZSh1A3X>VD|ROc%KP+f^aC73YOIi>xa9D@?afsA44AHcyyKPDwscT$HEc)A_kMmgcP&= zNp8hCZjtr!1faM3=ktgXYgQz$UC*z=UBTfzJ{FSJ&2!5@a?R`l_31y-5cAlI)r zY8jtl=j{;V-F6xBZuAv(`jxPxO{?{h8unU=%)~fAz`O~-v29lX-u&GFs2ujcy=9ti z@33M^V;Cg2qTBvE@5NY@9PGfu(AnOu1*m=ka3>0@UhnCsOd8S&maBcoI}R11FvG2d zvzwAd-}c?Fd4KOI{S3=g+TF~k(@VYi^&}~+eotnfjXN1isf z9(h{Be@dR#AcPPfl`L372;YzeO9h($ literal 0 HcmV?d00001 diff --git a/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-middle.png b/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-middle.png new file mode 100644 index 0000000000000000000000000000000000000000..238a3ff6c0955d83170c61513eb71e7acbe696e6 GIT binary patch literal 1025 zcmV+c1pfPpP)Px&yGcYrRCt{2+&^sMRvZWLZ>k%S#d)EXC}B9MK_ZKLY;-y!q{I_2q)NnscYG>K z7vi`>m#Fu$n-11pl_6pBorK~}rAr)HkwChflmYHM9E(L!_UqvArz&^lR4zW?_nC0s zll9BMSI_VF@EY(Agb=a_?g3!lQP5>9jPm_*&?S(i^4GyZ&?PL4u)6<6&@~W3$gC)^ z2q6pO-tfb~*{d2a0AN$sN?)H_5XDrWr4h3H-ku(-rK>3e+l9)rWN5~_*iAJ9xu1^C zxw_BKZD4g+)j(161P&O~Yf82yHN>E6BV_4)ncq?KZZoXoOihnh2siEB%RVquVA)MI zV*#u2ef1v+JJ%YdvcLfVxLk=fq=`8li*-Kku06-959V1WAuA=6d^YoPDV0{wRwJ!O zfyMJ%Fpr#Z#-zs2gB`}qkGto<`N8PUvq(Z#$xZvz4Z4QUmH$Y{31!L18dcud)U{HM z8;*7?-cKv0XpkCAj5)Q0(eekkirQ0w17KT9wp-NK4cWw_?>2t_;|jowsv>U|UzUVO zQ2!bsi>cp#J-%?CsQN{r@(avwPOgBBT4g668V?-+5aqMLYQ1a2P4|?@Uh=hs(UPl* z45`7y6o9zsY5ZQ*c&IQX4h)<<1G>g?9C}VIVVv872usPD0F2jyEH{3DVB?KZu z$rE5(O4c;9%CyAcgr`inT~e6<*8QYwK*@HC-^cH~ctVzjINEgYwU2+h{bTg8Dnr#T zh8@G$D3#pH5(=;UHexiM1D*#|_Z{>1um(hX8<{yE;B_uptspZo|qnU4JsO080-ym%q8V2E1e5Pfpzvav1gmfvPfE zMh)tf*wJ7z;>ac^ba%sgWyKt)lM?vHp9B*U)G{0r4=~o9#;4Lz@oP`%!&ew%on|_VRrPk v213Xpp}-=9EDQ=PLde2cqgNyoLKel}xW!dyKy<`m00000NkvXXu0mjfUwQm7 literal 0 HcmV?d00001 diff --git a/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-start.png b/packages/horizontal-layout/test/visual/lumo/screenshots/horizontal-layout/baseline/slots-without-start.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f8077cb974df69328b7cd5736676ff237bdf61 GIT binary patch literal 1109 zcmV-b1giUqP)Px(4@pEpRCt{2oIhw=R}_aom5hdJF;-GxJX#>A7K05U6l5=Q!6Mf19$pMqHV1VK=Sd!u&Xb`-5`^fQ391Jc3&T`v)O$O!;%FIX%QdWiN0ZMw5# z!UIX7zMJgUHde6Pf0-1DDa+Wp>~di>yz|^U)#qK#v&IUMvfn*q@6<+OI~a-zmPW4qm2BJn5j0BhfU;?JWOUDT?>t{ssK z49DAXW6Jxy@1OJqzwv4^M$~YS*!Dne!D4}AfL)Vfi>-JLLFPv2)g(~KrwXI?VO){e z_C|5R0xi-x$vS>Yv||Nl@MB9*xRwT>)k@9LwW`kDVP6hHVV%SVg?Tw;2tQLGE1L!83N4Nl++yPOzYILx1=q>$AgmjW` z(5A_ql7PjV?d@3G5TklTE!CBHKy?1DpYZD;vtnc#jabAFwP9oK0WbFn@05)nQ`Y{L zU0C%1!Bi<;E*SlJ?jgeEZw+O$l zZFRZl9>E_Y@>>NYwtb=ek`Ta09P~8U=-+p#U1;U?VM}AWIgRRN?I$yv){bCQ|z zuDhX-ebzgFGV7e@vNate^oPW@H);!3?w|S>SCBZIz-CJcXomn@l>mMS!eKqFUOZ(N zc6AbVPkY;~d?W)bZ8tY1tx3+iSMnPsH*CJo%z0PItkhD%mHaOm$e0tAA-+8l+a5Te zs^H7dMQizft3pG&QIn2mD*xuKKx$u&J?mvGgE8+=6<;~ z0oB+8zWgk)9RQ~hQ&h0zw1zK=oG8#ga$3XPA*VG6f}j#2Sb`uZg9w%&2+H7)Ba#I{ bP=)^hXp7w$KrG4)00000NkvXXu0mjf@EjZw literal 0 HcmV?d00001 diff --git a/packages/horizontal-layout/test/visual/material/horizontal-layout.test.js b/packages/horizontal-layout/test/visual/material/horizontal-layout.test.js index 55243c61d1c..c0a61938bb2 100644 --- a/packages/horizontal-layout/test/visual/material/horizontal-layout.test.js +++ b/packages/horizontal-layout/test/visual/material/horizontal-layout.test.js @@ -58,4 +58,78 @@ describe('horizontal-layout', () => { element.style.width = '100px'; await visualDiff(div, 'theme-wrap'); }); + + describe('slots', () => { + describe('all', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
Start
+
Middle
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-all'); + }); + }); + + describe('without end', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
Start
+
Middle
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-end'); + }); + }); + + describe('without start', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Middle
+
End
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-start'); + }); + }); + + describe('without middle', () => { + beforeEach(() => { + element = fixtureSync( + ` + +
Start
+
End
+
+ `, + ); + }); + + it('default', async () => { + await visualDiff(element, 'slots-without-middle'); + }); + }); + }); }); diff --git a/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-all.png b/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-all.png new file mode 100644 index 0000000000000000000000000000000000000000..c1192941c321e29666e911850eeadb0965f3760f GIT binary patch literal 1601 zcmV-H2EO@;P)Px)`$=qQ9m+#+=_1B#{&2GXg!>Ucc*6cP2A%qakB!N%}Aw*j{Q}`j5xDkx50bB@& z{BJ)oaJd{!ZQAJ}eJff)IyR2)F~HS(EW7`)f1lw?tN`%C4g>oPU&8wx-m}#eXKk>+ zWdDoGW8=i!wf>6uKcp99n*V8y9alzgf8E!4%%j9Nv>^i7!JZRtUF*pQ!jvA2G8W3% zEs$G^sF$}q>#khmVyt(r&sdcW*AU0({Eh#{isVd8fuvW^GKt z%nQrSh?2H>_bEX@jM2-3Qy(lUX=cAdGKlLk6_RwaT0tO+5?YnXp>` zu#EWibx&EHl%c2UJL+D-SjwGbGhboSx1zPodo7D%2()HC*n!8Ks`f7d0%q4C`pPytS zUun{}qQy8^a55DpT~?NkgPYzawn{YG!S>>Av)j9Z+$db!plA%qDA8t5IGlHlD9Gymmt8AnKLP{!-f5y{~E4w-5HVV^&SG zYO%$Gid|Msvu?Ce*eoQ=I9&RU60t?M0zox^XI*zWfL>Q7q}&KbKTvhG7jlUkQNkg8 z#KK-8Vv9bB+g%vyskIF0ThVIzS#aVDb90_Qn@pZ>^cf11F{ByAjp@K3 zn4vCw2l(bLqqnn~XEAoypWMAR*aHWPvG9+hx3hhn>~3G*fYYBSL*?TWU3Py)H}7Dr zUalIxm@M|MbpMEjxS`sjTV|QVrLTM7!kAB{RgNZoD_V>>DExLGAQr}3mB!r9;X-L^ za~baC(?I3H1)>yf5qH7%=YKaUo_ee`^y1*S_Gq@CxA z(QY^CXLQa?EA8nWX;!QMcV-~WC6)@`1syIu6B=`~6?jr3i^H70`~F=$qk8_iSJWVV zD_T-NGvHf>B)<1*JR?!=+qtrhv!!9*QRUT~{ISDf@$>P2ZM*mvx_bN0y{t|=Wt44| z1<2E`Fd|%fR(5H&*e7aUic^Ly`p&wpt1>{ThxDyzNjLAo9K&H|UiMULualME>L{=F zm^yQ#MFVv9UF(6}jr&L6`#Wbn&(rc-iS42|HM*)RM&X||mw2#B@ZDYji}C>gdx=CG z;Q6mPfcUec(;XGXyz3rMNJ0ll--_0<`Fs9{ZZ|Wq0yoPZjjsx9seOPC-7uF=he@w7 z1yep|lSh_U9IBrMCl|*{Nvbi)muZ%R?dxG)@Bl?uZ{HP+Au~hS8T$x^Qpn~Hs>Pv6ZYdHBY~daYePz$+sva^ccs!xiRk$!Uv^DbxfD2Px(U`a$lRCt{2+_7sLcNDrTEgQ+kPI7OS*-ir&!?vc_q(Te;tXgagb;pk4baZosKfA$#m^Uqn!oms<3DjN||bNt3k!GDx(eTQzE$)uA|}tMi3*& zT)Ef&6Xt^6g#FfL@ubJZ&^IXlAMjhCvLvkxx z#^UQV3jjN1j?V4g-)0AROtZ!`aGmWM@$22wmV=~w9ld?LxIkj}(&HaJcfqP3W{E9M z1po-#o7XR5ZFB0Dt`B$AW82zlTxOU)Cb<>ea*tt`r(Gq|3Cn+wo8j(P1OELV7pRDR$tSb?d<80CD%ZL*xHTc*-k$ZBOOs{+UNrs=g~)PvSZSh1A3X>VD|ROc%KP+f^aC73YOIi>xa9D@?afsA44AHcyyKPDwscT$HEc)A_kMmgcP&= zNp8hCZjtr!1faM3=ktgXYgQz$UC*z=UBTfzJ{FSJ&2!5@a?R`l_31y-5cAlI)r zY8jtl=j{;V-F6xBZuAv(`jxPxO{?{h8unU=%)~fAz`O~-v29lX-u&GFs2ujcy=9ti z@33M^V;Cg2qTBvE@5NY@9PGfu(AnOu1*m=ka3>0@UhnCsOd8S&maBcoI}R11FvG2d zvzwAd-}c?Fd4KOI{S3=g+TF~k(@VYi^&}~+eotnfjXN1isf z9(h{Be@dR#AcPPfl`L372;YzeO9h($ literal 0 HcmV?d00001 diff --git a/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-middle.png b/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-middle.png new file mode 100644 index 0000000000000000000000000000000000000000..238a3ff6c0955d83170c61513eb71e7acbe696e6 GIT binary patch literal 1025 zcmV+c1pfPpP)Px&yGcYrRCt{2+&^sMRvZWLZ>k%S#d)EXC}B9MK_ZKLY;-y!q{I_2q)NnscYG>K z7vi`>m#Fu$n-11pl_6pBorK~}rAr)HkwChflmYHM9E(L!_UqvArz&^lR4zW?_nC0s zll9BMSI_VF@EY(Agb=a_?g3!lQP5>9jPm_*&?S(i^4GyZ&?PL4u)6<6&@~W3$gC)^ z2q6pO-tfb~*{d2a0AN$sN?)H_5XDrWr4h3H-ku(-rK>3e+l9)rWN5~_*iAJ9xu1^C zxw_BKZD4g+)j(161P&O~Yf82yHN>E6BV_4)ncq?KZZoXoOihnh2siEB%RVquVA)MI zV*#u2ef1v+JJ%YdvcLfVxLk=fq=`8li*-Kku06-959V1WAuA=6d^YoPDV0{wRwJ!O zfyMJ%Fpr#Z#-zs2gB`}qkGto<`N8PUvq(Z#$xZvz4Z4QUmH$Y{31!L18dcud)U{HM z8;*7?-cKv0XpkCAj5)Q0(eekkirQ0w17KT9wp-NK4cWw_?>2t_;|jowsv>U|UzUVO zQ2!bsi>cp#J-%?CsQN{r@(avwPOgBBT4g668V?-+5aqMLYQ1a2P4|?@Uh=hs(UPl* z45`7y6o9zsY5ZQ*c&IQX4h)<<1G>g?9C}VIVVv872usPD0F2jyEH{3DVB?KZu z$rE5(O4c;9%CyAcgr`inT~e6<*8QYwK*@HC-^cH~ctVzjINEgYwU2+h{bTg8Dnr#T zh8@G$D3#pH5(=;UHexiM1D*#|_Z{>1um(hX8<{yE;B_uptspZo|qnU4JsO080-ym%q8V2E1e5Pfpzvav1gmfvPfE zMh)tf*wJ7z;>ac^ba%sgWyKt)lM?vHp9B*U)G{0r4=~o9#;4Lz@oP`%!&ew%on|_VRrPk v213Xpp}-=9EDQ=PLde2cqgNyoLKel}xW!dyKy<`m00000NkvXXu0mjfUwQm7 literal 0 HcmV?d00001 diff --git a/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-start.png b/packages/horizontal-layout/test/visual/material/screenshots/horizontal-layout/baseline/slots-without-start.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f8077cb974df69328b7cd5736676ff237bdf61 GIT binary patch literal 1109 zcmV-b1giUqP)Px(4@pEpRCt{2oIhw=R}_aom5hdJF;-GxJX#>A7K05U6l5=Q!6Mf19$pMqHV1VK=Sd!u&Xb`-5`^fQ391Jc3&T`v)O$O!;%FIX%QdWiN0ZMw5# z!UIX7zMJgUHde6Pf0-1DDa+Wp>~di>yz|^U)#qK#v&IUMvfn*q@6<+OI~a-zmPW4qm2BJn5j0BhfU;?JWOUDT?>t{ssK z49DAXW6Jxy@1OJqzwv4^M$~YS*!Dne!D4}AfL)Vfi>-JLLFPv2)g(~KrwXI?VO){e z_C|5R0xi-x$vS>Yv||Nl@MB9*xRwT>)k@9LwW`kDVP6hHVV%SVg?Tw;2tQLGE1L!83N4Nl++yPOzYILx1=q>$AgmjW` z(5A_ql7PjV?d@3G5TklTE!CBHKy?1DpYZD;vtnc#jabAFwP9oK0WbFn@05)nQ`Y{L zU0C%1!Bi<;E*SlJ?jgeEZw+O$l zZFRZl9>E_Y@>>NYwtb=ek`Ta09P~8U=-+p#U1;U?VM}AWIgRRN?I$yv){bCQ|z zuDhX-ebzgFGV7e@vNate^oPW@H);!3?w|S>SCBZIz-CJcXomn@l>mMS!eKqFUOZ(N zc6AbVPkY;~d?W)bZ8tY1tx3+iSMnPsH*CJo%z0PItkhD%mHaOm$e0tAA-+8l+a5Te zs^H7dMQizft3pG&QIn2mD*xuKKx$u&J?mvGgE8+=6<;~ z0oB+8zWgk)9RQ~hQ&h0zw1zK=oG8#ga$3XPA*VG6f}j#2Sb`uZg9w%&2+H7)Ba#I{ bP=)^hXp7w$KrG4)00000NkvXXu0mjf@EjZw literal 0 HcmV?d00001