UNPKG

wired-elements

Version:

Collection of hand-drawn sketchy web components

202 lines (187 loc) 5.27 kB
import { WiredBase, BaseCSS, Point } from './wired-base'; import { rectangle } from './wired-lib'; import { css, TemplateResult, html, CSSResultArray } from 'lit'; import { customElement, property } from 'lit/decorators.js'; interface WiredComboItem extends HTMLElement { value: string; selected: boolean; } interface ListboxValue { value: string; text: string; } @customElement('wired-listbox') export class WiredListbox extends WiredBase { @property({ type: Object }) value?: ListboxValue; @property({ type: String }) selected?: string; @property({ type: Boolean }) horizontal = false; private itemNodes: WiredComboItem[] = []; private lastSelectedItem?: WiredComboItem; private itemClickHandler = this.onItemClick.bind(this); static get styles(): CSSResultArray { return [ BaseCSS, css` :host { display: inline-block; font-family: inherit; position: relative; padding: 5px; outline: none; } :host(:focus) path { stroke-width: 1.5; } ::slotted(wired-item) { display: block; } :host(.wired-horizontal) ::slotted(wired-item) { display: inline-block; } ` ]; } render(): TemplateResult { return html` <slot id="slot" @slotchange="${() => this.requestUpdate()}"></slot> <div id="overlay"> <svg id="svg"></svg> </div> `; } firstUpdated() { this.setAttribute('role', 'listbox'); this.tabIndex = +((this.getAttribute('tabindex') || 0)); this.refreshSelection(); this.addEventListener('click', this.itemClickHandler); this.addEventListener('keydown', (event) => { switch (event.keyCode) { case 37: case 38: event.preventDefault(); this.selectPrevious(); break; case 39: case 40: event.preventDefault(); this.selectNext(); break; } }); } updated() { super.updated(); if (this.horizontal) { this.classList.add('wired-horizontal'); } else { this.classList.remove('wired-horizontal'); } if (!this.itemNodes.length) { this.itemNodes = []; const nodes = (this.shadowRoot!.getElementById('slot') as HTMLSlotElement).assignedNodes(); if (nodes && nodes.length) { for (let i = 0; i < nodes.length; i++) { const element = nodes[i] as WiredComboItem; if (element.tagName === 'WIRED-ITEM') { element.setAttribute('role', 'option'); this.itemNodes.push(element); } } } } } private onItemClick(event: Event) { event.stopPropagation(); this.selected = (event.target as WiredComboItem).value; this.refreshSelection(); this.fireSelected(); } private refreshSelection() { if (this.lastSelectedItem) { this.lastSelectedItem.selected = false; this.lastSelectedItem.removeAttribute('aria-selected'); } const slot = this.shadowRoot!.getElementById('slot') as HTMLSlotElement; const nodes = slot.assignedNodes(); if (nodes) { let selectedItem = null; for (let i = 0; i < nodes.length; i++) { const element = nodes[i] as WiredComboItem; if (element.tagName === 'WIRED-ITEM') { const value = element.value || ''; if (this.selected && (value === this.selected)) { selectedItem = element; break; } } } this.lastSelectedItem = selectedItem || undefined; if (this.lastSelectedItem) { this.lastSelectedItem.selected = true; this.lastSelectedItem.setAttribute('aria-selected', 'true'); } if (selectedItem) { this.value = { value: selectedItem.value || '', text: selectedItem.textContent || '' }; } else { this.value = undefined; } } } private fireSelected() { this.fire('selected', { selected: this.selected }); } private selectPrevious() { const list = this.itemNodes; if (list.length) { let index = -1; for (let i = 0; i < list.length; i++) { if (list[i] === this.lastSelectedItem) { index = i; break; } } if (index < 0) { index = 0; } else if (index === 0) { index = list.length - 1; } else { index--; } this.selected = list[index].value || ''; this.refreshSelection(); this.fireSelected(); } } private selectNext() { const list = this.itemNodes; if (list.length) { let index = -1; for (let i = 0; i < list.length; i++) { if (list[i] === this.lastSelectedItem) { index = i; break; } } if (index < 0) { index = 0; } else if (index >= (list.length - 1)) { index = 0; } else { index++; } this.selected = list[index].value || ''; this.refreshSelection(); this.fireSelected(); } } protected canvasSize(): Point { const s = this.getBoundingClientRect(); return [s.width, s.height]; } protected draw(svg: SVGSVGElement, size: Point) { rectangle(svg, 0, 0, size[0], size[1], this.seed); } }