Last active
May 13, 2024 18:37
-
-
Save doeixd/567d1865f0419719e0d0a94a81de6216 to your computer and use it in GitHub Desktop.
Small Framework
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var states = new WeakMap() | |
var remove = new WeakMap() | |
var setups = new WeakSet() | |
window.states = states | |
window.remove = remove | |
window.setups = setups | |
function createObserver(fn, options) { | |
const observer = new MutationObserver(fn); | |
return function (element) { | |
if (typeof element == 'string') element = document.querySelector(element); | |
try { | |
observer.observe(element, options); | |
} catch (e) {} | |
}; | |
} | |
function ensureRunAfterDOM(fn) { | |
let handleDOMLoaded = Fn(fn); | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', handleDOMLoaded); | |
} else { | |
handleDOMLoaded(); | |
} | |
} | |
function watch(selector, setup_fn, parent = document, wrapper = ((fn) => (args) => fn(args))) { | |
const setup = (args) => { | |
const cleanup = () => { | |
remove.get(args.el).forEach((fn) => fn(args)); | |
setups.delete(args.el); | |
remove.delete(args.el); | |
states.delete(args.el); | |
}; | |
args.style = applyInlineStylesToElement(args.el) | |
args.cleanup = cleanup; | |
if (setups.has(args.el)) return; | |
wrapper(setup_fn)(args); | |
setups.add(args.el); | |
}; | |
const eventNames = [ | |
...new Set( | |
[ | |
...Object.getOwnPropertyNames(document), | |
...Object.getOwnPropertyNames( | |
Object.getPrototypeOf(Object.getPrototypeOf(document)) | |
), | |
...Object.getOwnPropertyNames(Object.getPrototypeOf(window)), | |
].filter( | |
(k) => | |
k.startsWith('on') && | |
(document[k] == null || typeof document[k] == 'function') | |
) | |
), | |
]; | |
const eventDoesExists = (name = '') => { | |
return eventNames.includes( | |
'on' + name.toLowerCase().trim().replace(/^on/i, '') | |
); | |
}; | |
let on = | |
(el) => (eventName, handler, attrHandlerOrOptions, attrHandlerOptions) => { | |
eventName = eventName.trim().toLowerCase(); | |
if (eventDoesExists(eventName)) { | |
el.addEventListener( | |
eventName.trim().toLowerCase(), | |
handler, | |
attrHandlerOrOptions | |
); | |
return; | |
} | |
if (['attr', 'attribute'].includes(eventName)) { | |
let attr = handler; | |
createObserver( | |
(mutations) => { | |
for (let mutation of mutations) { | |
let shouldRun = false; | |
if (typeof attr == 'string') { | |
if (mutation.attributeName == attr) shouldRun = true; | |
} | |
if (attr instanceof RegExp) { | |
if (attr.test(mutation.attributeName)) shouldRun = true; | |
} | |
if (shouldRun) | |
attrHandlerOrOptions({ el, selector, record: mutation }); | |
} | |
}, | |
{ | |
attributes: true, | |
attributeOldValue: true, | |
} | |
)(el); | |
return; | |
} | |
if (['text', 'textChange', 'textChanged'].includes(eventName)) { | |
Array.from(el.childNodes) | |
.filter((e) => e.nodeType === Node.TEXT_NODE && e.textContent.trim()) | |
.forEach((_el) => { | |
createObserver( | |
(mutations) => { | |
for (let mutation of mutations) { | |
handler({ el: _el, selector, record: mutation }); | |
} | |
}, | |
{ | |
subtree: true, | |
characterData: true, | |
characterDataOldValue: true, | |
} | |
)(_el); | |
}); | |
return; | |
} | |
if (['unmount', 'remove', 'dispose', 'cleanup'].includes(eventName)) { | |
if (!remove.get(el)) remove.set(el, []); | |
remove.get(el).push(handler); | |
return | |
} | |
el?.addEventListener(eventName, handler, attrHandlerOrOptions) | |
}; | |
document.querySelectorAll(selector).forEach((el, idx, arr) => { | |
let state = states.has(el) ? states.get(el) : {}; | |
states.set(el, state); | |
setup({ on: on(el), state, el, idx, arr, record: {} }); | |
}); | |
createObserver( | |
(mutations) => { | |
for (let mutation of mutations) { | |
if (!mutation.target instanceof Element) continue; | |
if (mutation.target?.matches(selector)) { | |
let el = mutation.target; | |
if (states.has(el)) continue; | |
let state = states.has(el) ? states.get(el) : {}; | |
states.set(el, state); | |
setup({ on: on(el), state, record: mutation, arr: [el], idx: 0, el }); | |
} | |
if (mutation?.addedNodes?.length) { | |
let idx = -1; | |
for (let el of mutation.addedNodes) { | |
idx += 1; | |
if (!(mutation.target instanceof Element)) continue; | |
if (el?.matches?.(selector)) { | |
let state = states.has(el) ? states.get(el) : {}; | |
states.set(el, state); | |
setup({ | |
on: on(el), | |
state, | |
record: mutation, | |
arr: mutation?.addedNodes ?? [], | |
idx, | |
el, | |
}); | |
} | |
el?.querySelectorAll?.(selector).forEach((el, idx, arr) => { | |
let state = states.has(el) ? states.get(el) : {}; | |
states.set(el, state); | |
setup({ on: on(el), state, record: mutation, arr, idx, el }); | |
}); | |
} | |
} | |
if (mutation?.removedNodes?.length) { | |
for (let el of mutation.removedNodes) { | |
if (!mutation.target instanceof Element) continue; | |
el?.querySelectorAll?.(selector).forEach((el, idx, arr) => { | |
let state = states.has(el) ? states.get(el) : {}; | |
states.set(el, state); | |
setup({ on: on(el), state, record: mutation, arr, idx, el }); | |
if (remove.has(el)) { | |
remove | |
.get(el) | |
.forEach((fn) => | |
fn({ on: on(el), state, record: mutation, arr, idx, el }) | |
); | |
} | |
}); | |
} | |
} | |
} | |
}, | |
{ | |
subtree: true, | |
childList: true, | |
attributes: true, | |
} | |
)(document); | |
} | |
function JS(obj) { | |
return JSON.stringify(obj, null, 2); | |
} | |
function clone(obj) { | |
return JSON.parse(JSON.stringify(obj)); | |
} | |
function applyInlineStylesToElement(element) { | |
return function (styles) { | |
const oldStyles = JSON.parse(JSON.stringify(element.style)); | |
const applyStyles = (obj) => { | |
Object.entries(obj).forEach(([key, value]) => { | |
element.style[key] = value; | |
}); | |
}; | |
return { | |
apply: (additionalStyles) => { | |
applyStyles({ ...styles, ...additionalStyles }); | |
}, | |
revert: () => { | |
applyStyles(oldStyles); | |
}, | |
}; | |
}; | |
} | |
function html(html) { | |
var template = document.createElement('template'); | |
html = html.trim(); | |
template.innerHTML = html; | |
return template.content.firstChild; | |
} | |
const find = (...args) => document.querySelector(...args); | |
const findAll = (...args) => Array.from(document.querySelectorAll(...args)); | |
function convertColor(color, toSpace) { | |
let div = document.createElement('div'); | |
div.style.color = `color-mix(in ${toSpace}, ${color} 100%, transparent)`; | |
div.style.display = 'none'; | |
document.body.appendChild(div); | |
let result = window.getComputedStyle(div).color; | |
div.remove(); | |
return result; | |
} | |
function setQueryParam(key, value, type = 'soft') { | |
const url = new URL(window.location); | |
url.searchParams.set(key, value); | |
if (type == 'hard') window.location.search = url.href; | |
if (type == 'soft') history.pushState(urlParams, '', url.href); | |
} | |
function getQueryParam(key) { | |
const urlParams = new URLSearchParams(window.location.search); | |
urlParams.get(key); | |
window.location.search = urlParams; | |
} | |
function getAllQueryParam(key) { | |
const urlParams = new URLSearchParams(window.location.search); | |
urlParams.getAll(key); | |
window.location.search = urlParams; | |
} | |
const Params = { | |
get: getQueryParam, | |
set: setQueryParam, | |
getAll: getAllQueryParam, | |
} | |
window.Params = Params | |
function match(regex, value) { | |
return String(value).match(regex)?.[0]; | |
} | |
function wait(ms, fn) { | |
return new Promise((resolve) => { | |
setTimeout(() => resolve(fn().then((l) => l)), ms); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
watch.d.ts