Skip to content

Instantly share code, notes, and snippets.

@zishe
Forked from clinuxrulz/Window.tsx
Created May 13, 2024 19:46
Show Gist options
  • Save zishe/eb4357ea1c584de25e32b5b0425a8eee to your computer and use it in GitHub Desktop.
Save zishe/eb4357ea1c584de25e32b5b0425a8eee to your computer and use it in GitHub Desktop.
SolidJS reusable draggable/resizable window example (requires bootstrap style sheet and an icon for grip drag corner "svgs/icons/resize%20grip.svg")
import { batch, Component, createEffect, createMemo, onCleanup, onMount } from 'solid-js';
import { createStore } from 'solid-js/store';
const Window: Component<{
title?: string,
width?: number,
height?: number,
initX?: number,
initY?: number,
}> = (props) => {
var parent;
var dialog;
var initX = props.hasOwnProperty("initX") ? props.initX : 0;
var initY = props.hasOwnProperty("initY") ? props.initY : 0;
const [ state, setState ] = createStore({
dragging: false,
resizing: false,
x: initX,
y: initY
} as {
dragging: boolean,
resizing: boolean,
startOffsetX?: number,
startOffsetY?: number,
x: number,
y: number,
width?: number,
height?: number
});
onMount(() => {
batch(() => {
parent = dialog.parentNode;
parent.addEventListener("mousemove", mouseMove, { passive: true });
parent.addEventListener("mouseup", mouseUp);
parent.addEventListener("mouseleave", mouseLeave);
parent.addEventListener("touchmove", touchMove, { passive: true });
parent.addEventListener("touchend", touchEnd);
parent.addEventListener("touchcancel", touchCancel);
let rect = dialog.getBoundingClientRect();
setState("width", rect.width);
setState("height", rect.height);
});
});
onCleanup(() => {
parent.removeEventListener("mousemove", mouseMove);
parent.removeEventListener("mouseup", mouseUp);
parent.removeEventListener("mouseleave", mouseLeave);
parent.removeEventListener("touchmove", touchMove);
parent.removeEventListener("touchend", touchEnd);
parent.removeEventListener("touchcancel", touchCancel);
});
function dragStart(x: number, y: number) {
batch(() => {
setState("dragging", true);
setState("startOffsetX", x - state.x);
setState("startOffsetY", y - state.y);
});
}
function dragMove(x: number, y: number) {
if (!state.dragging) {
return;
}
batch(() => {
setState("x", x - state.startOffsetX);
setState("y", y - state.startOffsetY);
});
}
function dragEnd() {
if (!state.dragging) {
return;
}
batch(() => {
setState("dragging", false);
setState("startOffsetX", undefined);
setState("startOffsetY", undefined);
});
}
function resizeStart(x: number, y: number) {
batch(() => {
setState("resizing", true);
setState("startOffsetX", x - (state.x + state.width));
setState("startOffsetY", y - (state.y + state.height));
});
}
function resizeMove(x: number, y: number) {
batch(() => {
setState("width", x - state.startOffsetX - state.x);
setState("height", y - state.startOffsetY - state.y);
});
}
function resizeEnd() {
batch(() => {
setState("resizing", false);
setState("startOffsetX", undefined);
setState("startOffsetY", undefined);
});
}
function mouseDown(e: MouseEvent) {
dragStart(e.clientX, e.clientY);
}
function mouseDownResizeCorner(e: MouseEvent) {
resizeStart(e.clientX, e.clientY);
}
function mouseMove(e: MouseEvent) {
if (state.dragging) {
dragMove(e.clientX, e.clientY);
} else if (state.resizing) {
resizeMove(e.clientX, e.clientY);
}
}
function mouseUp(e: MouseEvent) {
if (state.dragging) {
dragEnd();
} else if (state.resizing) {
resizeEnd();
}
}
function mouseLeave(e: MouseEvent) {
if (state.dragging) {
dragEnd();
} else if (state.resizing) {
resizeEnd();
}
}
function touchStart(e: TouchEvent) {
if (e.touches.length != 1) {
return;
}
dragStart(e.touches[0].clientX, e.touches[0].clientY);
}
function touchStartResizeCorner(e: TouchEvent) {
if (e.touches.length != 1) {
return;
}
resizeStart(e.touches[0].clientX, e.touches[0].clientY);
}
function touchMove(e: TouchEvent) {
if (e.touches.length != 1) {
return;
}
if (state.dragging) {
dragMove(e.touches[0].clientX, e.touches[0].clientY);
} else if (state.resizing) {
resizeMove(e.touches[0].clientX, e.touches[0].clientY);
}
}
function touchEnd(e: TouchEvent) {
if (state.dragging) {
dragEnd();
} else if (state.resizing) {
resizeEnd();
}
}
function touchCancel(e: TouchEvent) {
if (state.dragging) {
dragEnd();
} else if (state.resizing) {
resizeEnd();
}
}
let styleWidth = createMemo(() => {
if (state.width != undefined) {
return " width: " + state.width + "px;";
} else if (props.width != undefined) {
return " width: " + props.width + "px;";
} else {
return "";
}
});
let styleHeight = createMemo(() => {
if (state.height != undefined) {
return " height: " + state.height + "px;";
} else if (props.height != undefined) {
return " height: " + props.height + "px;";
} else {
return "";
}
});
return (
<div ref={dialog} style={"display: flex; flex-direction: row; overflow: none; position: absolute; left: " + state.x + "px; top: " + state.y + "px;"}>
<div class="modal-dialog" role="document" style="overflow: none; width: 100%;">
<div class="modal-content">
<div class="modal-header" onMouseDown={mouseDown} onTouchStart={touchStart} style="cursor: grab;">
<h5 class="modal-title">{props.title != undefined ? props.title : ""}</h5>
</div>
<div class="modal-body" style={"overflow: auto;" + styleWidth() + styleHeight()}>
{props.children}
</div>
<div style="border: solid; border-width: 1px; border-color: grey;">
<div style="float: right; cursor: nwse-resize;" onMouseDown={mouseDownResizeCorner} onTouchStart={touchStartResizeCorner}>
<img src="svgs/icons/resize%20grip.svg" width="18" height="15" onMouseDown={e => { e.preventDefault(); e.returnValue = false; }}/>
</div>
</div>
</div>
</div>
</div>
);
};
export default Window;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment