customElements.define('ts-list', class extends HTMLElement { static observedAttributes = ['ix'] constructor() { super(); this.ix=0 } connectedCallback() { let sh = this.attachShadow({mode: 'open'}) sh.innerHTML=` ` Array.from(this.children).map(x=>this.decorateChild(x)) sh.getElementById('up').onclick = e=> this.mv(-1) sh.getElementById('dn').onclick = e=> this.mv(+1) sh.getElementById('add').onclick = e=>{ let el = document.createElement('p') this.insertBefore(el, this.children[this.ix].nextSibling) this.decorateChild(el) this.update()}} decorateChild(el){ el.contentEditable = true el.onclick = e=>this.setAttribute('ix', [].indexOf.call(this.children, e.target))} attributeChangedCallback(key,old,val) { switch (key) { case 'ix': { this.ix = parseInt(val); this.update() }}} get value() { return this.children[this.ix].innerText } get values() { return Array.from(this.children).map(x=>x.innerText) } go(ix){ this.setAttribute('ix', this.ix=ix);this.update() } mv(di){ this.go(Math.min(this.children.length-1, Math.max(0,this.ix + di))) } update() { let cs = Array.from(this.children) cs.forEach(c=>{c.classList.remove('active') }) if (this.ix ts-list { display:block; width: 200px; border: solid #333 2px; } p { padding: 2px; border: solid silver 1px; } p.active { background: silver } ` document.body.innerHTML=`

hello?

do the dishes

`