390 lines
14 KiB
JavaScript
390 lines
14 KiB
JavaScript
/**
|
|
* _ ___ Another
|
|
* / |/ (_)______ __ _____
|
|
* / / / __(_-</ // (_-<
|
|
* /_/|_/_/\__/___/\_, /___/
|
|
* /___/
|
|
* production !
|
|
*
|
|
* Licensed under the MIT License:
|
|
* This code is free to use and modify,
|
|
* as long as the copyright notice and license are kept.
|
|
*/
|
|
|
|
class Buildoz extends HTMLElement {
|
|
|
|
// static is evaluated when the class is defined, therefore while buildoz.js is executing.
|
|
// therefore document.currentScript refers to buildoz.js (but not later!!)
|
|
static _buildozUrl = document.currentScript?.src ?? ''
|
|
|
|
constructor(){
|
|
super() // always call super() first!
|
|
this.attrs = {}
|
|
|
|
// Usefull for relative dependencies, to keep lib fully portable
|
|
this.buildozUrl = Buildoz._buildozUrl // was defined in the past
|
|
}
|
|
|
|
static get observedAttributes(){ //observable attributes triggering attributeChangedCallback
|
|
// anything added here will be observed for all buildoz tags
|
|
// in your child, add you local 'color' observable attr with :
|
|
// return([...super.observedAttributes, 'color'])
|
|
return(['name'])
|
|
}
|
|
|
|
static define(name, cls){
|
|
const tag = `bz-${name}`
|
|
if(!customElements.get(tag)) { // no wild redefinition
|
|
customElements.define(tag, cls)
|
|
}
|
|
return cls
|
|
}
|
|
|
|
connectedCallback(){ // added to the DOM
|
|
this.classList.add('buildoz')
|
|
}
|
|
|
|
disconnectedCallback(){ // removed from the DOM
|
|
|
|
}
|
|
|
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
this.attrs[name] = newValue
|
|
if(name=='name') this.name = newValue
|
|
}
|
|
|
|
getBZAttribute(attrName){ // Little helper for defaults
|
|
return(this.getAttribute(attrName) || this.defaultAttrs[attrName] )
|
|
}
|
|
|
|
fireEvent(eventName, detail){
|
|
let myname = this.tagName.toLocaleLowerCase()
|
|
myname = myname.substring(myname.indexOf('-')+1)
|
|
const eventFullName = `bz:${myname}:${eventName}`
|
|
this.dispatchEvent(new CustomEvent(eventFullName, {
|
|
detail,
|
|
bubbles: true,
|
|
composed: true,
|
|
}))
|
|
}
|
|
}
|
|
|
|
class BZselect extends Buildoz {
|
|
#value
|
|
#fillFromMarkup = true
|
|
|
|
constructor(){
|
|
super()
|
|
this.value = null
|
|
this.open = false
|
|
this.value = null
|
|
this.generalClickEvent = null
|
|
this.defaultAttrs = {
|
|
label: 'Select...',
|
|
}
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback()
|
|
this.button = document.createElement('button')
|
|
this.button.textContent = this.getBZAttribute('label')
|
|
this.prepend(this.button)
|
|
this.button.addEventListener('click', this.toggle.bind(this))
|
|
if(!this.optionscontainer) this.optionscontainer = document.createElement('div')
|
|
this.optionscontainer.classList.add('options-container')
|
|
this.append(this.optionscontainer)
|
|
this.options = this.querySelectorAll('option')
|
|
if(this.#fillFromMarkup){ //can only do it once and only if fillOptions was not already called !!
|
|
for(const opt of this.options){
|
|
this.optionscontainer.append(opt) // Will move is to the right parent
|
|
opt.addEventListener('click', this.onClick.bind(this))
|
|
if(opt.getAttribute('selected') !== null) this.onOption(opt.value, true)
|
|
}
|
|
this.#fillFromMarkup = false
|
|
}
|
|
|
|
}
|
|
|
|
// static get observedAttributes(){ // Only if you want actions on attr change
|
|
// return([...super.observedAttributes, 'disabled'])
|
|
// }
|
|
|
|
// attributeChangedCallback(name, oldValue, newValue) {
|
|
// super.attributeChangedCallback(name, oldValue, newValue)
|
|
// }
|
|
|
|
get value(){ return(this.#value) }
|
|
|
|
set value(v) {
|
|
this.#value = v
|
|
if(this.options && this.options.length>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)
|
|
|