feat(details): use details and summary html tag to enable in browser searching (#1470)

pull/1481/head
Thomas Allmer 2023-07-31 19:58:42 +02:00 zatwierdzone przez GitHub
rodzic a067ccb9e0
commit 89f0f4a02c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 40 dodań i 19 usunięć

Wyświetl plik

@ -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`
<div
<details
part="base"
class=${classMap({
details: true,
@ -173,7 +197,7 @@ export default class SlDetails extends ShoelaceElement {
'details--rtl': isRtl
})}
>
<div
<summary
part="header"
id="header"
class="details__header"
@ -195,12 +219,12 @@ export default class SlDetails extends ShoelaceElement {
<sl-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>
</slot>
</span>
</div>
</summary>
<div class="details__body" role="region" aria-labelledby="header">
<slot part="content" id="content" class="details__content"></slot>
</div>
</div>
</details>
`;
}
}

Wyświetl plik

@ -28,6 +28,10 @@ export default css`
cursor: pointer;
}
.details__header::-webkit-details-marker {
display: none;
}
.details__header:focus {
outline: none;
}

Wyświetl plik

@ -31,20 +31,19 @@ describe('<sl-details>', () => {
`);
const body = el.shadowRoot!.querySelector<HTMLElement>('.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<SlDetails>(html`
<sl-details>
<sl-details summary="click me">
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.
</sl-details>
`);
const body = el.shadowRoot!.querySelector<HTMLElement>('.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('<sl-details>', () => {
consequat.
</sl-details>
`);
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
const showHandler = sinon.spy();
const afterShowHandler = sinon.spy();
@ -68,7 +66,6 @@ describe('<sl-details>', () => {
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('<sl-details>', () => {
consequat.
</sl-details>
`);
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
const hideHandler = sinon.spy();
const afterHideHandler = sinon.spy();
@ -92,7 +88,6 @@ describe('<sl-details>', () => {
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('<sl-details>', () => {
consequat.
</sl-details>
`);
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
const hideHandler = sinon.spy();
const afterHideHandler = sinon.spy();
@ -140,7 +134,6 @@ describe('<sl-details>', () => {
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 () => {