let runtimeState = {
hoverBg: "rgba(0,0,0,0.2)",
closeHoverBg: "rgba(196,43,28,1)",
titlebarHeight: 32,
buttonWidth: 46,
};
function applyStyle(style) {
if (!style || typeof style !== "object") return;
if (typeof style.hoverBg === "string") runtimeState.hoverBg = style.hoverBg;
if (typeof style.closeHoverBg === "string") runtimeState.closeHoverBg = style.closeHoverBg;
if (typeof style.titlebarHeight === "number") runtimeState.titlebarHeight = style.titlebarHeight;
if (typeof style.buttonWidth === "number") runtimeState.buttonWidth = style.buttonWidth;
const tb = document.querySelector("[data-tauri-decor-tb]");
if (tb) tb.style.height = `${runtimeState.titlebarHeight}px`;
document.querySelectorAll(".decor-tb-btn").forEach((b) => {
b.style.height = `${runtimeState.titlebarHeight}px`;
b.style.width = `${runtimeState.buttonWidth}px`;
});
}
function installRuntimeListener() {
const tauri = window.__TAURI__;
if (!tauri || !tauri.event || !tauri.event.listen) {
setTimeout(installRuntimeListener, 10);
return;
}
if (document.documentElement.dataset.tauriDecorListener === "1") return;
document.documentElement.dataset.tauriDecorListener = "1";
tauri.event.listen("decor://style-changed", (e) => applyStyle(e.payload || {}));
}
function createControls(config) {
if (config.hoverBg) runtimeState.hoverBg = config.hoverBg;
if (config.closeHoverBg) runtimeState.closeHoverBg = config.closeHoverBg;
if (config.buttonStyle?.height) {
const h = parseInt(config.buttonStyle.height, 10);
if (!Number.isNaN(h)) runtimeState.titlebarHeight = h;
}
if (config.buttonStyle?.width) {
const w = parseInt(config.buttonStyle.width, 10);
if (!Number.isNaN(w)) runtimeState.buttonWidth = w;
}
const run = () => {
const tauri = window.__TAURI__;
if (!tauri) return setTimeout(run, 10);
const win = tauri.window.getCurrentWindow();
const invoke = tauri.core.invoke;
const suppressWebViewContextMenu = (e) => {
e.preventDefault();
};
const updateControlsInset = () => {
const firstBtn = document.querySelector(".decor-tb-btn");
if (!firstBtn) return;
const insetRight = window.innerWidth - firstBtn.getBoundingClientRect().left;
document.documentElement.style.setProperty("--tauri-decor-controls-width", `${insetRight}px`);
};
const setup = (tbEl) => {
if (tbEl.querySelector(".decor-tb-btn")) return;
const parent = config.containerStyle
? (() => {
const div = document.createElement("div");
Object.assign(div.style, config.containerStyle);
tbEl.appendChild(div);
return div;
})()
: tbEl;
config.controls.forEach(id => {
const btn = document.createElement("button");
btn.id = `decor-tb-${id}`;
btn.classList.add("decor-tb-btn");
btn.dataset.decorRole = id;
if (config.buttonStyle) {
Object.assign(btn.style, config.buttonStyle);
}
btn.innerHTML = config.icons?.[id] || "";
if (config.ariaLabels?.[id]) {
btn.setAttribute("aria-label", config.ariaLabels[id]);
}
const localState = { snapTimer: null, actionLock: false, lastAction: 0 };
const cancelSnap = () => {
if (localState.snapTimer) {
clearTimeout(localState.snapTimer);
localState.snapTimer = null;
}
};
const tryAction = (action) => {
const now = Date.now();
if (localState.actionLock || now - localState.lastAction < 200) return;
localState.actionLock = true;
localState.lastAction = now;
cancelSnap();
Promise.resolve(action()).finally(() => {
setTimeout(() => { localState.actionLock = false; }, 100);
});
};
if (!config.cssHover) {
btn.onmouseenter = () => {
const hoverBg = id === "close" ? runtimeState.closeHoverBg : runtimeState.hoverBg;
btn.style.backgroundColor = hoverBg;
if (id === "maximize" && config.snapOverlay && !localState.actionLock) {
cancelSnap();
localState.snapTimer = setTimeout(() => {
if (!localState.actionLock) {
win.setFocus().then(() => invoke("plugin:decor|show_snap_overlay"));
}
localState.snapTimer = null;
}, 620);
}
};
btn.onmouseleave = () => {
btn.style.backgroundColor = config.buttonStyle?.backgroundColor || "transparent";
cancelSnap();
};
btn.onmousedown = (e) => {
if (e.button === 0) cancelSnap();
};
}
switch (id) {
case "minimize":
btn.onclick = (e) => { e.preventDefault(); tryAction(() => win.minimize()); };
break;
case "maximize":
if (config.restoreIcon) {
const syncMaxIcon = () => {
win.isMaximized().then(maximized => {
btn.innerHTML = maximized ? config.restoreIcon : (config.icons?.maximize || "");
btn.setAttribute("aria-label", maximized ? "Restore window" : "Maximize window");
});
};
syncMaxIcon();
win.onResized(syncMaxIcon);
}
btn.onclick = (e) => { e.preventDefault(); tryAction(() => win.toggleMaximize()); };
break;
case "close":
btn.onclick = () => win.close();
break;
}
btn.addEventListener(
"contextmenu",
(e) => {
suppressWebViewContextMenu(e);
if (id === "maximize" && config.snapOverlay) {
cancelSnap();
setTimeout(() => {
win
.setFocus()
.then(() => invoke("plugin:decor|show_snap_overlay"))
.catch(() => {});
}, 0);
}
},
true
);
parent.appendChild(btn);
});
if (config.css) {
const style = document.createElement("style");
style.innerHTML = config.css;
document.head.appendChild(style);
}
requestAnimationFrame(updateControlsInset);
window.addEventListener("resize", updateControlsInset);
};
const tbEl = document.querySelector("[data-tauri-decor-tb]");
if (tbEl) {
setup(tbEl);
return;
}
const observer = new MutationObserver(() => {
const el = document.querySelector("[data-tauri-decor-tb]");
if (el) {
observer.disconnect();
setup(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
};
run();
installRuntimeListener();
}