UNPKG

@pageblock/utils

Version:

A modern utility library for PageBlock and Webflow, providing reusable components and utilities for web applications and Webflow sites

358 lines (357 loc) 13.7 kB
const CONSTANTS = { STORAGE_PREFIX: "pb_countdown_", UPDATE_INTERVAL: 1e3, ATTRIBUTES: { COUNTDOWN: "data-pb-countdown", DAILY: "data-pb-countdown-daily", STORE: "data-pb-countdown-store", ID: "data-pb-countdown-id", MINUTES: "data-pb-countdown-minutes", END: "data-pb-countdown-end", SHOW: "data-pb-countdown-show", HIDE: "data-pb-countdown-hide", SHOW_TARGET: "data-pb-countdown-show-target", MESSAGE_TARGET: "data-pb-countdown-message-target", FREEZE: "data-pb-countdown-freeze" }, EVENTS: { COMPLETE: "countdown:complete", PAUSE: "countdown:pause", RESUME: "countdown:resume" } }; class Countdown { /** * @param {Object} options - Countdown options * @param {boolean} [options.debug=false] - Enable debug mode */ constructor(options = {}) { this.debug = options.debug || false; this.countdowns = /* @__PURE__ */ new Map(); this.init(); } /** * Debug logging function * @private */ log(...args) { if (this.debug) { console.log("[Countdown]", ...args); } } /** * Validate countdown configuration * @private * @param {CountdownConfig} config - Configuration to validate * @returns {string[]} Array of error messages */ validateConfig(config) { const errors = []; if (config.storageKey && !config.id) { errors.push("ID is required when storage is enabled"); } if (config.expiresDaily && (config.messageTarget || config.storageKey)) { errors.push("Daily countdown cannot use message target or storage features"); } if (config.endDate && isNaN(new Date(config.endDate).getTime())) { errors.push("Invalid end date format"); } return errors; } init() { const countdownElements = document.querySelectorAll("[data-pb-countdown]"); countdownElements.forEach((element) => this.setupCountdown(element)); } setupCountdown(element) { try { if (!element) { console.error("Invalid countdown element"); return; } if (element.hasAttribute("data-pb-countdown-freeze")) { element.setAttribute("data-pb-countdown-store", ""); } if (element.hasAttribute("data-pb-countdown-daily") && (element.hasAttribute("data-pb-countdown-message-target") || element.hasAttribute("data-pb-countdown-store"))) { console.warn("Daily countdown cannot use message target or storage features"); element.removeAttribute("data-pb-countdown-message-target"); element.removeAttribute("data-pb-countdown-store"); } if ((element.hasAttribute("data-pb-countdown-store") || element.hasAttribute("data-pb-countdown-freeze")) && !element.hasAttribute("data-pb-countdown-id")) { console.error("Storage/freeze enabled but no ID provided. Add data-pb-countdown-id attribute."); return; } const config = this.parseConfig(element); const now = /* @__PURE__ */ new Date(); let endTime = this.getEndTime(config, now); if (!endTime || isNaN(endTime.getTime())) { console.error("Invalid end time configuration. Please check your date format or minutes value."); return; } if (config.showDate) { const showAtDate = new Date(config.showDate); if (isNaN(showAtDate.getTime())) { console.error("Invalid show date configuration. Please use a valid date format."); return; } if (now < showAtDate) { element.style.display = "none"; } } const countdown = { element, config, endTime, timer: null, displayElement: element.querySelector("[data-pb-countdown-display]"), unitElements: { days: element.querySelector('[data-pb-countdown-unit="days"] [data-pb-countdown-display]'), hours: element.querySelector('[data-pb-countdown-unit="hours"] [data-pb-countdown-display]'), minutes: element.querySelector('[data-pb-countdown-unit="minutes"] [data-pb-countdown-display]'), seconds: element.querySelector('[data-pb-countdown-unit="seconds"] [data-pb-countdown-display]') } }; if (!countdown.displayElement && !Object.values(countdown.unitElements).some((el) => el)) { console.error("No display elements found"); return; } this.countdowns.set(element, countdown); this.startCountdown(countdown); } catch (error) { console.error("Error setting up countdown:", error); } } parseConfig(element) { return { settings: JSON.parse(element.getAttribute("data-pb-countdown-settings") || "{}"), endDate: element.getAttribute("data-pb-countdown-end"), startDate: element.getAttribute("data-pb-countdown-start"), showDate: element.getAttribute("data-pb-countdown-show"), hideOnComplete: element.getAttribute("data-pb-countdown-hide") !== null, showTarget: element.getAttribute("data-pb-countdown-show-target"), messageTarget: element.getAttribute("data-pb-countdown-message-target"), expiresDaily: element.getAttribute("data-pb-countdown-daily") !== null, minutes: parseInt(element.getAttribute("data-pb-countdown-minutes")) || null, storageKey: element.getAttribute("data-pb-countdown-store") !== null, id: element.getAttribute("data-pb-countdown-id"), freeze: element.getAttribute("data-pb-countdown-freeze") !== null }; } getEndTime(config, now) { let endTime; if (config.storageKey && config.id) { endTime = this.getStoredEndTime(config.id, now); } if (!endTime || isNaN(endTime.getTime())) { endTime = this.calculateEndTime(config, now); if (config.storageKey && config.id && endTime) { this.storeEndTime(config.id, now, endTime); } } return endTime; } getStoredEndTime(id, now) { try { const storedData = localStorage.getItem(`${CONSTANTS.STORAGE_PREFIX}${id}`); if (storedData) { const data = JSON.parse(storedData); const endTime = new Date(data.endTime); if (now >= endTime) { localStorage.removeItem(`${CONSTANTS.STORAGE_PREFIX}${id}`); return null; } return endTime; } } catch (error) { console.warn("Error reading stored countdown data:", error); } return null; } storeEndTime(id, startTime, endTime) { try { localStorage.setItem(`${CONSTANTS.STORAGE_PREFIX}${id}`, JSON.stringify({ startTime: startTime.toISOString(), endTime: endTime.toISOString() })); } catch (error) { console.warn("Error storing countdown data:", error); } } calculateEndTime(config, now) { if (config.minutes) { return new Date(now.getTime() + config.minutes * 6e4); } else if (config.expiresDaily) { const endTime = new Date(now); endTime.setHours(23, 59, 59, 999); return endTime; } else if (config.endDate) { const endTime = new Date(config.endDate); return isNaN(endTime.getTime()) ? null : endTime; } return new Date(now.getTime() + 24 * 60 * 60 * 1e3); } startCountdown(countdown) { const updateTimer = () => { const now = /* @__PURE__ */ new Date(); const distance = countdown.endTime - now; if (distance < 0) { this.handleCountdownEnd(countdown); return; } const timeUnits = this.calculateTimeUnits(distance); this.updateDisplay(countdown, timeUnits); if (countdown.config.freeze && countdown.config.storageKey && countdown.config.id) { this.storeFrozenState(countdown.config.id, timeUnits); } }; if (countdown.config.freeze && countdown.config.storageKey && countdown.config.id) { const frozenState = this.getFrozenState(countdown.config.id); if (frozenState) { const totalSeconds = frozenState.days * 24 * 60 * 60 + frozenState.hours * 60 * 60 + frozenState.minutes * 60 + frozenState.seconds; countdown.endTime = new Date((/* @__PURE__ */ new Date()).getTime() + totalSeconds * 1e3); this.updateDisplay(countdown, frozenState); } } updateTimer(); countdown.timer = setInterval(updateTimer, 1e3); } calculateTimeUnits(distance) { return { days: Math.floor(distance / (1e3 * 60 * 60 * 24)), hours: Math.floor(distance % (1e3 * 60 * 60 * 24) / (1e3 * 60 * 60)), minutes: Math.floor(distance % (1e3 * 60 * 60) / (1e3 * 60)), seconds: Math.floor(distance % (1e3 * 60) / 1e3) }; } updateDisplay(countdown, timeUnits) { const { days, hours, minutes, seconds } = timeUnits; if (Object.values(countdown.unitElements).some((el) => el)) { if (countdown.unitElements.days) countdown.unitElements.days.textContent = days.toString().padStart(2, "0"); if (countdown.unitElements.hours) countdown.unitElements.hours.textContent = hours.toString().padStart(2, "0"); if (countdown.unitElements.minutes) countdown.unitElements.minutes.textContent = minutes.toString().padStart(2, "0"); if (countdown.unitElements.seconds) countdown.unitElements.seconds.textContent = seconds.toString().padStart(2, "0"); } else if (countdown.displayElement) { let display = ""; if (days > 0) { display = `${days.toString().padStart(2, "0")}:`; } display += `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; countdown.displayElement.textContent = display; } else { console.warn("No display elements found for countdown. Please add either a [data-pb-countdown-display] element or individual unit elements."); } } handleCountdownEnd(countdown) { clearInterval(countdown.timer); if (countdown.config.hideOnComplete) { countdown.element.style.display = "none"; } if (countdown.config.showTarget) { const targetElement = document.querySelector(countdown.config.showTarget); if (targetElement) { targetElement.style.display = "block"; countdown.element.style.display = "none"; } } if (countdown.config.messageTarget && !countdown.config.expiresDaily) { const messageElement = document.querySelector(countdown.config.messageTarget); if (messageElement) { messageElement.style.display = "block"; } } if (countdown.config.storageKey && countdown.config.id) { try { if (!countdown.config.freeze) { localStorage.removeItem(`${CONSTANTS.STORAGE_PREFIX}${countdown.config.id}`); } } catch (error) { this.log("Error clearing stored countdown:", error); } } if (countdown.config.expiresDaily) { countdown.endTime = /* @__PURE__ */ new Date(); countdown.endTime.setHours(23, 59, 59, 999); this.startCountdown(countdown); } else { this.countdowns.delete(countdown.element); } countdown.element.dispatchEvent(new CustomEvent("countdown:complete", { detail: { countdown } })); } /** * Pause a specific countdown * @param {string} countdownId - ID of the countdown to pause */ pauseCountdown(countdownId) { const countdown = Array.from(this.countdowns.values()).find((c) => c.config.id === countdownId); if (countdown) { clearInterval(countdown.timer); countdown.pausedAt = /* @__PURE__ */ new Date(); this.log(`Paused countdown: ${countdownId}`); countdown.element.dispatchEvent(new CustomEvent(CONSTANTS.EVENTS.PAUSE, { detail: { countdown } })); } } /** * Resume a paused countdown * @param {string} countdownId - ID of the countdown to resume */ resumeCountdown(countdownId) { const countdown = Array.from(this.countdowns.values()).find((c) => c.config.id === countdownId); if (countdown && countdown.pausedAt) { const pauseDuration = /* @__PURE__ */ new Date() - countdown.pausedAt; countdown.endTime = new Date(countdown.endTime.getTime() + pauseDuration); delete countdown.pausedAt; this.startCountdown(countdown); this.log(`Resumed countdown: ${countdownId}`); countdown.element.dispatchEvent(new CustomEvent(CONSTANTS.EVENTS.RESUME, { detail: { countdown } })); } } storeFrozenState(id, timeUnits) { try { localStorage.setItem(`${CONSTANTS.STORAGE_PREFIX}${id}_frozen`, JSON.stringify(timeUnits)); } catch (error) { console.warn("Error storing frozen state:", error); } } getFrozenState(id) { try { const frozenData = localStorage.getItem(`${CONSTANTS.STORAGE_PREFIX}${id}_frozen`); if (frozenData) { return JSON.parse(frozenData); } } catch (error) { console.warn("Error reading frozen state:", error); } return null; } destroy() { this.countdowns.forEach((countdown) => { clearInterval(countdown.timer); if (countdown.config.storageKey && countdown.config.id) { try { if (!countdown.config.freeze) { localStorage.removeItem(`${CONSTANTS.STORAGE_PREFIX}${countdown.config.id}`); } } catch (error) { this.log("Error clearing stored countdown:", error); } } countdown.element.removeEventListener(CONSTANTS.EVENTS.COMPLETE, null); countdown.element.removeEventListener(CONSTANTS.EVENTS.PAUSE, null); countdown.element.removeEventListener(CONSTANTS.EVENTS.RESUME, null); countdown.displayElement = null; countdown.unitElements = null; }); this.countdowns.clear(); this.log("All countdowns destroyed"); } } export { Countdown, Countdown as default }; //# sourceMappingURL=countdown.js.map