(function (root, factory) { // this is the stupid UMD pattern, but eh we lost that battle a long time ago if (typeof exports === 'object' && typeof module !== 'undefined') { var exports_obj = factory(); module.exports = exports_obj.$; // Also export individual components for (var key in exports_obj) { if (key !== '$' && exports_obj.hasOwnProperty(key)) { exports[key] = exports_obj[key]; } } } else if (typeof define === 'function' && define.amd) { define([], factory); } else { var exports_obj = factory(); root.$ = exports_obj.$; for (var key in exports_obj) { if (key !== '$' && exports_obj.hasOwnProperty(key)) { root[key] = exports_obj[key]; } } } }(typeof self !== 'undefined' ? self : this, function () { // a collection of DOM utility functions that I thought were cool in jQuery class EventEmitter { constructor() { /** Map> */ this._topics = new Map(); } /** * Subscribe to `topic`. If `slot_key` is provided (truthy), * it dedupes by that key; otherwise a unique Symbol is used. * Returns an unsubscribe fn. */ on(topic, handler, slot_key = null) { let map = this._topics.get(topic); if (!map) { map = new Map(); this._topics.set(topic, map); } // use the provided slot_key or a fresh Symbol() const key = slot_key != null ? slot_key : Symbol(); map.set(key, handler); return () => this.off(topic, key); } /** * Unsubscribe by handler function or by slot_key. */ off(topic, handlerOrSlotKey) { const map = this._topics.get(topic); if (!map) return; // if it matches a slotKey directly, remove it if (map.has(handlerOrSlotKey)) { map.delete(handlerOrSlotKey); } else { // otherwise assume it's a function: remove all matching fns for (const [key, fn] of map.entries()) { if (fn === handlerOrSlotKey) { map.delete(key); } } } if (map.size === 0) { this._topics.delete(topic); } } /** * Emit to all handlers on `topic`. Handlers returning * 'remove_handler' are auto-removed. * Returns the number of handlers invoked. */ emit(topic, ...args) { let count = 0; const map = this._topics.get(topic); if (!map) return count; for (const [key, fn] of Array.from(map.entries())) { const res = fn(...args); count++; if (res === 'remove_handler') { map.delete(key); } } if (map.size === 0) { this._topics.delete(topic); } return count; } } class QueryWrapper extends Array { constructor(elements) { super(); if (elements) { this.push(...(Array.isArray(elements) ? elements : [elements])); } } } $ = function(selector_or_element) { let elements; if (selector_or_element instanceof Element || selector_or_element === document || selector_or_element === window) { elements = [selector_or_element]; } else if (typeof selector_or_element === 'string') { // Check if string looks like HTML (starts with < and ends with >) if (selector_or_element.trim().charAt(0) === '<' && selector_or_element.trim().charAt(selector_or_element.trim().length - 1) === '>') { // Create element from HTML string let temp = document.createElement('div'); temp.innerHTML = selector_or_element.trim(); elements = Array.from(temp.children); } else { // Treat as CSS selector elements = Array.from(document.querySelectorAll(selector_or_element)); } } else if (selector_or_element instanceof NodeList || Array.isArray(selector_or_element)) { elements = Array.from(selector_or_element); } else { elements = [selector_or_element]; } return new QueryWrapper(elements); }; $.options = { alwaysDoDifferentialUpdate: true, }; $.events = new EventEmitter(); $.on = $.events.on.bind($.events); $.off = $.events.off.bind($.events); $.emit = $.events.emit.bind($.events); $.each = function(selector, callback) { let elements; if (typeof selector === 'string') { elements = document.querySelectorAll(selector); } else if (selector instanceof QueryWrapper) { elements = selector; } else { elements = selector; } for (let i = 0; i < elements.length; i++) { callback(elements[i], i); } } $.post = function(url, data, callback) { return $.ajax({ method: 'POST', url: url, data: data, success: callback, error: function(xhr) { console.error('POST request failed:', xhr); } }); } $.get = function(url, callback) { return $.ajax({ method: 'GET', url: url, success: callback, error: function(xhr) { console.error('GET request failed:', xhr); } }); } $.ajax = function(options) { return new Promise((resolve, reject) => { var xhr = new XMLHttpRequest(); xhr.open(options.method || 'GET', options.url, true); // Set default headers if (options.method === 'POST' || options.data) { xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); } // Set custom headers if (options.headers) { for (var key in options.headers) { xhr.setRequestHeader(key, options.headers[key]); } } xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var response; try { response = JSON.parse(xhr.responseText); } catch (e) { // If JSON parsing fails, use raw response response = xhr.responseText; } if (xhr.status >= 200 && xhr.status < 300) { if (options.success) options.success(response); resolve(response); } else { if (options.error) options.error(xhr); reject(xhr); } } }; var data = null; if (options.data) { data = typeof options.data === 'string' ? options.data : JSON.stringify(options.data); } xhr.send(data); }); } $.ready = function(callback) { if (document.readyState !== 'loading') { callback(); } else { document.addEventListener('DOMContentLoaded', callback); } } // Helper function to execute scripts in HTML content function executeScripts(htmlContent) { // Create a temporary container to parse the HTML const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlContent; // Find all script tags and disable them temporarily const scripts = tempDiv.querySelectorAll('script'); const scriptData = []; scripts.forEach((script) => { // Store script data for later execution scriptData.push({ content: script.textContent || script.innerText || '', src: script.src, type: script.type, nonce: script.nonce, async: script.async, defer: script.defer }); // Disable the script by changing its type script.type = 'text/disabled-script'; }); // Return the modified HTML and script data return { html: tempDiv.innerHTML, scripts: scriptData }; } // Helper function to execute a single script function executeScript(scriptInfo) { if (scriptInfo.src) { // External script const newScript = document.createElement('script'); if (scriptInfo.type && scriptInfo.type !== 'text/disabled-script') { newScript.type = scriptInfo.type; } if (scriptInfo.nonce) newScript.nonce = scriptInfo.nonce; if (scriptInfo.async) newScript.async = scriptInfo.async; if (scriptInfo.defer) newScript.defer = scriptInfo.defer; newScript.src = scriptInfo.src; document.head.appendChild(newScript); } else if (scriptInfo.content.trim()) { // Inline script const newScript = document.createElement('script'); if (scriptInfo.type && scriptInfo.type !== 'text/disabled-script') { newScript.type = scriptInfo.type; } if (scriptInfo.nonce) newScript.nonce = scriptInfo.nonce; newScript.text = scriptInfo.content; // Execute by inserting and immediately removing document.head.appendChild(newScript); document.head.removeChild(newScript); } } // Add all the jQuery-like methods to QueryWrapper QueryWrapper.prototype.parent = function() { const parents = Array.from(this).map(el => el.parentNode).filter(el => el); return new QueryWrapper(parents); } QueryWrapper.prototype.load = function(url, opt = {}) { return new Promise((resolve, reject) => { const ajaxOptions = { method: opt.method || (opt.postData ? 'POST' : 'GET'), url: url, success: (response) => { if(opt.replace) this.replaceWith(response, opt); else this.html(response, opt); if (opt.onLoad) opt.onLoad(response, null); resolve(this); }, error: (xhr) => { if (opt.onError) opt.onError(xhr); reject(new Error('Failed to load: ' + url)); } }; if (opt.postData) { ajaxOptions.data = opt.postData; } $.ajax(ajaxOptions); }); } QueryWrapper.prototype.html = function(opt_content = false, prop = {}) { if (opt_content === false) { return Array.from(this).map(el => el.innerHTML).join(''); } if(typeof prop.diff == 'undefined') prop.diff = $.options.alwaysDoDifferentialUpdate; if(prop.diff === true) { const temp = document.createElement('div'); temp.innerHTML = opt_content; this.forEach(el => { morphdom(el, temp, { childrenOnly: true }); }); } else { this.forEach(el => { const result = executeScripts(opt_content); el.innerHTML = result.html; result.scripts.forEach(executeScript); }); } return this; } QueryWrapper.prototype.text = function(opt_content = false) { if (opt_content === false) { return Array.from(this).map(el => el.textContent).join(''); } this.forEach(el => { el.textContent = opt_content; }); return this; } QueryWrapper.prototype.replaceWith = function(content, prop = {}) { if(typeof prop.diff == 'undefined') prop.diff = $.options.alwaysDoDifferentialUpdate; if(prop.diff === true) { this.forEach(el => { morphdom(el, content); }); } else { if(typeof content === 'string') { this.forEach(el => { const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; el.parentNode.replaceChild(tempDiv.firstChild, el); }); } else if(content instanceof Element) { this.forEach(el => { el.parentNode.replaceChild(content.cloneNode(true), el); }); } } return this; } QueryWrapper.prototype.query = function(selector) { const found = Array.from(this).reduce((acc, el) => { const results = el.querySelectorAll(selector); return acc.concat(Array.from(results)); }, []); return new QueryWrapper(found); } QueryWrapper.prototype.find = function(selector) { const found = Array.from(this).reduce((acc, el) => { const results = el.querySelectorAll(selector); return acc.concat(Array.from(results)); }, []); return new QueryWrapper(found); } QueryWrapper.prototype.on = function(event, handler) { this.forEach(function(el) { el.addEventListener(event, handler); }); return this; } QueryWrapper.prototype.off = function(event, handler) { this.forEach(function(el) { el.removeEventListener(event, handler); }); return this; } QueryWrapper.prototype.hide = function() { return this.css({ display: 'none' }); } QueryWrapper.prototype.show = function() { return this.css({ display: '' }); } QueryWrapper.prototype.toggle = function() { this.forEach(function(el) { el.style.display = (el.style.display === 'none' || el.style.display === '') ? '' : 'none'; }); return this; } QueryWrapper.prototype.attr = function(name, value) { if (value === undefined) { return this.length > 0 ? this[0].getAttribute(name) : null; } this.forEach(function(el) { el.setAttribute(name, value); }); return this; } QueryWrapper.prototype.addClass = function(classNameOrList) { var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList]; this.forEach(function(el) { classList.forEach(function(classNames) { // Split space-separated class names var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; }); classes.forEach(function(className) { el.classList.add(className); }); }); }); return this; } QueryWrapper.prototype.removeClass = function(classNameOrList) { var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList]; this.forEach(function(el) { classList.forEach(function(classNames) { // Split space-separated class names var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; }); classes.forEach(function(className) { el.classList.remove(className); }); }); }); return this; } QueryWrapper.prototype.toggleClass = function(classNameOrList, force) { var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList]; this.forEach(function(el) { classList.forEach(function(classNames) { // Split space-separated class names var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; }); classes.forEach(function(className) { if (typeof force !== 'undefined') { el.classList.toggle(className, force); } else { el.classList.toggle(className); } }); }); }); return this; } QueryWrapper.prototype.empty = function() { this.forEach(function(el) { el.innerHTML = ''; }); return this; } QueryWrapper.prototype.css = function(styles, optOrValue) { // If no arguments or first argument is a string (getter mode) if (arguments.length === 0 || (typeof styles === 'string' && arguments.length === 1)) { if (this.length === 0) return undefined; var el = this[0]; if (typeof styles === 'string') { // Get computed style for a specific property return window.getComputedStyle(el)[styles]; } else { // Return computed styles object (not commonly used) return window.getComputedStyle(el); } } // Setter mode this.forEach(function(el) { if (typeof styles === 'string' && arguments.length >= 2) { // Single property setter: .css('prop', 'value') el.style[styles] = optOrValue; } else if (typeof styles === 'object') { // Multiple properties setter: .css({prop1: 'value1', prop2: 'value2'}) for (var key in styles) { el.style[key] = styles[key]; } } }); return this; } QueryWrapper.prototype.each = function(callback) { this.forEach(function(el, index) { callback(el, index); }); return this; } QueryWrapper.prototype.append = function(child_or_html) { this.forEach(function(el) { if (typeof child_or_html === 'string' && el.insertAdjacentHTML) { if (child_or_html.includes(' max) return max; return v; } function pick_entry_from_range(array, value) { if (!array) return {}; let result = {}; for (const [pv] of Object.entries(array)) { if (value >= pv.from && value <= pv.to) result = pv; } return result; } return { $: $, EventEmitter: EventEmitter, QueryWrapper: QueryWrapper, first: first, clamp: clamp, pick_entry_from_range: pick_entry_from_range }; }));