diff options
| author | ivar <i@oiee.no> | 2025-10-26 11:33:38 +0100 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2025-10-26 11:33:38 +0100 |
| commit | 2e8ad7dc6d49361c6ee00dc628e119c0c06e2779 (patch) | |
| tree | becc4cde99eff6ed1d168f9e9c454c0f2bbbee9d /VegaData/wwwroot/framework.js | |
| parent | 8de1187b627625b94ef8088de3e9255ccd17baf4 (diff) | |
| download | vegadata-2e8ad7dc6d49361c6ee00dc628e119c0c06e2779.tar.xz vegadata-2e8ad7dc6d49361c6ee00dc628e119c0c06e2779.zip | |
.
Diffstat (limited to 'VegaData/wwwroot/framework.js')
| -rw-r--r-- | VegaData/wwwroot/framework.js | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/VegaData/wwwroot/framework.js b/VegaData/wwwroot/framework.js new file mode 100644 index 0000000..d1e6cc6 --- /dev/null +++ b/VegaData/wwwroot/framework.js @@ -0,0 +1,166 @@ +const targetMap = new WeakMap(); +let activeEffect = null; + +const root = document.getElementById("root"); + +function createElement(tagName, props = {}, children = []) { + const el = document.createElement(tagName); + + for (const [key, value] of Object.entries(props || {})) { + if (key.startsWith("on") && typeof value === "function") { + el.addEventListener(key.substring(2).toLowerCase(), value); + } else if (key === "style" && typeof value === "object") { + Object.assign(el.style, value); + } else { + el.setAttribute(key, value); + } + } + + const appendChild = (child) => { + if (Array.isArray(child)) { + child.forEach(appendChild); + } else if (typeof child === "function") { + const placeholder = document.createTextNode(""); + el.appendChild(placeholder); + e(() => { + placeholder.textContent = child() ?? ""; + }); + } else if (child instanceof Node) { + el.appendChild(child); + } else if (child != null) { + el.appendChild(document.createTextNode(String(child))); + } + }; + + children.forEach(appendChild); + return el; +} + +function track(target, key) { + if (!activeEffect) { + return; + } + let depsMap = targetMap.get(target); + if (!depsMap) { + depsMap = new Map(); + targetMap.set(target, depsMap); + } + + let dep = depsMap.get(key); + if (!dep) { + dep = new Set(); + depsMap.set(key, dep); + } + + dep.add(activeEffect); +} + +function trigger(target, key) { + const depsMap = targetMap.get(target); + if (!depsMap) { + return; + } + + const dep = depsMap.get(key); + if (dep) { + dep.forEach((effect) => effect()); + } +} + +//** +// Create a reactive value, the value is at .value. +// To use this in element props you need to supply the .value read as a function. +// */ +export function r(target) { + target = { value: target }; + return new Proxy(target, { + get(obj, key, receiver) { + track(obj, key); + return Reflect.get(obj, key, receiver); + }, + set(obj, key, value, receiver) { + const result = Reflect.set(obj, key, value, receiver); + trigger(obj, key); + return result; + }, + }); +} + +//** +// Run code when value changes +// */ +export function e(fn) { + let active = true; + + const runner = () => { + if (active) { + activeEffect = runner; + fn(); + activeEffect = null; + } + }; + + runner(); + + runner.stop = () => { + active = false; + }; + + return runner; +} + +export function gqp(key) { + return new URLSearchParams(location.search).get(key) +} + +export function sqp(query) { + if ('URLSearchParams' in window) { + const url = new URL(window.location) + for (const key of Object.keys(query)) { + const value = encodeURIComponent(query[key]) + if (!value || value === "") url.searchParams.delete(key) + else url.searchParams.set(key, value) + } + history.pushState(null, '', url); + } +} + +//** +// Combine elements +// */ +export function c(a, b) { + const normalize = (x) => (x == null ? [] : Array.isArray(x) ? x : [x]); + + return [...normalize(a), ...normalize(b)]; +} + +//** +// Mount element to a target (target is #root by default) +// */ +export async function m(component, target = root) { + if (typeof component === "function") { + component = await component(); + } + if (Array.isArray(component)) { + target.append(...component); + } else { + target.appendChild(component); + } +} + +export function css(styleObject) { + return Object.entries(styleObject).map(([key, value]) => { + const kebabKey = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + return `${kebabKey}: ${value}`; + }).join("; "); +} + +//** +// Create element +// */ +export function t(name, props, ...children) { + if (typeof name === "function") { + return name({ ...props, children }); + } + return createElement(name, props, children); +} |
