"use strict"; "use client"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.tsx var src_exports = {}; __export(src_exports, { default: () => src_default, startHolyLoader: () => startHolyLoader, stopHolyLoader: () => stopHolyLoader }); module.exports = __toCommonJS(src_exports); var React = __toESM(require("react"), 1); // src/constants.ts var DEFAULTS = { color: "#59a2ff", initialPosition: 0.08, height: 4, easing: "ease", speed: 200, zIndex: 2147483647, showSpinner: false, boxShadow: void 0, ignoreSearchParams: false, dir: "ltr" }; var START_HOLY_EVENT = "holy-progress-start"; var STOP_HOLY_EVENT = "holy-progress-stop"; // src/utils.ts var toAbsoluteURL = /* @__PURE__ */ __name((url) => { return new URL(url, window.location.href).href; }, "toAbsoluteURL"); var isSamePageAnchor = /* @__PURE__ */ __name((currentUrl, newUrl) => { const current = new URL(toAbsoluteURL(currentUrl)); const next = new URL(toAbsoluteURL(newUrl)); return current.href.split("#")[0] === next.href.split("#")[0]; }, "isSamePageAnchor"); var isSameHost = /* @__PURE__ */ __name((currentUrl, newUrl) => { const current = new URL(toAbsoluteURL(currentUrl)); const next = new URL(toAbsoluteURL(newUrl)); return current.hostname.replace(/^www\./, "") === next.hostname.replace(/^www\./, ""); }, "isSameHost"); var paramsAreEqual = /* @__PURE__ */ __name((params1, params2) => Array.from(params1).every( ([key, value]) => params2.has(key) && params2.get(key) === value ), "paramsAreEqual"); var hasSameQueryParameters = /* @__PURE__ */ __name((currentUrl, newUrl) => { const current = new URL(toAbsoluteURL(currentUrl)); const next = newUrl instanceof URL ? newUrl : new URL(toAbsoluteURL(newUrl)); const currentParams = new URLSearchParams(current.search); const nextParams = new URLSearchParams(next.search); return paramsAreEqual(currentParams, nextParams) && paramsAreEqual(nextParams, currentParams); }, "hasSameQueryParameters"); var isSamePathname = /* @__PURE__ */ __name((currentUrl, newUrl) => { const current = new URL(toAbsoluteURL(currentUrl)); const next = newUrl instanceof URL ? newUrl : new URL(toAbsoluteURL(newUrl)); return current.pathname === next.pathname; }, "isSamePathname"); var clamp = /* @__PURE__ */ __name((n, min, max) => Math.max(min, Math.min(n, max)), "clamp"); var queue = /* @__PURE__ */ (() => { const pending = []; const next = /* @__PURE__ */ __name(() => { const fn = pending.shift(); if (fn !== void 0) { fn(next); } }, "next"); return (fn) => { pending.push(fn); if (pending.length === 1) { next(); } }; })(); var repaintElement = /* @__PURE__ */ __name((obj) => { void obj.offsetWidth; return obj; }, "repaintElement"); // src/HolyProgress.ts var _HolyProgress = class _HolyProgress { /** * Create a HolyProgress instance. * @param {Partial} [customSettings] - Optional custom settings to override defaults. */ constructor(customSettings) { /** * Sets the progress to a specific value. * @private * @param {number} n - The new progress value (0 to 1). * @returns {HolyProgress} The current instance for chaining methods. */ this.setTo = /* @__PURE__ */ __name((n) => { const isStarted = typeof this.progressN === "number"; n = clamp(n, this.settings.initialPosition, 1); this.progressN = n === 1 ? null : n; const progressBar = this.getOrCreateBar(!isStarted); if (!progressBar) { return this; } repaintElement(progressBar); queue((next) => { if (!this.bar) { return; } Object.assign(this.bar.style, this.barPositionCSS(n), { transition: `all ${this.settings.speed}ms ${this.settings.easing}` }); if (n === 1) { progressBar.style.transition = "none"; progressBar.style.opacity = "1"; repaintElement(progressBar); setTimeout(() => { progressBar.style.transition = `all ${this.settings.speed}ms linear`; progressBar.style.opacity = "0"; setTimeout(() => { this.removeBarFromDOM(); next(); }, this.settings.speed); this.removeSpinnerFromDOM(); }, this.settings.speed); } else { setTimeout(next, this.settings.speed); } }); return this; }, "setTo"); /** * Converts a progress value (0 to 1) into a percentage representation. * Used for calculating the visual width of the progress bar. * @private * @param {number} n - The progress value to convert. * @returns {number} The percentage representation of the progress value. */ this.toBarPercentage = /* @__PURE__ */ __name((n) => this.settings.dir === "ltr" ? (-1 + n) * 100 : (1 - n) * 100, "toBarPercentage"); /** * Initiates the progress bar's movement. If already started, it continues from the current position. * Automatically handles automatic incrementation ('trickle') if enabled. * @public * @returns {HolyProgress} The current instance for chaining methods. */ this.start = /* @__PURE__ */ __name(() => { if (this.progressN === null) { this.setTo(0); this.startTrickle(); if (this.settings.showSpinner === true) { this.createSpinner(); } } return this; }, "start"); /** * Performs automatic incrementation of the progress bar. * This function is recursive and continues to increment the progress at intervals defined by `speed`. * @private */ this.startTrickle = /* @__PURE__ */ __name(() => { const run = /* @__PURE__ */ __name(() => { if (this.progressN === null) return; this.incrementStatus(); setTimeout(run, this.settings.speed); }, "run"); setTimeout(run, this.settings.speed); }, "startTrickle"); /** * Completes the progress, moving it to 100% * @public * @returns {HolyProgress} The current instance for chaining methods. */ this.complete = /* @__PURE__ */ __name(() => this.setTo(1), "complete"); /** * Calculates an increment value based on the current status of the progress. * This is used to determine the amount of progress to add during automatic incrementation. * @private * @param {number} status - The current progress status. * @returns {number} The calculated increment value. */ this.calculateIncrement = /* @__PURE__ */ __name((status) => { const base = 0.1; const scale = 5; return base * Math.exp(-scale * status); }, "calculateIncrement"); /** * Increments the progress bar by a specified amount, or by an amount determined by `calculateIncrement` if not specified. * @private * @param {number} [amount] - The amount to increment the progress bar. * @returns {HolyProgress} The current instance for chaining methods. */ this.incrementStatus = /* @__PURE__ */ __name((amount) => { if (this.progressN === null) { return this.start(); } if (this.progressN > 1) { return this; } if (typeof amount !== "number") { amount = this.calculateIncrement(this.progressN); } this.progressN = clamp(this.progressN + amount, 0, 0.994); return this.setTo(this.progressN); }, "incrementStatus"); /** * Creates and initializes a new progress bar element in the DOM. * It sets up the necessary styles and appends the element to the document body. * @private * @param {boolean} fromStart - Indicates if the bar is created from the start position. * @returns {HTMLElement | null} The created progress bar element, or null if creation fails. */ this.createBar = /* @__PURE__ */ __name((fromStart) => { var _a, _b; const barContainer = document.createElement("div"); barContainer.id = "holy-progress"; barContainer.style.pointerEvents = "none"; barContainer.innerHTML = '
'; this.bar = barContainer.querySelector( '[role="bar"]' ); if (!this.bar) { return null; } const percentage = this.toBarPercentage( fromStart ? 0 : (_a = this.progressN) != null ? _a : 0 ); this.bar.style.background = this.settings.color; if (typeof this.settings.height === "number") { this.bar.style.height = `${this.settings.height}px`; } else { this.bar.style.height = this.settings.height; } this.bar.style.zIndex = this.settings.zIndex.toString(); this.bar.style.position = "fixed"; this.bar.style.width = "100%"; this.bar.style.top = "0"; this.bar.style.left = "0"; this.bar.style.transition = "all 0 linear"; this.bar.style.transform = `translate3d(${percentage}%,0,0)`; this.bar.style.boxShadow = (_b = this.settings.boxShadow) != null ? _b : ""; document.body.appendChild(barContainer); return barContainer; }, "createBar"); /** * Creates and initializes a new spinner element in the DOM. * It sets up the necessary styles and appends the element to the document body. * @private * @returns {void} */ this.createSpinner = /* @__PURE__ */ __name(() => { if (document.getElementById("holy-progress-spinner") !== null) { return; } const spinner = document.createElement("div"); spinner.id = "holy-progress-spinner"; spinner.style.pointerEvents = "none"; spinner.style.display = "block"; spinner.style.position = "fixed"; spinner.style.zIndex = this.settings.zIndex.toString(); spinner.style.top = "15px"; spinner.style.right = "15px"; spinner.style.width = "18px"; spinner.style.height = "18px"; spinner.style.boxSizing = "border-box"; spinner.style.border = "solid 2px transparent"; spinner.style.borderTopColor = this.settings.color; spinner.style.borderLeftColor = this.settings.color; spinner.style.borderRadius = "50%"; spinner.style.animation = "holy-progress-spinner 400ms linear infinite"; const keyframes = ` @keyframes holy-progress-spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; const style = document.createElement("style"); style.innerHTML = keyframes; spinner.appendChild(style); document.body.appendChild(spinner); }, "createSpinner"); this.getOrCreateBar = /* @__PURE__ */ __name((fromStart) => { var _a; return (_a = document.getElementById("holy-progress")) != null ? _a : this.createBar(fromStart); }, "getOrCreateBar"); this.removeBarFromDOM = /* @__PURE__ */ __name(() => { var _a; return (_a = document.getElementById("holy-progress")) == null ? void 0 : _a.remove(); }, "removeBarFromDOM"); this.removeSpinnerFromDOM = /* @__PURE__ */ __name(() => { var _a; return (_a = document.getElementById("holy-progress-spinner")) == null ? void 0 : _a.remove(); }, "removeSpinnerFromDOM"); /** * Determines the most suitable CSS positioning strategy based on browser capabilities. * Checks for transform properties with vendor prefixes and standard un-prefixed properties. * @private * @returns {TransformStrategy} - The optimal CSS positioning strategy ('translate3d', 'translate', or 'margin'). */ this.getTransformStrategy = /* @__PURE__ */ __name(() => { const style = document.body.style; const prefixes = ["Webkit", "Moz", "ms", "O", ""]; let transformProp = ""; for (let i = 0; i < prefixes.length; i++) { if (`${prefixes[i]}Transform` in style) { transformProp = prefixes[i]; break; } } if (transformProp !== "" && `${transformProp}Perspective` in style) { return "translate3d"; } if (transformProp !== "") { return "translate"; } return "margin"; }, "getTransformStrategy"); /** * Generates the CSS for the progress bar position based on the detected positioning strategy. * Dynamically sets the transform or margin-left properties for the bar's position. * @private * @param {number} n - Position value of the bar, as a number between 0 and 1. * @returns {Object} - CSS styles for the progress bar. */ this.barPositionCSS = /* @__PURE__ */ __name((n) => { const transformStrategy = this.getTransformStrategy(); const barPosition = `${this.toBarPercentage(n)}%`; if (transformStrategy === "translate3d") { return { transform: `translate3d(${barPosition},0,0)` }; } if (transformStrategy === "translate") { return { transform: `translate(${barPosition},0)` }; } return { marginLeft: barPosition }; }, "barPositionCSS"); this.settings = __spreadValues(__spreadValues({}, DEFAULTS), customSettings); this.progressN = null; this.bar = null; } }; __name(_HolyProgress, "HolyProgress"); var HolyProgress = _HolyProgress; // src/index.tsx var startHolyLoader = /* @__PURE__ */ __name(() => { document.dispatchEvent(new Event(START_HOLY_EVENT)); }, "startHolyLoader"); var stopHolyLoader = /* @__PURE__ */ __name(() => { document.dispatchEvent(new Event(STOP_HOLY_EVENT)); }, "stopHolyLoader"); var HolyLoader = /* @__PURE__ */ __name(({ color = DEFAULTS.color, initialPosition = DEFAULTS.initialPosition, height = DEFAULTS.height, easing = DEFAULTS.easing, speed = DEFAULTS.speed, zIndex = DEFAULTS.zIndex, boxShadow = DEFAULTS.boxShadow, showSpinner = DEFAULTS.showSpinner, ignoreSearchParams = DEFAULTS.ignoreSearchParams, dir = DEFAULTS.dir }) => { const holyProgressRef = React.useRef(null); React.useEffect(() => { const startProgress = /* @__PURE__ */ __name(() => { if (holyProgressRef.current === null) { return; } try { holyProgressRef.current.start(); } catch (error) { } }, "startProgress"); const stopProgress = /* @__PURE__ */ __name(() => { if (holyProgressRef.current === null) { return; } try { holyProgressRef.current.complete(); } catch (error) { } }, "stopProgress"); let isHistoryPatched = false; const stopProgressOnHistoryUpdate = /* @__PURE__ */ __name(() => { if (isHistoryPatched) { return; } const originalPushState = history.pushState.bind(history); history.pushState = (...args) => { const url = args[2]; if (url && isSamePathname(window.location.href, url) && (ignoreSearchParams || hasSameQueryParameters(window.location.href, url))) { originalPushState(...args); return; } stopProgress(); originalPushState(...args); }; const originalReplaceState = history.replaceState.bind(history); history.replaceState = (...args) => { const url = args[2]; if (url && isSamePathname(window.location.href, url) && (ignoreSearchParams || hasSameQueryParameters(window.location.href, url))) { originalReplaceState(...args); return; } stopProgress(); originalReplaceState(...args); }; isHistoryPatched = true; }, "stopProgressOnHistoryUpdate"); const handleClick = /* @__PURE__ */ __name((event) => { try { const target = event.target; const anchor = target.closest("a"); if (anchor === null || anchor.target === "_blank" || anchor.hasAttribute("download") || event.ctrlKey || event.metaKey || // Skip if URL points to a different domain !isSameHost(window.location.href, anchor.href) || // Skip if URL is a same-page anchor (href="#", href="#top", etc.). isSamePageAnchor(window.location.href, anchor.href) || // Skip if URL uses a non-http/https protocol (mailto:, tel:, etc.). !toAbsoluteURL(anchor.href).startsWith("http") || // Skip if the URL is the same as the current page isSamePathname(window.location.href, anchor.href) && (ignoreSearchParams || hasSameQueryParameters(window.location.href, anchor.href))) { return; } startProgress(); } catch (error) { stopProgress(); } }, "handleClick"); try { if (holyProgressRef.current === null) { holyProgressRef.current = new HolyProgress({ color, height, initialPosition, easing, speed, zIndex, boxShadow, showSpinner, dir }); } document.addEventListener("click", handleClick); document.addEventListener(START_HOLY_EVENT, startProgress); document.addEventListener(STOP_HOLY_EVENT, stopProgress); stopProgressOnHistoryUpdate(); } catch (error) { } return () => { document.removeEventListener("click", handleClick); document.removeEventListener(START_HOLY_EVENT, startProgress); document.removeEventListener(STOP_HOLY_EVENT, stopProgress); }; }, [holyProgressRef]); return null; }, "HolyLoader"); var src_default = HolyLoader; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { startHolyLoader, stopHolyLoader }); //# sourceMappingURL=index.cjs.map