UNPKG

tiny-essentials

Version:

Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.

175 lines (150 loc) 4.84 kB
'use strict'; /** * A basic function that performs a task when the system is ready. * Used for handlers in the readiness queue. * * @typedef {() => void} Fn */ /** * A function that determines whether a specific handler should be executed. * Should return `true` to allow execution, or `false` to skip the handler. * * @typedef {() => boolean} FnFilter */ /** * @typedef {Object} Handler * @property {Fn} fn - Function to execute when ready. * @property {boolean} once - Whether to execute only once. * @property {number} priority - Execution order (higher priority runs first). * @property {FnFilter|null} filter - Optional filter function to determine execution. * @property {boolean} domOnly - Whether to run as soon as DOM is ready (before full readiness). */ class TinyDomReadyManager { /** @type {Handler[]} */ #handlers = []; /** @type {boolean} */ #isDomReady = false; /** @type {boolean} */ #isFullyReady = false; /** @type {Promise<any>[]} */ #promises = []; /** * Checks if the DOM is ready and if all Promises have been resolved. */ #checkAllReady() { if (this.#isDomReady) { Promise.all(this.#promises) .then(() => { this.#isFullyReady = true; this.#runHandlers(false); // run non-domOnly }) .catch((err) => { console.error('[TinyDomReadyManager] Promise rejected:', err); }); } } /** * Executes handlers by filtering them by `domOnly` flag and sorting by priority. * @param {boolean} domOnlyOnly - Whether to run only `domOnly` handlers. */ #runHandlers(domOnlyOnly) { this.#handlers .filter((h) => h.domOnly === domOnlyOnly) .sort((a, b) => b.priority - a.priority) .forEach((handler) => this.#invokeHandler(handler)); this.#handlers = this.#handlers.filter((h) => !(h.once && (domOnlyOnly ? h.domOnly : true))); } /** * Executes a handler if its filter passes. * @param {Handler} handler */ #invokeHandler(handler) { if (typeof handler.filter === 'function') { try { if (!handler.filter()) return; } catch (err) { console.warn('[TinyDomReadyManager] Filter error:', err); return; } } try { handler.fn(); } catch (err) { console.error('[TinyDomReadyManager] Handler error:', err); } } /** * Marks the system as DOM-ready and runs DOM-only handlers. * @private */ _markDomReady() { this.#isDomReady = true; this.#runHandlers(true); // Run domOnly this.#checkAllReady(); // Then check for full readiness } /** * Initializes the manager using `DOMContentLoaded`. */ init() { if (this.#isDomReady) throw new Error('[TinyDomReadyManager] init() has already been called.'); if (document.readyState === 'interactive' || document.readyState === 'complete') { this._markDomReady(); } else { document.addEventListener('DOMContentLoaded', () => this._markDomReady()); } } /** * Adds a Promise to delay full readiness. * @param {Promise<any>} promise * @throws {TypeError} */ addPromise(promise) { if (!(promise instanceof Promise)) throw new TypeError('[TinyDomReadyManager] promise must be a valid Promise.'); if (this.#isFullyReady) return; this.#promises.push(promise); if (this.#isDomReady) this.#checkAllReady(); } /** * Registers a handler to run either after DOM is ready or after full readiness. * * @param {Fn} fn - Function to execute. * @param {Object} [options] * @param {boolean} [options.once=true] - Execute only once. * @param {number} [options.priority=0] - Higher priority runs first. * @param {FnFilter|null} [options.filter=null] - Optional filter function. * @param {boolean} [options.domOnly=false] - If true, executes after DOM ready only. * @throws {TypeError} If fn is not a function. */ onReady(fn, { once = true, priority = 0, filter = null, domOnly = false } = {}) { if (typeof fn !== 'function') throw new TypeError('[TinyDomReadyManager] fn must be a function.'); const handler = { fn, once, priority, filter, domOnly }; if (domOnly && this.#isDomReady) { this.#invokeHandler(handler); if (!once) this.#handlers.push(handler); return; } if (!domOnly && this.#isFullyReady) { this.#invokeHandler(handler); } else { this.#handlers.push(handler); } } /** * Returns whether the system is fully ready (DOM + Promises). * @returns {boolean} */ isReady() { return this.#isFullyReady; } /** * Returns whether the DOM is ready (DOMContentLoaded has fired). * Does not wait for promises. * @returns {boolean} */ isDomReady() { return this.#isDomReady; } } module.exports = TinyDomReadyManager;