/* $ SELECTOR */ HTMLElement.prototype.$ = function(selector) { return window.$(selector, this) } DocumentFragment.prototype.$ = function(selector) { return window.$(selector, this) } window.$ = function(selector, el = document) { return el.querySelector(selector) } window.$$ = function(selector, el = document) { return Array.from(el.querySelectorAll(selector)) } /* CONSOLE */ console.red = function(message) { this.log(`%c${message}`, "color: rgb(254, 79, 42);"); }; console.green = function(message) { this.log(`%c${message}`, "color: rgb(79, 254, 42);"); } /* GET CSS VARIABLES FOR DARK OR LIGHT MODE */ window.darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; document.documentElement.classList.add(darkMode ? 'dark' : 'light'); window.getColor = function(name) { const rootStyles = getComputedStyle(document.documentElement); const color = rootStyles.getPropertyValue(`--${name}`).trim(); if(!color) { throw new Error("Color not found") } return color } /* MOBILE */ window.isMobile = function isMobile() { return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent); } window.css = function css(cssString) { let container = document.querySelector("style#pageStyle"); if(!container) { container = document.createElement('style'); container.id = "pageStyle"; document.head.appendChild(container); } let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); primarySelector = primarySelector.replace(/\*/g, "all"); primarySelector = primarySelector.replace(/#/g, "id-"); primarySelector = primarySelector.replace(/,/g, ""); let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) if(!stylesheet) { stylesheet = document.createElement('style'); stylesheet.id = primarySelector; stylesheet.appendChild(document.createTextNode(cssString)); container.appendChild(stylesheet); } else { stylesheet.innerText = cssString } } window.html = function html(elementString) { let parser = new DOMParser(); let doc = parser.parseFromString(elementString, 'text/html'); return doc.body.firstChild; } window.util = {} window.util.observeClassChange = (el, callback) => { if (!el || !(el instanceof Element)) { throw new Error("observeClassChange requires a valid DOM element."); } const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "attributes" && mutation.attributeName === "class") { callback(el.classList); } } }); observer.observe(el, { attributes: true, attributeFilter: ["class"] }); return observer; // Optional: return it so you can disconnect later } /* PAGE SETUP */ Object.defineProperty(Array.prototype, 'last', { get() { return this[this.length - 1]; }, enumerable: false, }); /* QUILL */ window.quill = { rendering: [], render: (el) => { if(el instanceof Shadow) { let parent = quill.rendering[quill.rendering.length-1] if(!parent) { parent = document.body } parent.appendChild(el) } else { if(!el.render) {el.render = () => {}} let parent = quill.rendering[quill.rendering.length-1] if(!parent) throw new Error("Quill: no parent for element") parent.appendChild(el) } quill.rendering.push(el) el.render() quill.rendering.pop(el) }, rerender: (el) => { Array.from(el.attributes).forEach(attr => el.removeAttribute(attr.name)); el.innerHTML = "" el.removeAllListeners() quill.rendering.push(el) el.render() quill.rendering.pop() }, loadPage: () => { let URL = window.location.pathname if(!window.routes[URL]) { throw new Error("No URL for this route: ", URL) } let pageClass = window[routes[URL]] document.title = pageClass.title ?? document.title document.body.innerHTML = "" let page = new pageClass() quill.render(page) }, isStack: (el) => { return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack") }, } window.Shadow = class extends HTMLElement { constructor() { super() } } window.registerShadow = (el, tagname) => { if (typeof el.prototype.render !== 'function') { throw new Error("Element must have a render: " + el.prototype.constructor.name) } if(!tagname) { tagname = el.prototype.constructor.name.toLowerCase() + "-" } customElements.define(tagname, el) if(el.css) { css(el.css) } window[el.prototype.constructor.name] = function (...params) { let instance = new el(...params) quill.render(instance) return instance } } HTMLElement.prototype.rerender = function() { quill.rerender(this) } /* Styling */ window.vh = "vh" window.vw = "vw" window.px = "px" window.em = "em" window.rem = "rem" window.inches = "in" HTMLElement.prototype.addStyle = function(func) { return func(this) } window.css = function css(cssString) { let container = document.querySelector("style#pageStyle"); if(!container) { container = document.createElement('style'); container.id = "pageStyle"; document.head.appendChild(container); } let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); primarySelector = primarySelector.replace(/\*/g, "all"); primarySelector = primarySelector.replace(/#/g, "id-"); primarySelector = primarySelector.replace(/,/g, ""); let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) if(!stylesheet) { stylesheet = document.createElement('style'); stylesheet.id = primarySelector; stylesheet.appendChild(document.createTextNode(cssString)); container.appendChild(stylesheet); } else { stylesheet.innerText = cssString } } function extendHTMLElementWithStyleSetters() { let allStyleProps = Object.keys(document.createElement("div").style) allStyleProps.forEach(prop => { if(prop === "translate") return HTMLElement.prototype[prop] = function(value) { this.style[prop] = value; return this; }; }); } extendHTMLElementWithStyleSetters(); HTMLElement.prototype.padding = function(one, two, three = "px") { const setPadding = (side, val) => { const directionName = `padding${side.charAt(0).toUpperCase()}${side.slice(1)}`; this.style[directionName] = (typeof val === 'number') ? `${val}${three}` : val; }; if(one === "horizontal" || one === "vertical") { // is one a direction if (one === "horizontal") { setPadding("left", two); setPadding("right", two); } else if (one === "vertical") { setPadding("top", two); setPadding("bottom", two); } } else { // is two a value if(typeof one !== 'number' || isNaN(one)) { this.style.padding = one } else { this.style.padding = one + two } } return this; }; HTMLElement.prototype.paddingTop = function(value, unit = "px") { this.style.paddingTop = value + unit return this } HTMLElement.prototype.paddingLeft = function(value, unit = "px") { this.style.paddingLeft = value + unit return this } HTMLElement.prototype.paddingBottom = function(value, unit = "px") { this.style.paddingBottom = value + unit return this } HTMLElement.prototype.paddingRight = function(value, unit = "px") { this.style.paddingRight = value + unit return this } HTMLElement.prototype.margin = function(direction, value, unit = "px") { if (!value) { this.style.margin = direction; return this; } const setMargin = (side, val) => { const directionName = `margin${side.charAt(0).toUpperCase()}${side.slice(1)}`; this.style[directionName] = (typeof val === 'number') ? `${val}${unit}` : val; }; if (direction === "horizontal") { setMargin("left", value); setMargin("right", value); } else if (direction === "vertical") { setMargin("top", value); setMargin("bottom", value); } else { setMargin(direction, value); } return this; }; HTMLElement.prototype.marginTop = function(value, unit = "px") { this.style.marginTop = value + unit return this } HTMLElement.prototype.marginLeft = function(value, unit = "px") { this.style.marginLeft = value + unit return this } HTMLElement.prototype.marginBottom = function(value, unit = "px") { this.style.marginBottom = value + unit return this } HTMLElement.prototype.marginRight = function(value, unit = "px") { this.style.marginRight = value + unit return this } HTMLElement.prototype.width = function(value, unit = "px") { if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); this.style.width = value + unit return this } HTMLElement.prototype.minWidth = function(value, unit = "px") { if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); this.style.minWidth = value + unit return this } HTMLElement.prototype.height = function(value, unit = "px") { if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); this.style.height = value + unit return this } HTMLElement.prototype.minHeight = function(value, unit = "px") { if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); this.style.minHeight = value + unit return this } HTMLElement.prototype.fontSize = function(value, unit = "px") { switch(value) { case "6xl": value = "3.75"; unit = "rem" break; case "5xl": value = "3"; unit = "rem" break; case "4xl": value = "2.25"; unit = "rem" break; case "3xl": value = "1.875"; unit = "rem" break; case "2xl": value = "1.5"; unit = "rem" break; case "xl": value = "1.25"; unit = "rem" break; case "l": value = "1.125"; unit = "rem" break; case "s": value = "0.875"; unit = "rem" break; case "xs": value = "0.75"; unit = "rem" break; default: break; } this.style.fontSize = value + unit return this } function checkPositionType(el) { let computed = window.getComputedStyle(el).position if(!(computed === "absolute" || computed === "fixed")) { el.style.position = "absolute" } } HTMLElement.prototype.x = function(value, unit = "px") { if (typeof value !== 'number' || isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); checkPositionType(this) this.style.left = value + unit return this } HTMLElement.prototype.y = function(value, unit = "px") { if (typeof value !== 'number' || isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); checkPositionType(this) this.style.top = value + unit return this } HTMLElement.prototype.xRight = function(value, unit = "px") { if (typeof value !== 'number' || isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); checkPositionType(this) this.style.right = value + unit return this } HTMLElement.prototype.yBottom = function(value, unit = "px") { if (typeof value !== 'number' || isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); checkPositionType(this) this.style.bottom = value + unit return this } HTMLElement.prototype.borderRadius = function(value, unit = "px") { if (typeof value !== 'number' || isNaN(value)) throw new Error(`Invalid value: ${value}. Expected a number.`); this.style.borderRadius = value + unit return this } HTMLElement.prototype.positionType = function (value) { if(!(value === "absolute" || value === "relative" || value === "static" || value === "fixed" || value === "sticky")) { console.error("HTMLElement.overlflow: must have valid overflow value!") return; } this.style.position = value return this } /* Elements */ quill.setChildren = function(el, innerContent) { if(typeof innerContent === "string") { el.innerText = innerContent } else if(typeof innerContent === "function") { el.render = innerContent } else { throw new Error("Children of unknown type") } } window.a = function a( href, inner=href ) { if(!href) throw new Error("quill a: missing href argument. Function: a( href, inner=href )") let link = document.createElement("a") link.setAttribute('href', href); quill.setChildren(link, inner) quill.render(link) return link } window.img = function img(src, width="", height="") { let image = document.createElement("img") if(!src || !(typeof src==="string")) { throw new Error("img: missing first argument: src | String") } else { image.src = src } if(width && typeof width === "string") { image.style.width = width } else if(width) { image.style.width = width + "px" } if(height && typeof height === "string") { image.style.height = height } else if(height) { image.style.height = height + "px" } quill.render(image) return image } HTMLImageElement.prototype.backgroundColor = function(value) { if (this.src.endsWith('.svg') || this.src.startsWith('data:image/svg+xml')) { fetch(this.src).then(response => response.text()) .then(svgText => { const modifiedSvg = svgText.replace(/\bfill="[^"]*"/g, `fill="${value}"`); const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); this.src = URL.createObjectURL(blob); }).catch(error => { console.error('Error updating SVG fill:', error); }); } else { this.style.backgroundColor = value; } return this; // Always returns the element itself }; window.p = function p(innerText) { let el = document.createElement("p") if(typeof innerText === "function") { el.render = innerText } else { el.innerText = innerText } el.style.margin = "0"; quill.render(el) return el } window.h1 = function h1(innerText) { let el = document.createElement("h1") el.innerText = innerText quill.render(el) return el } window.h2 = function h2(innerText) { let el = document.createElement("h2") el.innerText = innerText quill.render(el) return el } window.h3 = function h3(innerText) { let el = document.createElement("h3") el.innerText = innerText quill.render(el) return el } window.div = function (innerText) { let el = document.createElement("div") el.innerText = innerText ?? "" quill.render(el) return el } window.span = function (innerText) { let el = document.createElement("span") el.innerText = innerText quill.render(el) return el } window.button = function (children) { let el = document.createElement("button") quill.setChildren(el, children) quill.render(el) return el } window.form = function(cb) { let el = document.createElement("form") el.render = cb quill.render(el) return el } window.input = function(placeholder, width, height) { let el = document.createElement("input") el.placeholder = placeholder el.style.width = width el.style.height = height quill.render(el) return el } window.textarea = function(placeholder) { let el = document.createElement("textarea") el.placeholder = placeholder quill.render(el) return el } /* STACKS */ window.VStack = function (cb = () => {}) { let styles = ` display: flex; flex-direction: column; ` let nowRendering = quill.rendering[quill.rendering.length-1] if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { nowRendering.style.cssText += styles nowRendering.classList.add("VStack") cb() return nowRendering } let div = document.createElement("div") div.classList.add("VStack") div.style.cssText += styles div.render = cb quill.render(div) return div } window.HStack = function (cb = () => {}) { let styles = ` display: flex; flex-direction: row; `; let nowRendering = quill.rendering[quill.rendering.length - 1]; if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { nowRendering.style.cssText += styles; nowRendering.classList.add("HStack") cb(); return nowRendering; } let div = document.createElement("div"); div.classList.add("HStack"); div.style.cssText += styles; div.render = cb; quill.render(div); return div; }; window.ZStack = function (cb = () => {}) { let nowRendering = quill.rendering[quill.rendering.length - 1]; if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { nowRendering.classList.add("ZStack") cb(); return nowRendering; } let div = document.createElement("div"); div.classList.add("ZStack"); div.render = cb; quill.render(div); return div; }; /* EVENTS */ HTMLElement.prototype.onAppear = function(func) { func(this); return this; }; HTMLElement.prototype.onClick = function(func) { this._storeListener("click", func); return this; }; HTMLElement.prototype.onMouseDown = function(func) { this._storeListener("mousedown", func); return this; }; HTMLElement.prototype.onMouseUp = function(func) { this._storeListener("mouseup", func); return this; }; HTMLElement.prototype.onRightClick = function(func) { this._storeListener("contextmenu", func); return this; }; HTMLElement.prototype.onHover = function(cb) { const onEnter = () => cb.call(this, true); const onLeave = () => cb.call(this, false); this._storeListener("mouseover", onEnter); this._storeListener("mouseleave", onLeave); return this; }; HTMLElement.prototype.onFocus = function(cb) { if (!this.matches('input, textarea, select, button')) { throw new Error("Can't put focus event on non-form element!"); } this._storeListener("focus", cb); return this; }; HTMLElement.prototype.onBlur = function(cb) { if (!this.matches('input, textarea, select, button')) { throw new Error("Can't put blur event on non-form element!"); } this._storeListener("blur", cb); return this; }; HTMLElement.prototype.onKeyDown = function(cb) { this._storeListener("keydown", cb); return this; }; HTMLElement.prototype.onEvent = function(name, cb) { window._storeListener(window, name, cb); return this; }; HTMLElement.prototype._storeListener = function(type, handler, options) { window._storeListener(this, type, handler, options) } window.__listeners = [] function _storeListener(target, type, handler, options) { if (!target.__listeners) target.__listeners = []; const optionsString = JSON.stringify(options); const index = target.__listeners.findIndex(listener => listener.type === type && listener.handler.toString() === handler.toString() && JSON.stringify(listener.options) === optionsString ); if (index === -1) { target.addEventListener(type, handler, options); target.__listeners.push({ type, handler, options }); } else { const old = target.__listeners[index]; target.removeEventListener(old.type, old.handler, old.options); // Replace with the new one target.addEventListener(type, handler, options); target.__listeners[index] = { type, handler, options }; } } HTMLElement.prototype.removeAllListeners = function() { if (!this.__listeners) return; for (const { type, handler, options } of this.__listeners) { this.removeEventListener(type, handler, options); } this.__listeners = []; return this; }; /* ATTRIBUTES */ HTMLElement.prototype.attr = function(attributes) { if ( typeof attributes !== "object" || attributes === null || Array.isArray(attributes) ) { throw new TypeError("attr() expects an object with key-value pairs"); } for (const [key, value] of Object.entries(attributes)) { this.setAttribute(key, value); } console.log(this) return this; };