// Adjustable panel gap controls
// Feature Registration Pattern implementation
import type { ReaderContext } from "../core/reader-context";
export function init(context: ReaderContext): void {
const state = createPanelGapState();
applyPanelGap(state.gapSize, state.showBorders);
context.events.on("panel-gap:set", (detail: { gap: number }) => {
setPanelGap(state, detail.gap);
});
context.events.on("panel-gap:increase", (detail?: { amount: number }) => {
increasePanelGap(state, detail?.amount);
});
context.events.on("panel-gap:decrease", (detail?: { amount: number }) => {
decreasePanelGap(state, detail?.amount);
});
context.events.on("panel-gap:borders:toggle", () => {
togglePanelBorders(state);
});
context.events.on("ui:show-settings", (detail: { container: HTMLElement }) => {
renderPanelGapControls(detail.container, state);
});
context.events.on("reader:unload", () => {
const controls = document.querySelector(".panel-gap-controls");
controls?.remove();
});
}
interface PanelGapState {
gapSize: number;
showBorders: boolean;
}
function createPanelGapState(initialGap: number = 4): PanelGapState {
const saved = localStorage.getItem("reader-panel-gap");
return {
gapSize: saved ? parseInt(saved) : initialGap,
showBorders: false,
};
}
function applyPanelGap(gap: number, showBorders: boolean): void {
document.documentElement.style.setProperty("--panel-gap", `${gap}px`);
document.documentElement.style.setProperty(
"--panel-border-width",
showBorders ? "1px" : "0px",
);
localStorage.setItem("reader-panel-gap", String(gap));
}
function setPanelGap(state: PanelGapState, gap: number): PanelGapState {
const clampedGap = Math.max(0, Math.min(20, gap));
state.gapSize = clampedGap;
document.documentElement.style.setProperty("--panel-gap", `${clampedGap}px`);
localStorage.setItem("reader-panel-gap", String(clampedGap));
const controls = document.querySelector(".panel-gap-controls");
if (controls) {
updatePanelGapUI(controls as HTMLElement, state);
}
return state;
}
function increasePanelGap(state: PanelGapState, amount: number = 2): PanelGapState {
return setPanelGap(state, state.gapSize + amount);
}
function decreasePanelGap(state: PanelGapState, amount: number = 2): PanelGapState {
return setPanelGap(state, state.gapSize - amount);
}
function togglePanelBorders(state: PanelGapState): PanelGapState {
state.showBorders = !state.showBorders;
document.documentElement.style.setProperty(
"--panel-border-width",
state.showBorders ? "1px" : "0px",
);
const controls = document.querySelector(".panel-gap-controls");
if (controls) {
updatePanelGapUI(controls as HTMLElement, state);
}
return state;
}
function renderPanelGapControls(
container: HTMLElement,
state: PanelGapState,
): void {
const existing = container.querySelector(".panel-gap-controls");
existing?.remove();
const controls = document.createElement("div");
controls.className =
"panel-gap-controls fixed bottom-24 right-4 bg-gray-900 bg-opacity-90 rounded-lg p-2 flex flex-col gap-2 z-40";
controls.innerHTML = `
${state.gapSize}px
`;
controls
.querySelector(".panel-gap-increase")
?.addEventListener("click", () => {
increasePanelGap(state);
updatePanelGapUI(controls, state);
});
controls
.querySelector(".panel-gap-decrease")
?.addEventListener("click", () => {
decreasePanelGap(state);
updatePanelGapUI(controls, state);
});
controls
.querySelector(".panel-gap-borders")
?.addEventListener("click", () => {
togglePanelBorders(state);
updatePanelGapUI(controls, state);
});
container.appendChild(controls);
}
function updatePanelGapUI(container: HTMLElement, state: PanelGapState): void {
const gapLabel = container.querySelector("span");
if (gapLabel) {
gapLabel.textContent = `${state.gapSize}px`;
}
const bordersBtn = container.querySelector(".panel-gap-borders");
if (bordersBtn) {
bordersBtn.textContent = state.showBorders ? "▦" : "▢";
}
}