From 89f0f4a02c952f3902b5cb0d0b5d498ed448e77d Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Mon, 31 Jul 2023 19:58:42 +0200 Subject: [PATCH] feat(details): use details and summary html tag to enable in browser searching (#1470) --- src/components/details/details.component.ts | 42 ++++++++++++++++----- src/components/details/details.styles.ts | 4 ++ src/components/details/details.test.ts | 13 ++----- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/components/details/details.component.ts b/src/components/details/details.component.ts index 88a3f40a3..68d066812 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 7c4a3a63e..ac1bb023f 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 160cea5d3..7dc61c009 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 () => {