/** * Toast notification types * @enum {string} */ export const ToastType = { error: "error", success: "success", warning: "warning", }; export class Dom { /** * Create a `
` * @param {Object} o * @param {string} [o.text] * @param {Partial} [o.styles] * @param {string} [o.id] * @param {string[]} [o.classes] * @param {HTMLElement[]} [o.children] * @param {Record} [o.attributes] * @returns {HTMLParagraphElement} */ static p(o = {}) { const p = document.createElement("p"); if (o.styles) Object.assign(p.style, o.styles); if (o.id) p.id = o.id; if (o.classes) for (const cls of o.classes) p.classList.add(cls); if (o.text) p.textContent = o.text; if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) p.setAttribute(k, v); if (o.children) p.append(...o.children); return p; } /** * Create a dual range input (min and max) * @param {Object} o * @param {number} o.min - Minimum possible value * @param {number} o.max - Maximum possible value * @param {number} o.currentMin - Current minimum value * @param {number} o.currentMax - Current maximum value * @param {number} o.step - Step size * @param {(min: number, max: number) => void} o.onChange - Callback when range changes * @param {Partial} [o.styles] * @param {string} [o.id] * @param {string[]} [o.classes] * @param {HTMLElement[]} [o.children] * @param {Record} [o.attributes] * @returns {HTMLDivElement} */ static range(o) { const container = document.createElement("div"); if (o.styles) Object.assign(container.style, o.styles); if (o.id) container.id = o.id; if (o.classes) for (const cls of o.classes) container.classList.add(cls); if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) container.setAttribute(k, v); // Ensure current values are within bounds const safeCurrentMin = Math.max(o.min, Math.min(o.currentMin, o.max)); const safeCurrentMax = Math.max(o.min, Math.min(o.currentMax, o.max)); // Create track container const trackContainer = Dom.div({ styles: { alignItems: "center", display: "flex", height: "20px", margin: "10px 0", position: "relative", }, }); // Create track const track = Dom.div({ styles: { background: "#e0e0e0", borderRadius: "4px", height: "6px", position: "relative", width: "100%", }, }); // Create active range const activeRange = Dom.div({ styles: { background: "#4caf50", borderRadius: "4px", height: "100%", left: "0%", position: "absolute", width: "100%", }, }); // Create min slider using Dom.input const minSlider = Dom.input({ max: o.max.toString(), min: o.min.toString(), onInput: () => { const minVal = parseInt(minSlider.value, 10); const maxVal = parseInt(maxSlider.value, 10); if (minVal > maxVal) { minSlider.value = maxVal.toString(); } updateActiveRange(); o.onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); }, step: o.step.toString(), styles: { appearance: "none", background: "transparent", height: "100%", pointerEvents: "none", position: "absolute", width: "100%", zIndex: "2", }, type: "range", value: safeCurrentMin, }); minSlider.style.pointerEvents = "auto"; // Create max slider using Dom.input const maxSlider = Dom.input({ max: o.max.toString(), min: o.min.toString(), onInput: () => { const minVal = parseInt(minSlider.value, 10); const maxVal = parseInt(maxSlider.value, 10); if (maxVal < minVal) { maxSlider.value = minVal.toString(); } updateActiveRange(); o.onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); }, step: o.step.toString(), styles: { appearance: "none", background: "transparent", height: "100%", pointerEvents: "none", position: "absolute", width: "100%", zIndex: "2", }, type: "range", value: safeCurrentMax, }); maxSlider.style.pointerEvents = "auto"; // Value displays const minValueDisplay = Dom.span({ styles: { color: "#0066cc", fontSize: "0.85rem", fontWeight: "bold", }, text: safeCurrentMin.toString(), }); const maxValueDisplay = Dom.span({ styles: { color: "#0066cc", fontSize: "0.85rem", fontWeight: "bold", }, text: safeCurrentMax.toString(), }); const valueDisplay = Dom.div({ children: [minValueDisplay, maxValueDisplay], styles: { display: "flex", justifyContent: "space-between", marginBottom: "5px", }, }); // Update active range position const updateActiveRange = () => { const minVal = parseInt(minSlider.value, 10); const maxVal = parseInt(maxSlider.value, 10); const minPercent = ((minVal - o.min) / (o.max - o.min)) * 100; const maxPercent = ((maxVal - o.min) / (o.max - o.min)) * 100; activeRange.style.left = `${minPercent}%`; activeRange.style.width = `${maxPercent - minPercent}%`; minValueDisplay.textContent = minVal.toString(); maxValueDisplay.textContent = maxVal.toString(); }; // Initial update updateActiveRange(); // Assemble track track.append(activeRange, minSlider, maxSlider); trackContainer.appendChild(track); // Assemble container container.append(valueDisplay, trackContainer); if (o.children) container.append(...o.children); return container; } }