diff --git a/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js index bf635dcd34..3ac14d4c6b 100644 --- a/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js +++ b/packages/horizontal-layout/src/vaadin-horizontal-layout-mixin.js @@ -30,9 +30,13 @@ export const HorizontalLayoutMixin = (superClass) => const children = currentNodes.filter((node) => node.nodeType === Node.ELEMENT_NODE); - if (children.length) { - children[children.length - 1].setAttribute('last-start-child', ''); - } + children.forEach((child, idx) => { + if (idx === children.length - 1) { + child.setAttribute('last-start-child', ''); + } else if (child.hasAttribute('last-start-child')) { + child.removeAttribute('last-start-child'); + } + }); const nodes = currentNodes.filter((node) => !isEmptyTextNode(node)); this.toggleAttribute('has-start', nodes.length > 0); @@ -49,9 +53,13 @@ export const HorizontalLayoutMixin = (superClass) => } } - if (currentNodes.length) { - currentNodes[0].setAttribute('first-end-child', ''); - } + currentNodes.forEach((child, idx) => { + if (idx === 0) { + child.setAttribute('first-end-child', ''); + } else if (child.hasAttribute('first-end-child')) { + child.removeAttribute('first-end-child'); + } + }); this.toggleAttribute('has-end', currentNodes.length > 0); @@ -72,10 +80,19 @@ export const HorizontalLayoutMixin = (superClass) => } } - if (currentNodes.length) { - currentNodes[0].setAttribute('first-middle-child', ''); - currentNodes[currentNodes.length - 1].setAttribute('last-middle-child', ''); - } + currentNodes.forEach((child, idx) => { + if (idx === 0) { + child.setAttribute('first-middle-child', ''); + } else if (child.hasAttribute('first-middle-child')) { + child.removeAttribute('first-middle-child'); + } + + if (idx === currentNodes.length - 1) { + child.setAttribute('last-middle-child', ''); + } else if (child.hasAttribute('last-middle-child')) { + child.removeAttribute('last-middle-child'); + } + }); this.toggleAttribute('has-middle', currentNodes.length > 0); diff --git a/packages/horizontal-layout/test/horizontal-layout.common.js b/packages/horizontal-layout/test/horizontal-layout.common.js index d1a99bcf16..61c14d4b9d 100644 --- a/packages/horizontal-layout/test/horizontal-layout.common.js +++ b/packages/horizontal-layout/test/horizontal-layout.common.js @@ -110,4 +110,178 @@ describe('vaadin-horizontal-layout', () => { expect(wrapper.scrollWidth).to.equal(200); }); }); + + 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; + }); + }); + }); });