shoelace/src/components/option/option.ts

134 wiersze
4.4 KiB
TypeScript
Czysty Zwykły widok Historia

2023-01-13 20:43:55 +00:00
import '../icon/icon';
2022-12-19 22:46:31 +00:00
import { classMap } from 'lit/directives/class-map.js';
2023-01-13 20:43:55 +00:00
import { customElement, property, query, state } from 'lit/decorators.js';
import { html } from 'lit';
2022-12-17 16:27:30 +00:00
import { LocalizeController } from '../../utilities/localize';
2023-01-13 20:43:55 +00:00
import { watch } from '../../internal/watch';
import ShoelaceElement from '../../internal/shoelace-element';
2022-12-17 16:27:30 +00:00
import styles from './option.styles';
import type { CSSResultGroup } from 'lit';
/**
2022-12-20 18:36:53 +00:00
* @summary Options define the selectable items within various form controls such as [select](/components/select).
2023-01-12 15:26:25 +00:00
* @documentation https://shoelace.style/components/option
2022-12-20 18:36:53 +00:00
* @status stable
2023-01-12 15:26:25 +00:00
* @since 2.0
2022-12-17 16:27:30 +00:00
*
* @dependency sl-icon
*
2022-12-20 18:36:53 +00:00
* @slot - The option's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item.
* @slot suffix - Used to append an icon or similar element to the menu item.
2022-12-17 16:27:30 +00:00
*
2022-12-20 18:36:53 +00:00
* @csspart checked-icon - The checked icon, an `<sl-icon>` element.
2022-12-17 16:27:30 +00:00
* @csspart base - The component's base wrapper.
2022-12-20 18:36:53 +00:00
* @csspart label - The option's label.
* @csspart prefix - The container that wraps the prefix.
* @csspart suffix - The container that wraps the suffix.
2022-12-17 16:27:30 +00:00
*/
@customElement('sl-option')
export default class SlOption extends ShoelaceElement {
static styles: CSSResultGroup = styles;
2022-12-20 17:13:39 +00:00
private cachedTextLabel: string;
// @ts-expect-error - Controller is currently unused
2022-12-17 16:27:30 +00:00
private readonly localize = new LocalizeController(this);
2023-01-03 20:04:07 +00:00
@query('.option__label') defaultSlot: HTMLSlotElement;
2022-12-28 16:42:08 +00:00
@state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
@state() selected = false; // the option is selected and has aria-selected="true"
2022-12-28 21:07:37 +00:00
@state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging
2022-12-28 16:42:08 +00:00
2022-12-28 20:31:42 +00:00
/**
* The option's value. When selected, the containing form control will receive this value. The value must be unique
* from other options in the same group. Values may not contain spaces, as spaces are used as delimiters when listing
* multiple values.
*/
@property({ reflect: true }) value = '';
2022-12-17 16:27:30 +00:00
/** Draws the option in a disabled state, preventing selection. */
@property({ type: Boolean, reflect: true }) disabled = false;
connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'option');
this.setAttribute('aria-selected', 'false');
}
2023-01-03 20:04:07 +00:00
private handleDefaultSlotChange() {
const textLabel = this.getTextLabel();
// Ignore the first time the label is set
if (typeof this.cachedTextLabel === 'undefined') {
this.cachedTextLabel = textLabel;
return;
}
// When the label changes, emit a slotchange event so parent controls see it
if (textLabel !== this.cachedTextLabel) {
this.cachedTextLabel = textLabel;
this.emit('slotchange', { bubbles: true, composed: false, cancelable: false });
}
}
private handleMouseEnter() {
this.hasHover = true;
}
private handleMouseLeave() {
this.hasHover = false;
2022-12-28 16:42:08 +00:00
}
2022-12-19 22:46:31 +00:00
@watch('disabled')
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
@watch('selected')
handleSelectedChange() {
this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
}
2022-12-28 20:31:42 +00:00
@watch('value')
handleValueChange() {
if (this.value.includes(' ')) {
console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);
this.value = this.value.replace(/ /g, '_');
}
}
2023-01-03 20:04:07 +00:00
/** Returns a plain text label based on the option's content. */
getTextLabel() {
return (this.textContent ?? '').trim();
2022-12-28 21:07:37 +00:00
}
2022-12-17 16:27:30 +00:00
render() {
return html`
2022-12-19 22:46:31 +00:00
<div
2022-12-20 18:36:53 +00:00
part="base"
2022-12-19 22:46:31 +00:00
class=${classMap({
option: true,
'option--current': this.current,
2022-12-20 18:36:53 +00:00
'option--disabled': this.disabled,
2022-12-28 21:07:37 +00:00
'option--selected': this.selected,
'option--hover': this.hasHover
2022-12-19 22:46:31 +00:00
})}
2022-12-28 21:07:37 +00:00
@mouseenter=${this.handleMouseEnter}
@mouseleave=${this.handleMouseLeave}
2022-12-19 22:46:31 +00:00
>
2022-12-20 18:36:53 +00:00
<sl-icon part="checked-icon" class="option__check" name="check" library="system" aria-hidden="true"></sl-icon>
<slot part="prefix" name="prefix" class="option__prefix"></slot>
<slot part="label" class="option__label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot part="suffix" name="suffix" class="option__suffix"></slot>
2022-12-17 16:27:30 +00:00
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'sl-option': SlOption;
}
}