This repository has been archived on 2026-02-28. You can view files and clone it, but cannot push or open issues or pull requests.
2025-12-20 23:41:21 +02:00

204 lines
6.7 KiB
JavaScript

const { CSS_CLASSES } = window;
const ICON_PATHS = {
checked: [
"M7.89561 14.8538L6.30462 13.2629L14.3099 5.25755L15.9009 6.84854L7.89561 14.8538Z",
"M4.08643 11.0903L5.67742 9.49929L9.4485 13.2704L7.85751 14.8614L4.08643 11.0903Z"
],
unchecked: [
"M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z",
"M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z"
]
};
const CHECKED_COLOR = "hsl(139, 47.3%, 43.9%)";
const UNCHECKED_COLOR = "hsl(218, 4.6%, 46.9%)";
function createSwitch(
labelText,
descriptionText,
inputId,
defaultChecked = false,
onToggle = null
) {
let isToggling = false;
const container = document.createElement("div");
container.className = CSS_CLASSES.container;
const checkbox = document.createElement("input");
checkbox.id = inputId;
checkbox.type = "checkbox";
checkbox.className = CSS_CLASSES.input;
checkbox.tabIndex = 0;
checkbox.checked = defaultChecked;
const sliderSVG = createSliderSVG(defaultChecked);
const switchControl = document.createElement("div");
switchControl.className = `${CSS_CLASSES.switchControl} ${defaultChecked ? CSS_CLASSES.switchControlChecked : ""}`;
switchControl.style.opacity = "1";
switchControl.style.backgroundColor = defaultChecked ? CHECKED_COLOR : UNCHECKED_COLOR;
switchControl.style.cursor = "pointer";
switchControl.tabIndex = -1;
switchControl.appendChild(sliderSVG);
switchControl.appendChild(checkbox);
const controlWrapper = document.createElement("div");
controlWrapper.className = CSS_CLASSES.control;
controlWrapper.appendChild(switchControl);
const label = document.createElement("label");
label.htmlFor = inputId;
label.className = CSS_CLASSES.label;
label.textContent = labelText;
const labelRow = document.createElement("div");
labelRow.className = CSS_CLASSES.labelRow;
labelRow.appendChild(label);
labelRow.appendChild(controlWrapper);
container.appendChild(labelRow);
if (descriptionText) {
const description = document.createElement("div");
description.className = CSS_CLASSES.description;
description.textContent = descriptionText;
const noteWrapper = document.createElement("div");
noteWrapper.className = CSS_CLASSES.note;
noteWrapper.appendChild(description);
container.appendChild(noteWrapper);
}
const divider = document.createElement("div");
divider.className = CSS_CLASSES.divider;
container.appendChild(divider);
const updateSwitch = (checked) => {
switchControl.classList.toggle(CSS_CLASSES.switchControlChecked, checked);
switchControl.style.backgroundColor = checked ? CHECKED_COLOR : UNCHECKED_COLOR;
sliderSVG.style.left = checked ? "12px" : "-3px";
updateSVGIcon(sliderSVG, checked);
};
const handleToggle = async () => {
if (isToggling) return;
isToggling = true;
const prev = !checkbox.checked;
if (typeof onToggle === "function") {
try {
const ret = await onToggle(checkbox.checked);
if (ret === false) checkbox.checked = prev;
} finally {
isToggling = false;
}
} else {
isToggling = false;
}
updateSwitch(checkbox.checked);
};
switchControl.addEventListener("click", (e) => {
if (isToggling) return;
if (e.target === checkbox) return;
e.preventDefault();
e.stopPropagation();
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event("change", { bubbles: true }));
});
checkbox.addEventListener("click", (e) => {
if (isToggling) {
e.stopPropagation();
e.preventDefault();
return;
}
});
checkbox.addEventListener("change", handleToggle);
return container;
}
function createSliderSVG(isChecked) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("class", CSS_CLASSES.slider);
svg.setAttribute("viewBox", "0 0 28 20");
svg.setAttribute("preserveAspectRatio", "xMinYMid meet");
svg.style.left = isChecked ? "12px" : "-3px";
svg.style.position = "absolute";
svg.style.transition = "left 0.2s";
const circle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
circle.setAttribute("fill", "white");
circle.setAttribute("x", "4");
circle.setAttribute("y", "0");
circle.setAttribute("height", "20");
circle.setAttribute("width", "20");
circle.setAttribute("rx", "10");
svg.appendChild(circle);
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
icon.setAttribute("viewBox", "0 0 20 20");
icon.setAttribute("fill", "none");
const color = isChecked ? CHECKED_COLOR : UNCHECKED_COLOR;
if (isChecked) {
const checkPaths = [
"M7.89561 14.8538L6.30462 13.2629L14.3099 5.25755L15.9009 6.84854L7.89561 14.8538Z",
"M4.08643 11.0903L5.67742 9.49929L9.4485 13.2704L7.85751 14.8614L4.08643 11.0903Z"
];
checkPaths.forEach(d => {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", color);
path.setAttribute("d", d);
icon.appendChild(path);
});
} else {
const xPaths = [
"M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z",
"M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z"
];
xPaths.forEach(d => {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", color);
path.setAttribute("d", d);
icon.appendChild(path);
});
}
svg.appendChild(icon);
return svg;
}
function createSVGIcon(parentSVG, isChecked) {
const color = isChecked ? CHECKED_COLOR : UNCHECKED_COLOR;
const paths = ICON_PATHS[isChecked ? "checked" : "unchecked"];
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
icon.setAttribute("viewBox", "0 0 20 20");
icon.setAttribute("fill", "none");
paths.forEach(d => {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", color);
path.setAttribute("d", d);
icon.appendChild(path);
});
parentSVG.appendChild(icon);
}
function updateSVGIcon(sliderSVG, isChecked) {
const oldIcon = sliderSVG.querySelector("svg svg");
if (oldIcon) {
oldIcon.remove();
}
createSVGIcon(sliderSVG, isChecked);
}