fix: pin HexEditorModal to viewport on open (1.2.3)

makeDraggable previously used position: absolute with top/left 50%, which
relies on the nearest positioned ancestor being .modal-container. Any
plugin or theme that establishes a CSS containing block on an ancestor
(via transform/will-change/filter/contain) silently re-roots the modal
against that inner element, so it opens partway off-screen — as
reported in hexmaker#26.

Switch to position: fixed and compute the centered top/left in JS
against window.innerWidth/innerHeight, clamped 8px inside the viewport.
position: fixed bypasses most ancestor-containing-block issues; the JS
clamp catches the remaining edge cases (transformed ancestors that also
hijack fixed positioning).

Drag listeners now bind to modalEl.ownerDocument so dragging works
correctly inside popout windows.
This commit is contained in:
isaprettycoolguy@protonmail.com 2026-06-17 10:26:30 -04:00
parent ce869324fc
commit d12cf38ceb
5 changed files with 35 additions and 17 deletions

View file

@ -1,7 +1,7 @@
{
"id": "hexmaker",
"name": "Hexmap World Creator",
"version": "1.2.2",
"version": "1.2.3",
"minAppVersion": "1.12.0",
"description": "Create a hex map and a guidebook level wiki for your setting with minimal fuss. Hex crawl worldbuilding toolbox. System agnostic, ready for any TRPG you care to run in it.",
"author": "morkdev",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "hexmaker-plugin",
"version": "1.2.2",
"version": "1.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hexmaker-plugin",
"version": "1.2.2",
"version": "1.2.3",
"license": "MIT",
"dependencies": {
"minisearch": "^7.2.0"

View file

@ -1,6 +1,6 @@
{
"name": "hexmaker-plugin",
"version": "1.2.2",
"version": "1.2.3",
"description": "A plugin to power hex crawl worldbuilding and running.",
"main": "main.js",
"scripts": {

View file

@ -12,13 +12,30 @@ export class HexmakerModal extends Modal {
if (modalEl.dataset.draggable) return;
modalEl.dataset.draggable = "1";
modalEl.addClass("duckmage-editor-modal-drag");
modalEl.setCssProps({
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
margin: '0',
});
// position: fixed so the containing block is the viewport rather than
// the nearest transform/filter/will-change ancestor. Other plugins or
// themes can establish a containing block on a `.modal-container` ancestor
// (issue #26 — a third-party plugin re-rooted position:absolute away from
// the viewport, landing the modal partway off-screen).
modalEl.setCssProps({ position: 'fixed', margin: '0' });
const doc = modalEl.ownerDocument;
const win = doc.defaultView ?? window;
// Clamp the centered position so the modal can never open off-screen,
// even if a transformed ancestor still establishes a containing block
// for fixed positioning.
const PADDING = 8;
const centerInViewport = () => {
const r = modalEl.getBoundingClientRect();
const maxLeft = Math.max(PADDING, win.innerWidth - r.width - PADDING);
const maxTop = Math.max(PADDING, win.innerHeight - r.height - PADDING);
const left = Math.min(Math.max((win.innerWidth - r.width) / 2, PADDING), maxLeft);
const top = Math.min(Math.max((win.innerHeight - r.height) / 2, PADDING), maxTop);
modalEl.setCssProps({ left: `${left}px`, top: `${top}px` });
};
win.requestAnimationFrame(centerInViewport);
modalEl.addEventListener("mousedown", (e: MouseEvent) => {
const modalContent = modalEl.querySelector<HTMLElement>(".modal-content");
@ -27,18 +44,18 @@ export class HexmakerModal extends Modal {
e.preventDefault();
const r = modalEl.getBoundingClientRect();
modalEl.setCssProps({ transform: 'none', left: `${r.left}px`, top: `${r.top}px` });
modalEl.setCssProps({ left: `${r.left}px`, top: `${r.top}px` });
const sx = e.clientX, sy = e.clientY;
const ox = r.left, oy = r.top;
const onMove = (ev: MouseEvent) => {
modalEl.setCssProps({ left: `${ox + ev.clientX - sx}px`, top: `${oy + ev.clientY - sy}px` });
};
const onUp = () => {
activeDocument.removeEventListener("mousemove", onMove);
activeDocument.removeEventListener("mouseup", onUp);
doc.removeEventListener("mousemove", onMove);
doc.removeEventListener("mouseup", onUp);
};
activeDocument.addEventListener("mousemove", onMove);
activeDocument.addEventListener("mouseup", onUp);
doc.addEventListener("mousemove", onMove);
doc.addEventListener("mouseup", onUp);
});
}
}

View file

@ -37,5 +37,6 @@
"1.1.15": "1.12.0",
"1.2.0": "1.12.0",
"1.2.1": "1.12.0",
"1.2.2": "1.12.0"
"1.2.2": "1.12.0",
"1.2.3": "1.12.0"
}