shoelace/src/components/details/details.ts

226 wiersze
7.1 KiB
TypeScript
Czysty Zwykły widok Historia

2023-01-13 20:43:55 +00:00
import '../icon/icon';
2022-03-24 12:01:09 +00:00
import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../internal/animate';
2023-01-13 20:43:55 +00:00
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query } from 'lit/decorators.js';
2022-03-24 12:01:09 +00:00
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
2023-01-13 20:43:55 +00:00
import { html } from 'lit';
2022-06-09 22:14:38 +00:00
import { LocalizeController } from '../../utilities/localize';
2023-01-13 20:43:55 +00:00
import { waitForEvent } from '../../internal/event';
import { watch } from '../../internal/watch';
import ShoelaceElement from '../../internal/shoelace-element';
2021-07-10 00:45:44 +00:00
import styles from './details.styles';
import type { CSSResultGroup } from 'lit';
2021-07-12 14:36:06 +00:00
2020-07-15 21:30:37 +00:00
/**
Enrich components `@summary` with description from docs (#962) * keep header styles with repositioned description text * `animated-image` move description to component * code style * `avatar` add summary from docs * `badge` add summary from docs * `breadcrumb` add summary from docs * `button` add summary from docs * lead sentence is now part of the header * `button-group` add summary from docs * `card` add summary from docs * `checkbox` add summary from docs * `color-picker` add summary from docs * `details` add summary from docs * `dialog` add summary from docs * `divider` add summary from docs * `drawer` add summary from docs * `dropdown` add summary from docs * `format-bytes` add summary from docs * `format-date` add summary from docs * `format-number` add summary from docs * `icon` add summary from docs * `icon-button` add summary from docs * `image-comparer` add summary from docs * `include` add summary from docs * `input` add summary from docs * `menu` add summary from docs * `menu-item` add summary from docs * `menu-label` add summary from docs * `popup` add summary from docs * `progressbar` add summary from docs * `progress-ring` add summary from docs * `radio` add summary from docs * `radio-button` add summary from docs * `range` add summary from docs * `rating` add summary from docs * `relative-time` add summary from docs * `select` add summary from docs * `skeleton` add summary from docs * `spinner` add summary from docs * `split-panel` add summary from docs * `switch` add summary from docs * `tab-group` add summary from docs * `tag` add summary from docs * `textarea` add summary from docs * `tooltip` add summary from docs * `visually-hidden` add summary from docs * `animation` add summary from docs * `breadcrumb-item` add summary from docs * `mutation-observer` add summary from docs * `radio-group` add summary from docs * `resize-observer` add summary from docs * `tab` add summary from docs * `tab-panel` add summary from docs * `tree` add summary from docs * `tree-item` add summary from docs * remove `title` for further usage of `Sl` classnames in docs * revert: use markdown parser for component summary
2022-10-21 13:56:35 +00:00
* @summary Details show a brief summary and expand to show additional content.
2023-01-12 15:26:25 +00:00
* @documentation https://shoelace.style/components/details
2020-07-15 21:30:37 +00:00
* @status stable
2023-01-12 15:26:25 +00:00
* @since 2.0
2020-07-15 21:30:37 +00:00
*
2021-02-26 14:09:13 +00:00
* @dependency sl-icon
*
2022-12-06 16:18:14 +00:00
* @slot - The details' main content.
2022-08-03 15:06:52 +00:00
* @slot summary - The details' summary. Alternatively, you can use the `summary` attribute.
2022-12-06 16:18:14 +00:00
* @slot expand-icon - Optional expand icon to use instead of the default. Works best with `<sl-icon>`.
* @slot collapse-icon - Optional collapse icon to use instead of the default. Works best with `<sl-icon>`.
2020-07-15 21:30:37 +00:00
*
2021-06-25 20:25:46 +00:00
* @event sl-show - Emitted when the details opens.
2021-09-27 21:58:14 +00:00
* @event sl-after-show - Emitted after the details opens and all animations are complete.
2021-06-25 20:25:46 +00:00
* @event sl-hide - Emitted when the details closes.
2021-09-27 21:58:14 +00:00
* @event sl-after-hide - Emitted after the details closes and all animations are complete.
*
2022-12-06 16:18:14 +00:00
* @csspart base - The component's base wrapper.
* @csspart header - The header that wraps both the summary and the expand/collapse icon.
* @csspart summary - The container that wraps the summary.
* @csspart summary-icon - The container that wraps the expand/collapse icons.
2021-06-25 20:25:46 +00:00
* @csspart content - The details content.
*
2021-06-25 20:25:46 +00:00
* @animation details.show - The animation to use when showing details. You can use `height: auto` with this animation.
* @animation details.hide - The animation to use when hiding details. You can use `height: auto` with this animation.
2020-07-15 21:30:37 +00:00
*/
2021-03-18 13:04:23 +00:00
@customElement('sl-details')
2022-08-17 15:37:37 +00:00
export default class SlDetails extends ShoelaceElement {
static styles: CSSResultGroup = styles;
2021-03-06 17:01:39 +00:00
2023-01-03 20:04:07 +00:00
private readonly localize = new LocalizeController(this);
2021-03-06 17:01:39 +00:00
@query('.details') details: HTMLElement;
@query('.details__header') header: HTMLElement;
@query('.details__body') body: HTMLElement;
2022-12-01 20:25:09 +00:00
@query('.details__expand-icon-slot') expandIconSlot: HTMLSlotElement;
2021-02-26 14:09:13 +00:00
2022-12-06 16:18:14 +00:00
/**
* 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.
*/
2021-07-01 00:04:46 +00:00
@property({ type: Boolean, reflect: true }) open = false;
2020-07-15 21:30:37 +00:00
2022-12-06 16:18:14 +00:00
/** The summary to show in the header. If you need to display HTML, use the `summary` slot instead. */
2021-04-02 11:47:25 +00:00
@property() summary: string;
2020-07-15 21:30:37 +00:00
2021-02-26 14:09:13 +00:00
/** Disables the details so it can't be toggled. */
2021-07-01 00:04:46 +00:00
@property({ type: Boolean, reflect: true }) disabled = false;
2021-03-06 17:01:39 +00:00
2021-06-02 12:47:55 +00:00
firstUpdated() {
2021-02-05 21:09:05 +00:00
this.body.hidden = !this.open;
2021-03-30 13:30:00 +00:00
this.body.style.height = this.open ? 'auto' : '0';
2020-07-15 21:30:37 +00:00
}
2023-01-03 20:04:07 +00:00
private handleSummaryClick() {
2020-07-15 21:30:37 +00:00
if (!this.disabled) {
if (this.open) {
this.hide();
} else {
this.show();
}
2020-07-15 21:30:37 +00:00
this.header.focus();
}
}
2023-01-03 20:04:07 +00:00
private handleSummaryKeyDown(event: KeyboardEvent) {
2020-07-15 21:30:37 +00:00
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
if (this.open) {
this.hide();
} else {
this.show();
}
2020-07-15 21:30:37 +00:00
}
if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
event.preventDefault();
this.hide();
2020-07-15 21:30:37 +00:00
}
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
event.preventDefault();
this.show();
2020-07-15 21:30:37 +00:00
}
}
2021-06-15 13:26:35 +00:00
@watch('open', { waitUntilFirstUpdate: true })
2021-05-27 20:29:10 +00:00
async handleOpenChange() {
if (this.open) {
// Show
2022-11-03 15:33:10 +00:00
const slShow = this.emit('sl-show', { cancelable: true });
if (slShow.defaultPrevented) {
this.open = false;
return;
}
2021-05-27 20:29:10 +00:00
2022-02-14 16:08:26 +00:00
await stopAnimations(this.body);
2021-05-27 20:29:10 +00:00
this.body.hidden = false;
2022-06-09 22:14:38 +00:00
const { keyframes, options } = getAnimation(this, 'details.show', { dir: this.localize.dir() });
2021-05-27 20:29:10 +00:00
await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);
this.body.style.height = 'auto';
2022-09-16 20:21:40 +00:00
this.emit('sl-after-show');
2021-05-27 20:29:10 +00:00
} else {
// Hide
2022-11-03 15:33:10 +00:00
const slHide = this.emit('sl-hide', { cancelable: true });
if (slHide.defaultPrevented) {
this.open = true;
return;
}
2021-05-27 20:29:10 +00:00
2022-02-14 16:08:26 +00:00
await stopAnimations(this.body);
2021-05-27 20:29:10 +00:00
2022-06-09 22:14:38 +00:00
const { keyframes, options } = getAnimation(this, 'details.hide', { dir: this.localize.dir() });
2021-05-27 20:29:10 +00:00
await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);
this.body.hidden = true;
this.body.style.height = 'auto';
2022-09-16 20:21:40 +00:00
this.emit('sl-after-hide');
2021-05-27 20:29:10 +00:00
}
2021-02-26 14:09:13 +00:00
}
2023-01-03 20:04:07 +00:00
/** Shows the details. */
async show() {
if (this.open || this.disabled) {
return undefined;
}
this.open = true;
return waitForEvent(this, 'sl-after-show');
}
/** Hides the details */
async hide() {
if (!this.open || this.disabled) {
return undefined;
}
this.open = false;
return waitForEvent(this, 'sl-after-hide');
}
2020-07-15 21:30:37 +00:00
render() {
2022-12-01 20:25:09 +00:00
const isRtl = this.localize.dir() === 'rtl';
2021-02-26 14:09:13 +00:00
return html`
2020-07-15 21:30:37 +00:00
<div
part="base"
2021-02-26 14:09:13 +00:00
class=${classMap({
2020-07-15 21:30:37 +00:00
details: true,
'details--open': this.open,
2022-12-01 20:25:09 +00:00
'details--disabled': this.disabled,
'details--rtl': isRtl
2021-02-26 14:09:13 +00:00
})}
2020-07-15 21:30:37 +00:00
>
<header
2020-08-02 10:54:17 +00:00
part="header"
2021-12-30 17:14:39 +00:00
id="header"
2020-07-15 21:30:37 +00:00
class="details__header"
role="button"
2021-02-26 14:09:13 +00:00
aria-expanded=${this.open ? 'true' : 'false'}
2021-12-30 17:14:39 +00:00
aria-controls="content"
2021-02-26 14:09:13 +00:00
aria-disabled=${this.disabled ? 'true' : 'false'}
tabindex=${this.disabled ? '-1' : '0'}
2021-03-06 17:01:39 +00:00
@click=${this.handleSummaryClick}
@keydown=${this.handleSummaryKeyDown}
2020-07-15 21:30:37 +00:00
>
2022-12-01 18:23:09 +00:00
<slot name="summary" part="summary" class="details__summary">${this.summary}</slot>
2020-07-15 21:30:37 +00:00
2022-12-01 20:25:09 +00:00
<span part="summary-icon" class="details__summary-icon">
<slot name="expand-icon">
<sl-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>
</slot>
<slot name="collapse-icon">
<sl-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>
</slot>
</span>
2020-07-15 21:30:37 +00:00
</header>
2021-05-26 11:31:26 +00:00
<div class="details__body">
2022-12-02 22:03:59 +00:00
<slot part="content" id="content" class="details__content" role="region" aria-labelledby="header"></slot>
2020-07-15 21:30:37 +00:00
</div>
</div>
2021-02-26 14:09:13 +00:00
`;
2020-07-15 21:30:37 +00:00
}
}
2021-05-26 11:31:26 +00:00
setDefaultAnimation('details.show', {
keyframes: [
{ height: '0', opacity: '0' },
{ height: 'auto', opacity: '1' }
],
options: { duration: 250, easing: 'linear' }
});
setDefaultAnimation('details.hide', {
keyframes: [
{ height: 'auto', opacity: '1' },
{ height: '0', opacity: '0' }
],
options: { duration: 250, easing: 'linear' }
});
2021-03-12 14:09:08 +00:00
declare global {
interface HTMLElementTagNameMap {
'sl-details': SlDetails;
}
}