diff --git a/src/components/details/details.component.ts b/src/components/details/details.component.ts index 88a3f40a..68d06681 100644 --- a/src/components/details/details.component.ts +++ b/src/components/details/details.component.ts @@ -47,11 +47,13 @@ export default class SlDetails extends ShoelaceElement { private readonly localize = new LocalizeController(this); - @query('.details') details: HTMLElement; + @query('.details') details: HTMLDetailsElement; @query('.details__header') header: HTMLElement; @query('.details__body') body: HTMLElement; @query('.details__expand-icon-slot') expandIconSlot: HTMLSlotElement; + detailsObserver: MutationObserver; + /** * Indicates whether or not the details is open. You can toggle this attribute to show and hide the details, or you * can use the `show()` and `hide()` methods and this attribute will reflect the details' open state. @@ -65,11 +67,31 @@ export default class SlDetails extends ShoelaceElement { @property({ type: Boolean, reflect: true }) disabled = false; firstUpdated() { - this.body.hidden = !this.open; this.body.style.height = this.open ? 'auto' : '0'; + if (this.open) { + this.details.open = true; + } + + this.detailsObserver = new MutationObserver(changes => { + for (const change of changes) { + if (change.type === 'attributes' && change.attributeName === 'open') { + if (this.details.open) { + this.show(); + } else { + this.hide(); + } + } + } + }); + this.detailsObserver.observe(this.details, { attributes: true }); } - private handleSummaryClick() { + disconnectedCallback() { + this.detailsObserver.disconnect(); + } + + private handleSummaryClick(ev: MouseEvent) { + ev.preventDefault(); if (!this.disabled) { if (this.open) { this.hide(); @@ -106,15 +128,16 @@ export default class SlDetails extends ShoelaceElement { @watch('open', { waitUntilFirstUpdate: true }) async handleOpenChange() { if (this.open) { + this.details.open = true; // Show const slShow = this.emit('sl-show', { cancelable: true }); if (slShow.defaultPrevented) { this.open = false; + this.details.open = false; return; } await stopAnimations(this.body); - this.body.hidden = false; const { keyframes, options } = getAnimation(this, 'details.show', { dir: this.localize.dir() }); await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options); @@ -125,6 +148,7 @@ export default class SlDetails extends ShoelaceElement { // Hide const slHide = this.emit('sl-hide', { cancelable: true }); if (slHide.defaultPrevented) { + this.details.open = true; this.open = true; return; } @@ -133,9 +157,9 @@ export default class SlDetails extends ShoelaceElement { const { keyframes, options } = getAnimation(this, 'details.hide', { dir: this.localize.dir() }); await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options); - this.body.hidden = true; this.body.style.height = 'auto'; + this.details.open = false; this.emit('sl-after-hide'); } } @@ -164,7 +188,7 @@ export default class SlDetails extends ShoelaceElement { const isRtl = this.localize.dir() === 'rtl'; return html` -
- +
-
+ `; } } diff --git a/src/components/details/details.styles.ts b/src/components/details/details.styles.ts index 7c4a3a63..ac1bb023 100644 --- a/src/components/details/details.styles.ts +++ b/src/components/details/details.styles.ts @@ -28,6 +28,10 @@ export default css` cursor: pointer; } + .details__header::-webkit-details-marker { + display: none; + } + .details__header:focus { outline: none; } diff --git a/src/components/details/details.test.ts b/src/components/details/details.test.ts index 160cea5d..7dc61c00 100644 --- a/src/components/details/details.test.ts +++ b/src/components/details/details.test.ts @@ -31,20 +31,19 @@ describe('', () => { `); const body = el.shadowRoot!.querySelector('.details__body')!; - expect(body.hidden).to.be.false; + expect(parseInt(getComputedStyle(body).height)).to.be.greaterThan(0); }); it('should not be visible without the open attribute', async () => { const el = await fixture(html` - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. `); const body = el.shadowRoot!.querySelector('.details__body')!; - - expect(body.hidden).to.be.true; + expect(parseInt(getComputedStyle(body).height)).to.equal(0); }); it('should emit sl-show and sl-after-show when calling show()', async () => { @@ -55,7 +54,6 @@ describe('', () => { consequat. `); - const body = el.shadowRoot!.querySelector('.details__body')!; const showHandler = sinon.spy(); const afterShowHandler = sinon.spy(); @@ -68,7 +66,6 @@ describe('', () => { expect(showHandler).to.have.been.calledOnce; expect(afterShowHandler).to.have.been.calledOnce; - expect(body.hidden).to.be.false; }); it('should emit sl-hide and sl-after-hide when calling hide()', async () => { @@ -79,7 +76,6 @@ describe('', () => { consequat. `); - const body = el.shadowRoot!.querySelector('.details__body')!; const hideHandler = sinon.spy(); const afterHideHandler = sinon.spy(); @@ -92,7 +88,6 @@ describe('', () => { expect(hideHandler).to.have.been.calledOnce; expect(afterHideHandler).to.have.been.calledOnce; - expect(body.hidden).to.be.true; }); it('should emit sl-show and sl-after-show when setting open = true', async () => { @@ -127,7 +122,6 @@ describe('', () => { consequat. `); - const body = el.shadowRoot!.querySelector('.details__body')!; const hideHandler = sinon.spy(); const afterHideHandler = sinon.spy(); @@ -140,7 +134,6 @@ describe('', () => { expect(hideHandler).to.have.been.calledOnce; expect(afterHideHandler).to.have.been.calledOnce; - expect(body.hidden).to.be.true; }); it('should not open when preventing sl-show', async () => {