const DRAG_HANDLE_ICON = `
`
class DragPage extends HTMLElement {
public headerRef: HTMLDivElement
public titleRef: HTMLDivElement
public dragHandleRef: HTMLDivElement
private initWidth: number = 400
static get observedAttributes() {
return ['heading', 'key', 'minimized', 'color']
}
constructor() {
super()
}
public connectedCallback() {
const style = `
:root {
width: auto;
}
:host {
position: absolute;
z-index: 9;
background-color: #ffffff;
text-align: center;
border: 1px solid #000000;
width: auto;
}
:host([active]) {
z-index: 999;
}
:host([disabled]) #header {
background-color: #9E9E9E;
cursor: unset;
}
#header {
display: flex;
justify-content: space-between;
padding: 10px;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
#drag-handle {
width: 20px;
height: 20px;
cursor: move;
}
#drag-handle svg {
width: 20px;
height: 20px;
}
#heading {
margin: 0 auto;
}
#controls {
display: inline-flex;
margin-left: 10px;
}
.btn {
line-height: 9px;
width: 10px;
height: 10px;
border: 1px solid black;
border-radius: 3px;
box-shadow: 1px 1px black;
cursor: pointer;
color: white;
}
.btn:not(:last-of-type) {
margin: 0 5px 0 0;
}
.btn:hover {
box-shadow: -1px -1px black;
border: 1px solid black;
color: white;
}
:host([disabled]) .btn, :host([disabled]) .btn:hover {
border: 1px solid #616161;
box-shadow: inset 1px 1px black;
color: #424242;
cursor: unset;
}
.btn:active {
box-shadow: inset 1px 1px black;
}
`
const template = document.createElement('template')
template.innerHTML = `
`
const shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.appendChild(template.content.cloneNode(true))
this.headerRef = this.shadowRoot!.querySelector('#header')!
this.titleRef = this.shadowRoot!.querySelector('#heading')!
this.dragHandleRef = this.shadowRoot!.querySelector('#drag-handle')!
this.restorePosition()
this.dragHandleRef.addEventListener('mousedown', this.beginElementDrag)
this.dragHandleRef.addEventListener('dblclick', this.handleDblCLick)
this.headerRef.querySelectorAll('.btn').forEach( (btnEl) => btnEl.addEventListener('click', this.handleBtnClick))
// set the initial width to the width of the content
this.initWidth = this.getBoundingClientRect().width
// update the z-index when the element is clicked
this.addEventListener('click', () => {
if (this.id !== 'canvas_main') {
this.style.zIndex = '3'
const otherPanes = Array.from(document.getElementsByTagName('drag-pane')).filter( (el: HTMLElement) => el !== this && el.id !== 'canvas_main')
otherPanes.forEach( (el: HTMLElement) => el.style.zIndex = '2')
}
})
}
public get heading(): string {
return this.getAttribute('heading') || ''
}
public set heading(newState: string) {
this.setAttribute('heading', newState)
}
public get minimized(): boolean {
return this.getAttribute('minimized') === 'true' || this.hasAttribute('minimized') && this.getAttribute('minimized') === ''
}
public set minimized(newState: boolean) {
if (newState) {
this.setAttribute('minimized', 'true')
} else {
this.removeAttribute('minimized')
}
}
public set color(newColor: string) {
this.setAttribute('color', newColor)
}
public get color(): string {
return this.getAttribute('color') || this.defaultHeaderColor
}
public get key(): string|undefined {
return this.getAttribute('key') || undefined
}
public set key(newValue: string|undefined){
if (newValue) {
this.setAttribute('key', newValue)
} else {
this.removeAttribute('key')
}
}
attributeChangedCallback(_name: string) {
if (_name === 'minimized') {
if (this.minimized) {
// this.style.height = '30px'
this.shadowRoot!.querySelector('#content')!.style.display = 'none'
this.style.width = this.initWidth + 'px'
} else {
// this.style.height = 'auto'
this.shadowRoot!.querySelector('#content')!.style.display = 'block'
this.style.width = this.initWidth + 'px'
}
}
}
private defaultHeaderColor: string = '#2196F3'
private handleBtnClick = (event: MouseEvent) => {
const el = event!.target as HTMLDivElement
switch (el.id) {
case 'close':
this.dispatchEvent(new Event('remove'))
this.remove()
break
case 'minimize':
this.minimized = !this.minimized
this.dispatchEvent(new Event('toggleminimize', {bubbles: true, composed: true}))
break
default:
break
}
}
private handleDblCLick = () => {
this.minimized = !this.minimized
this.dispatchEvent(new Event('toggleminimize', {bubbles: true, composed: true}))
}
private pos: {x: number, y: number} = {x:0, y:0}
private storePosition(){
if (this.key) {
window.localStorage.setItem(`drag-pane-${this.key}`, JSON.stringify(this.pos))
}
}
private restorePosition(){
if (this.key) {
const storeValue = window.localStorage.getItem(`drag-pane-${this.key}`)
if (storeValue) {
this.pos = JSON.parse(storeValue)
this.style.top = this.pos.y + 'px'
this.style.left = this.pos.x + 'px'
}
}
}
private didMove: boolean = false
// private moveThreshold: number = 5;
private beginElementDrag = (event: MouseEvent) => {
event.preventDefault()
this.setAttribute('active', 'true')
// get the mouse cursor position at startup:
this.pos.x = event.clientX
this.pos.y = event.clientY
document.onmouseup = this.endElementDrag
// call a function whenever the cursor moves:
document.onmousemove = this.elementDrag
}
private elementDrag = (event: MouseEvent) => {
event.preventDefault()
// calculate the new cursor position:
const pos1 = this.pos.x - event.clientX
const pos2 = this.pos.y - event.clientY
this.pos.x = event.clientX
this.pos.y = event.clientY
this.didMove = true
this.dispatchEvent(new Event('dragstart', {bubbles: true, composed: true}))
// set the element's new position:
const newPosition = {top: this.offsetTop - pos2, left: this.offsetLeft - pos1}
this.style.top = newPosition.top + 'px'
this.style.left = newPosition.left + 'px'
}
private endElementDrag = () => {
// release element
document.onmouseup = null
document.onmousemove = null
this.setAttribute('active', 'false')
if (this.didMove) {
this.storePosition()
this.dispatchEvent(new Event('dragend', {bubbles: true, composed: true}))
this.didMove = false
}
}
}
window.customElements.define('drag-pane', DragPage)
export default DragPage