Created
January 26, 2021 21:12
-
-
Save e111077/1bb50247396d382a077300c1b47991c5 to your computer and use it in GitHub Desktop.
Draggable webcomponent dialog panel esample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import '@material/mwc-icon-button'; | |
import '@material/mwc-icon'; | |
import '../story_renderer/story-renderer'; | |
import {customElement, html, LitElement, property, PropertyValues, query} from 'lit-element'; | |
import {headline5} from '../../catalog/styles/m_typography'; | |
import {styles} from './story-knob-panel.css'; | |
// icons from material icon picker | |
const closeSvg = | |
html`<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>`; | |
const dragHandleSvg = | |
html`<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path d="M20,9H4v2h16V9z M4,15h16v-2H4V15z"/></g></g></g></svg>`; | |
const dockSvg = | |
html`<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 15h8v-2H3v2zm0 4h8v-2H3v2zm0-8h8V9H3v2zm0-6v2h8V5H3zm10 0h8v14h-8V5z"/></svg>`; | |
const popoutSvg = | |
html`<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/></svg>`; | |
/** | |
* Default dimensions for offsets and heights used for drag bounding | |
*/ | |
export const DEFAULT_DIMENSIONS = { | |
RIGHT_OFFSET: 8, | |
TOP_OFFSET: 8, | |
PANEL_WIDTH: 320, | |
DRAG_BAR_HEIGHT: 32, | |
}; | |
/** | |
* A right-side panel for the knobs | |
* | |
* @fires open-changed {{open: boolean}} Fired when opened or closed | |
*/ | |
@customElement('story-knob-panel') | |
export class StoryKnobPanel extends LitElement { | |
static styles = [headline5, styles]; | |
@query('.dragBar') dragBar!: HTMLElement|null; | |
@property({type: Boolean}) showCloseIcon = false; | |
@property({type: Boolean, reflect: true}) open = false; | |
@property({type: Boolean, reflect: true}) draggable = false; | |
@property({type: Boolean}) hideDragIcon = false; | |
@property({type: String, reflect: true}) type: 'modal'|'inline' = 'inline'; | |
private isDragging = false; | |
private previousX = 0; | |
private currentX = 0; | |
private previousY = 0; | |
private currentY = 0; | |
private dragStartPos = { | |
x: 0, | |
y: 0, | |
}; | |
update(changed: PropertyValues) { | |
super.update(changed); | |
if (changed.has('type')) { | |
this.open = this.type === 'inline'; | |
} | |
if (changed.has('draggable')) { | |
this.translatePos(0, 0); | |
this.previousX = 0; | |
this.previousY = 0; | |
} | |
} | |
render() { | |
let closeIcon = html``; | |
let dragIcon = html``; | |
let dragBar = html``; | |
if (this.showCloseIcon) { | |
closeIcon = html` | |
<mwc-icon-button | |
label="close" | |
@click=${this.close}> | |
${closeSvg} | |
</mwc-icon-button> | |
`; | |
} | |
if (this.draggable) { | |
dragBar = html` | |
<div | |
class="dragBar" | |
@pointerdown=${this.onDragStart} | |
@pointermove=${this.onDrag} | |
@pointerup=${this.onDragEnd}> | |
<mwc-icon>${dragHandleSvg}</mwc-icon> | |
</div> | |
`; | |
} | |
if (!this.hideDragIcon) { | |
const iconSvg = this.draggable ? dockSvg : popoutSvg; | |
const iconLabel = this.draggable ? 'dock' : 'pop out'; | |
dragIcon = html` | |
<mwc-icon-button | |
class="dragIconButton" | |
.label=${iconLabel} | |
@click=${this.onDragIconClick}> | |
${iconSvg} | |
</mwc-icon-button> | |
`; | |
} | |
return html` | |
<aside> | |
${dragBar} | |
<div class="content"> | |
<div id="title"> | |
<h3 class="m-headline5">Knobs</h3> | |
${dragIcon} | |
${closeIcon} | |
</div> | |
<div id="knobs"> | |
<slot></slot> | |
</div> | |
</div> | |
</aside> | |
`; | |
} | |
updated(changed: PropertyValues) { | |
super.updated(changed); | |
if (changed.has('open')) { | |
this.dispatchEvent(new CustomEvent('open-changed', { | |
detail: { | |
open: this.open, | |
} | |
})); | |
} | |
} | |
close() { | |
this.open = false; | |
} | |
show() { | |
this.open = true; | |
} | |
private onDragIconClick() { | |
this.draggable = !this.draggable; | |
} | |
private onDragStart(event: PointerEvent) { | |
this.dragStartPos = {x: event.x, y: event.y}; | |
this.isDragging = true; | |
if (this.dragBar) { | |
this.dragBar.setPointerCapture(event.pointerId); | |
} | |
} | |
private onDrag(event: PointerEvent) { | |
if (!this.isDragging) { | |
return; | |
} | |
const newX = this.previousX + event.x - this.dragStartPos.x; | |
const newY = this.previousY + event.y - this.dragStartPos.y; | |
this.translatePos(newX, newY); | |
} | |
private onDragEnd(event: PointerEvent) { | |
this.isDragging = false; | |
this.previousX = this.currentX; | |
this.previousY = this.currentY; | |
if (this.dragBar) { | |
this.dragBar.releasePointerCapture(event.pointerId); | |
} | |
} | |
private translatePos(x: number, y: number) { | |
if (x === 0 && y === 0) { | |
this.style.transform = ''; | |
return; | |
} | |
const rightBound = DEFAULT_DIMENSIONS.RIGHT_OFFSET; | |
const leftBound = | |
(DEFAULT_DIMENSIONS.RIGHT_OFFSET + DEFAULT_DIMENSIONS.PANEL_WIDTH) - | |
window.innerWidth; | |
const topBound = -DEFAULT_DIMENSIONS.TOP_OFFSET; | |
const bottomBound = window.innerHeight - | |
(DEFAULT_DIMENSIONS.DRAG_BAR_HEIGHT + DEFAULT_DIMENSIONS.TOP_OFFSET); | |
// do not allow drag outside right bound | |
if (x > rightBound) { | |
x = rightBound; | |
} | |
// do not allow drag outside left bound | |
if (x < leftBound) { | |
x = leftBound; | |
} | |
// do not allow drag outside top bound | |
if (y < topBound) { | |
y = topBound; | |
} | |
// do not allow drag outside bottom bound | |
if (y > bottomBound) { | |
y = bottomBound; | |
} | |
this.currentX = x; | |
this.currentY = y; | |
this.style.transform = `translate(${x}px, ${y}px)`; | |
} | |
} | |
declare global { | |
interface HTMLElementTagNameMap { | |
'story-knob-panel': StoryKnobPanel; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment