/** * _ ___ Another * / |/ (_)______ __ _____ * / / / __(_-0) { const opt = Array.from(this.options).find(opt => opt.value==v) if(v || (opt && opt.textContent)) this.button.textContent = opt.textContent else this.button.textContent = this.getBZAttribute('label') this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } } toggle(){ for(const opt of this.options){ if(this.open) { opt.classList.remove('open') this.optionscontainer.classList.remove('open') } else { document.querySelectorAll('bz-select').forEach((sel) => { if((sel!==this) && sel.open) sel.toggle() }) opt.classList.add('open') this.optionscontainer.classList.add('open') } } this.open = !this.open } onClick(evt){ evt.stopPropagation() const opt = evt.target.closest('option') if(opt && opt.value) this.onOption(opt.value) } onOption(value, silent=false){ if(this.getAttribute('disabled') !== null) return this.value = value if(!silent) this.toggle() } addOption(value, markup){ // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM const opt = document.createElement('option') opt.setAttribute('value', value) opt.innerHTML = markup opt.addEventListener('click',this.onClick.bind(this)) if(!this.optionscontainer) this.optionscontainer = document.createElement('div') this.optionscontainer.append(opt) this.options = this.querySelectorAll('option') this.#fillFromMarkup = false } fillOptions(opts, erase = true){ // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM if(erase){ this.options = this.querySelectorAll('option') this.options.forEach(node => { node.remove() }) this.options = this.querySelectorAll('option') this.onOption('', true) // unselect last } for(const opt of opts) this.addOption(opt.value, opt.markup) } } Buildoz.define('select', BZselect) class BZtoggler extends Buildoz { #value constructor(){ super() this.open = false this.defaultAttrs = { labelLeft:'', labelRight:'', trueValue: 'yes', falseValue: 'no', classOn:'', classOff:'', tabindex:0, disabled:false } } connectedCallback(){ super.connectedCallback() this.labelRight = document.createElement('div') this.labelRight.classList.add('toggle-label-right') this.labelRight.innerHTML = this.getBZAttribute('labelRight') this.labelLeft = document.createElement('div') this.labelLeft.classList.add('toggle-label-left') this.labelLeft.innerHTML = this.getBZAttribute('labelLeft') this.switch = document.createElement('div') this.switch.classList.add('toggle-switch') this.toggleBar = document.createElement('span') this.toggleBar.classList.add('toggle-bar') this.thumb = document.createElement('span') this.thumb.classList.add('toggle-thumb') this.toggleBar.append(this.thumb) this.switch.append(this.toggleBar) this.appendChild(this.labelLeft) this.appendChild(this.switch) this.appendChild(this.labelRight) this.setAttribute('tabindex', this.getBZAttribute('tabindex')) this.switch.addEventListener('click', this.toggle.bind(this)) } turnOn() { if(this.getBZAttribute('classOff')) this.switch.classList.remove(this.getBZAttribute('classOff')) if(this.getBZAttribute('classOn')) this.switch.classList.add(this.getBZAttribute('classOn')) this.thumb.classList.add('turned-on') this.#value = this.getBZAttribute('trueValue') this.focus() } turnOff() { if(this.getBZAttribute('classOn')) this.switch.classList.remove(this.getBZAttribute('classOn')) if(this.getBZAttribute('classOff'))this.switch.classList.add(this.getBZAttribute('classOff')) this.thumb.classList.remove('turned-on') this.#value = this.getBZAttribute('falseValue') this.focus() } toggle(event) { if(event) { event.preventDefault(); event.stopPropagation(); } if(this.getBZAttribute('disabled')) return if(this.#value == this.getBZAttribute('trueValue')) this.turnOff() else this.turnOn() this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } get value(){ return(this.#value) } set value(v) { this.#value = v if(this.defaultAttrs){ if(this.#value == this.getBZAttribute('trueValue')) this.turnOn() else this.turnOff() this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } } } Buildoz.define('toggler', BZtoggler) class BZslidePane extends Buildoz { constructor(){ super() this.open = false this.defaultAttrs = { side: 'bottom' } this.dragMove = this.dragMove.bind(this) this.dragEnd = this.dragEnd.bind(this) this.lastClientX = 0 this.lastClientY = 0 // Fill with innerHTML or other DOM manip should not allow coating to be removed this._observer = new MutationObserver(muts => { this.coat() }) } connectedCallback(){ super.connectedCallback() this.coat() this._observer.observe(this, { childList: true }) // Do this last } disconnectedCallback() { super.disconnectedCallback() this._observer.disconnect() } coat(){ if(this.handle && this.querySelector(this.dispatchEvent.handle)) return this._observer.disconnect() if(this.querySelector(this.dispatchEvent.handle)) this.querySelector(this.dispatchEvent.handle).remove() this.handle = document.createElement('div') this.handle.classList.add('handle') this.prepend(this.handle) this.handle.addEventListener('pointerdown', this.dragStart.bind(this)) this._observer.observe(this, { childList: true }) } dragStart(evt){ evt.target.setPointerCapture(evt.pointerId) this.dragStartX = evt.clientX this.dragStartY = evt.clientY this.lastClientX = evt.clientX this.lastClientY = evt.clientY this.handle.addEventListener('pointermove', this.dragMove) this.handle.addEventListener('pointerup', this.dragEnd) } dragMove(evt){ const box = this.getBoundingClientRect() const boundaryEl = this.offsetParent || this.parentElement const parentBox = boundaryEl.getBoundingClientRect() let width, height, min, max switch(this.getAttribute('side')){ case 'top': min = parseInt(this.getBZAttribute('minheight')) || 0 if(evt.clientY > (box.top + min)) height = (evt.clientY - box.top) else if(evt.clientY < this.lastClientY) height = min else if(evt.clientY > this.lastClientY) height = 0 else break max = parseInt(this.getBZAttribute('maxheight')) || Math.floor(parentBox.height/2) height = Math.min(height, parentBox.height, max) this.style.height = height+'px' break case 'bottom': min = parseInt(this.getBZAttribute('minheight')) || 0 if(evt.clientY < (box.bottom - min)) height = (box.bottom - evt.clientY) else if(evt.clientY > this.lastClientY) height = min else if(evt.clientY < this.lastClientY) height = 0 else break max = parseInt(this.getBZAttribute('maxheight')) || Math.floor(parentBox.height/2) height = Math.min(height, parentBox.height, max) this.style.height = height+'px' break case 'left': min = parseInt(this.getBZAttribute('minwidth')) || 0 if(evt.clientX < (box.left + min)) width = (evt.clientX - box.left) else if(evt.clientX > this.lastClientX) width = min else if(evt.clientX < this.lastClientX) width = 0 else break max = parseInt(this.getBZAttribute('maxwidth')) || Math.floor(parentBox.width/2) width = Math.min(width, parentBox.width, max) this.style.width = width+'px' break case 'right': min = parseInt(this.getBZAttribute('minwidth')) || 0 if(evt.clientX < (box.right - min)) width = (box.right - evt.clientX) else if(evt.clientX < this.lastClientX) width = min else if(evt.clientX > this.lastClientX) width = 0 else break max = parseInt(this.getBZAttribute('maxwidth')) || Math.floor(parentBox.width/2) width = Math.min(width, parentBox.width, max) this.style.width = width+'px' break } this.lastClientX = evt.clientX this.lastClientY = evt.clientY } dragEnd(evt){ evt.target.releasePointerCapture(evt.pointerId) this.handle.removeEventListener('pointermove', this.dragMove) this.handle.removeEventListener('pointerup', this.dragEnd) } } Buildoz.define('slidepane', BZslidePane)