Skip to content

Instantly share code, notes, and snippets.

@e111077
Created January 26, 2021 21:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save e111077/1bb50247396d382a077300c1b47991c5 to your computer and use it in GitHub Desktop.
Save e111077/1bb50247396d382a077300c1b47991c5 to your computer and use it in GitHub Desktop.
Draggable webcomponent dialog panel esample
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