mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
202 lines
No EOL
5.9 KiB
TypeScript
202 lines
No EOL
5.9 KiB
TypeScript
function clamp(n: number, min: number, max: number) {
|
|
return Math.max(min, Math.min(max, n));
|
|
}
|
|
|
|
function getVarPx(el: HTMLElement, name: string, fallback: number) {
|
|
const v = getComputedStyle(el).getPropertyValue(name).trim();
|
|
if (!v) return fallback;
|
|
const s = v.toLowerCase();
|
|
if (s.endsWith("px")) {
|
|
const n = Number(s.slice(0, -2));
|
|
return Number.isFinite(n) ? n : fallback;
|
|
}
|
|
const n = Number(s);
|
|
return Number.isFinite(n) ? n : fallback;
|
|
}
|
|
|
|
function getVarPct(el: HTMLElement, name: string, fallback: number) {
|
|
const v = getComputedStyle(el).getPropertyValue(name).trim();
|
|
if (!v) return fallback;
|
|
const s = v.toLowerCase();
|
|
if (s.endsWith("%")) {
|
|
const n = Number(s.slice(0, -1));
|
|
return Number.isFinite(n) ? n : fallback;
|
|
}
|
|
const n = Number(s);
|
|
return Number.isFinite(n) ? n : fallback;
|
|
}
|
|
|
|
function ensureFlexParent(parent: HTMLElement, axis: "row" | "column") {
|
|
// Don't stomp on an existing layout if it's already flex in the right direction
|
|
const cs = getComputedStyle(parent);
|
|
if (cs.display !== "flex") parent.style.display = "flex";
|
|
parent.style.flexDirection = axis;
|
|
parent.style.overflow = "hidden";
|
|
}
|
|
|
|
function ensurePaneCanShrink(pane: HTMLElement) {
|
|
// Critical for nested flex layouts (otherwise children overflow)
|
|
pane.style.minWidth = "0";
|
|
pane.style.minHeight = "0";
|
|
}
|
|
|
|
function setFixedSize(
|
|
pane: HTMLElement,
|
|
axis: "x" | "y",
|
|
px: number,
|
|
) {
|
|
// For flex: fixed pane should not grow/shrink, basis = px
|
|
pane.style.flexGrow = "0";
|
|
pane.style.flexShrink = "0";
|
|
pane.style.flexBasis = `${px}px`;
|
|
|
|
// Helps some browsers respect size
|
|
if (axis === "x") {
|
|
pane.style.width = `${px}px`;
|
|
} else {
|
|
pane.style.height = `${px}px`;
|
|
}
|
|
}
|
|
|
|
function setFlexFill(pane: HTMLElement) {
|
|
// Fill remaining space
|
|
pane.style.flex = "1 1 auto";
|
|
}
|
|
|
|
function enableFlexSplitters() {
|
|
// Horizontal: A | hSplit | B (top/split/bottom)
|
|
for (const splitter of document.querySelectorAll<HTMLElement>(".hSplit:not(.styleOnly)")) {
|
|
const parent = splitter.parentElement as HTMLElement | null;
|
|
if (!parent) continue;
|
|
|
|
const kids = Array.from(parent.children) as HTMLElement[];
|
|
if (kids.length !== 3 || kids[1] !== splitter) {
|
|
console.warn("hSplit parent must be A | splitter | B", parent);
|
|
continue;
|
|
}
|
|
|
|
const a = kids[0];
|
|
const b = kids[2];
|
|
|
|
ensureFlexParent(parent, "column");
|
|
ensurePaneCanShrink(a);
|
|
ensurePaneCanShrink(b);
|
|
setFlexFill(b); // bottom fills
|
|
|
|
const gap = splitter.getBoundingClientRect().height || 8;
|
|
splitter.style.flex = `0 0 ${gap}px`;
|
|
|
|
// Optional per-splitter CSS vars:
|
|
// --split-default: 60% (of parent height)
|
|
// --split-min-a: 80px
|
|
// --split-min-b: 180px
|
|
const defPct = getVarPct(splitter, "--split-default", 60);
|
|
const minA = getVarPx(splitter, "--split-min-a", 80);
|
|
const minB = getVarPx(splitter, "--split-min-b", 180);
|
|
|
|
// Set initial size (A is fixed)
|
|
{
|
|
const r = parent.getBoundingClientRect();
|
|
const px = clamp((defPct / 100) * r.height, minA, r.height - gap - minB);
|
|
setFixedSize(a, "y", px);
|
|
}
|
|
|
|
let dragging = false;
|
|
|
|
splitter.addEventListener("pointerdown", (e) => {
|
|
dragging = true;
|
|
splitter.setPointerCapture(e.pointerId);
|
|
document.body.style.cursor = "row-resize";
|
|
e.preventDefault();
|
|
});
|
|
|
|
splitter.addEventListener("pointermove", (e) => {
|
|
if (!dragging) return;
|
|
const r = parent.getBoundingClientRect();
|
|
const y = e.clientY - r.top;
|
|
|
|
const maxA = r.height - gap - minB;
|
|
const newA = clamp(y, minA, maxA);
|
|
setFixedSize(a, "y", newA);
|
|
});
|
|
|
|
splitter.addEventListener("pointerup", (e) => {
|
|
dragging = false;
|
|
document.body.style.cursor = "";
|
|
splitter.releasePointerCapture(e.pointerId);
|
|
});
|
|
|
|
splitter.addEventListener("pointercancel", () => {
|
|
dragging = false;
|
|
document.body.style.cursor = "";
|
|
});
|
|
}
|
|
|
|
// Vertical: A | vSplit | B (left/split/right)
|
|
for (const splitter of document.querySelectorAll<HTMLElement>(".vSplit:not(.styleOnly)")) {
|
|
const parent = splitter.parentElement as HTMLElement | null;
|
|
if (!parent) continue;
|
|
|
|
const kids = Array.from(parent.children) as HTMLElement[];
|
|
if (kids.length !== 3 || kids[1] !== splitter) {
|
|
console.warn("vSplit parent must be A | splitter | B", parent);
|
|
continue;
|
|
}
|
|
|
|
const a = kids[0];
|
|
const b = kids[2];
|
|
|
|
ensureFlexParent(parent, "row");
|
|
ensurePaneCanShrink(a);
|
|
ensurePaneCanShrink(b);
|
|
setFlexFill(a); // left fills
|
|
|
|
const gap = splitter.getBoundingClientRect().width || 8;
|
|
splitter.style.flex = `0 0 ${gap}px`;
|
|
|
|
|
|
const defPct = getVarPct(splitter, "--split-default", 50);
|
|
const minA = getVarPx(splitter, "--split-min-a", 220);
|
|
const minB = getVarPx(splitter, "--split-min-b", 220);
|
|
|
|
// Set initial size (B is fixed)
|
|
{
|
|
const r = parent.getBoundingClientRect();
|
|
const px = clamp((defPct / 100) * r.width, minB, r.width - gap - minA);
|
|
setFixedSize(b, "x", px);
|
|
}
|
|
|
|
let dragging = false;
|
|
|
|
splitter.addEventListener("pointerdown", (e) => {
|
|
dragging = true;
|
|
splitter.setPointerCapture(e.pointerId);
|
|
document.body.style.cursor = "col-resize";
|
|
e.preventDefault();
|
|
});
|
|
|
|
splitter.addEventListener("pointermove", (e) => {
|
|
if (!dragging) return;
|
|
const r = parent.getBoundingClientRect();
|
|
|
|
// Right pane width = distance from right edge
|
|
const xFromRight = r.right - e.clientX;
|
|
|
|
const maxB = r.width - gap - minA;
|
|
const newB = clamp(xFromRight, minB, maxB);
|
|
setFixedSize(b, "x", newB);
|
|
});
|
|
|
|
splitter.addEventListener("pointerup", (e) => {
|
|
dragging = false;
|
|
document.body.style.cursor = "";
|
|
splitter.releasePointerCapture(e.pointerId);
|
|
});
|
|
|
|
splitter.addEventListener("pointercancel", () => {
|
|
dragging = false;
|
|
document.body.style.cursor = "";
|
|
});
|
|
}
|
|
}
|
|
enableFlexSplitters(); |