wired-elements
Version:
Collection of hand-drawn sketchy web components
202 lines (187 loc) • 5.27 kB
text/typescript
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;
}
export class WiredListbox extends WiredBase {
value?: ListboxValue;
selected?: string;
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" ="${() => 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);
}
}