From b2c6b8d398fc07829887c6af93cfe85aa7de5f65 Mon Sep 17 00:00:00 2001 From: natxocc Date: Wed, 8 Apr 2026 01:30:23 +0200 Subject: [PATCH] Add on-off in router --- sigwork_original.js | 219 +++++++++++++++++++------------------------- 1 file changed, 93 insertions(+), 126 deletions(-) diff --git a/sigwork_original.js b/sigwork_original.js index a49e29b..22c0065 100644 --- a/sigwork_original.js +++ b/sigwork_original.js @@ -1,15 +1,15 @@ const isFn = (v) => typeof v === 'function'; const isNode = (v) => v instanceof Node; -let isScheduled = false; -const queue = new Set(); +let isScheduled = false, activeEffect = null, context = null; +const queue = new Set(), reactiveCache = new WeakMap(); + const tick = () => { queue.forEach(fn => fn()); queue.clear(); isScheduled = false; } -let activeEffect = null; export const effect = (fn, is_scope = false) => { let cleanup = null; const run = () => { @@ -22,10 +22,7 @@ export const effect = (fn, is_scope = false) => { run.e.forEach(subs => subs.delete(run)); run.e.clear(); if (isFn(cleanup)) cleanup(); - if (run.c) { - run.c.forEach(s => s()); - run.c.length = 0; - } + if (run.c) { run.c.forEach(s => s()); run.c.length = 0; } } run.e = new Set(); if (is_scope) run.c = []; @@ -34,8 +31,6 @@ export const effect = (fn, is_scope = false) => { return stop; } -export const scope = f => effect(f, true); - const track = (subs) => { if (activeEffect && !activeEffect.c) { subs.add(activeEffect); @@ -48,10 +43,10 @@ export const signal = (value) => { return { _isSig: true, get value() { track(subs); return value; }, - set value(newValue) { - if (newValue === value) return; - value = newValue; - subs.forEach(fn => queue.add(fn)); + set value(v) { + if (v === value) return; + value = v; + subs.forEach(f => queue.add(f)); if (!isScheduled) { isScheduled = true; queueMicrotask(tick); } } } @@ -60,9 +55,9 @@ export const signal = (value) => { export const untrack = (fn) => { const prev = activeEffect; activeEffect = null; - const result = fn(); + const res = fn(); activeEffect = prev; - return result; + return res; } export const computed = (fn) => { @@ -71,21 +66,20 @@ export const computed = (fn) => { return { get value() { return sig.value; } }; } -const reactiveCache = new WeakMap(); export const reactive = (obj) => { if (reactiveCache.has(obj)) return reactiveCache.get(obj); const subs = {}; const proxy = new Proxy(obj, { - get(t, key) { - track(subs[key] ??= new Set()); - const val = t[key]; - return (val && typeof val === 'object') ? reactive(val) : val; + get(t, k) { + track(subs[k] ??= new Set()); + const v = t[k]; + return (v && typeof v === 'object') ? reactive(v) : v; }, - set(t, key, val) { - if (t[key] === val) return true; - t[key] = val; - if (subs[key]) { - subs[key].forEach(fn => queue.add(fn)); + set(t, k, v) { + if (t[k] === v) return true; + t[k] = v; + if (subs[k]) { + subs[k].forEach(f => queue.add(f)); if (!isScheduled) { isScheduled = true; queueMicrotask(tick); } } return true; @@ -99,51 +93,42 @@ export const persist = (key, target) => { const saved = localStorage.getItem(key); if (saved !== null) { const data = JSON.parse(saved); - if (target._isSig) target.value = data; - else Object.assign(target, data); + target._isSig ? (target.value = data) : Object.assign(target, data); } - effect(() => { - const val = target._isSig ? target.value : target; - localStorage.setItem(key, JSON.stringify(val)); - }); + effect(() => localStorage.setItem(key, JSON.stringify(target._isSig ? target.value : target))); return target; }; export const storage = (key, val) => persist(key, signal(val)); export const watch = (source, cb) => { - let first = true, oldValue; + let first = true, old; return effect(() => { - const newValue = isFn(source) ? source() : source.value; - if (!first) untrack(() => cb(newValue, oldValue)); - else first = false; - oldValue = newValue; + const val = isFn(source) ? source() : source.value; + if (!first) untrack(() => cb(val, old)); + first = false; old = val; }); } -let context = null; -export const onMount = (fn) => context?.m.push(fn); -export const onUnmount = (fn) => context?.u.push(fn); -export const provide = (key, value) => context && (context.p[key] = value); -export const inject = (key, dft) => context && (key in context.p ? context.p[key] : dft); +export const onMount = (f) => context?.m.push(f); +export const onUnmount = (f) => context?.u.push(f); +export const provide = (k, v) => context && (context.p[k] = v); +export const inject = (k, d) => context && (k in context.p ? context.p[k] : d); -const remove = async (node) => { - if (Array.isArray(node)) return Promise.all(node.map(remove)); - if (node.$off) await node.$off(node); - else if (node.$l) await new Promise(res => node.$l(res)); - node.$s?.(); - if (node.$c) node.$c.u.forEach(f => f()); - node.remove(); +const remove = async (n) => { + if (Array.isArray(n)) return Promise.all(n.map(remove)); + if (n.$off) await n.$off(n); + else if (n.$l) await new Promise(r => n.$l(r)); + n.$s?.(); + if (n.$c) n.$c.u.forEach(f => f()); + n.remove(); } -const render = (fn, ...data) => { - let node; - const stop = effect(() => { - node = fn(...data); - if (isFn(node)) node = node(); - }, true); - if (node) node.$s = stop; - return node; +const render = (fn, ...d) => { + let n; + const s = effect(() => { n = fn(...d); if (isFn(n)) n = n(); }, true); + if (n) n.$s = s; + return n; } export const h = (tag, props = {}, ...children) => { @@ -153,75 +138,74 @@ export const h = (tag, props = {}, ...children) => { context = { m: [], u: [], p: { ...(prev?.p || {}) } }; const ctx = context; let el; - const stop = effect(() => { - el = tag(props, { children, emit: (evt, ...args) => props[`on${evt[0].toUpperCase()}${evt.slice(1)}`]?.(...args) }); + const s = effect(() => { + el = tag(props, { children, emit: (e, ...a) => props[`on${e[0].toUpperCase()}${e.slice(1)}`]?.(...a) }); return () => ctx.u.forEach(f => f()); }, true); const out = isNode(el) ? el : document.createTextNode(String(el)); - out.$c = ctx; - out.$s = stop; + out.$c = ctx; out.$s = s; if (props.on) out.$on = props.on; if (props.off) out.$off = props.off; context = prev; return out; } if (!tag) return children; - const isSvg = tag === 'svg' || tag === 'path' || tag === 'circle'; + const isSvg = /^(svg|path|circle|rect)$/.test(tag); const el = isSvg ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag); - for (const key in props) { - const val = props[key]; - if (key.startsWith('on') && key !== 'on' && key !== 'off') el.addEventListener(key.slice(2).toLowerCase(), val); - else if (key === "ref") isFn(val) ? val(el) : val.value = el; - else if (key === "on") el.$on = val; - else if (key === "off") el.$off = val; - else if (isFn(val)) effect(() => el[key] = val()); - else el[key] = val; + for (const k in props) { + const v = props[k]; + if (k.startsWith('on') && k !== 'on' && k !== 'off') el.addEventListener(k.slice(2).toLowerCase(), v); + else if (k === "ref") isFn(v) ? v(el) : v.value = el; + else if (k === "on") el.$on = v; + else if (k === "off") el.$off = v; + else if (isFn(v)) effect(() => el[k] = v()); + else el[k] = v; } - children.forEach(child => append(el, child)); + children.forEach(c => append(el, c)); return el; } -const append = (parent, child) => { - if (child == null) return; - if (isFn(child)) { +const append = (p, c) => { + if (c == null) return; + if (isFn(c)) { const anchor = document.createTextNode(''); - parent.appendChild(anchor); + p.appendChild(anchor); let nodes = []; effect(async () => { - const raw = [child()].flat(Infinity).filter(n => n != null); - const newNodes = raw.map(n => isNode(n) ? n : document.createTextNode(String(n))); - for (const n of nodes) { if (!newNodes.includes(n)) await remove(n); } - newNodes.forEach((n, i) => { + const raw = [c()].flat(Infinity).filter(n => n != null); + const next = raw.map(n => isNode(n) ? n : document.createTextNode(String(n))); + for (const n of nodes) { if (!next.includes(n)) await remove(n); } + next.forEach((n, i) => { if (!nodes.includes(n)) { - parent.insertBefore(n, newNodes[i+1] || anchor); + p.insertBefore(n, next[i+1] || anchor); if (n.$on) n.$on(n); if (n.$c) n.$c.m.forEach(f => f()); } }); - nodes = newNodes; + nodes = next; }, true); } else { - const n = isNode(child) ? child : document.createTextNode(String(child)); - parent.appendChild(n); + const n = isNode(c) ? c : document.createTextNode(String(c)); + p.appendChild(n); if (n.$on) n.$on(n); } } -export const If = (cond, renderFn, fallback = null, transitions = {}) => { +export const If = (cond, t, f = null, trans = {}) => { let cached, current; return () => { const show = !!cond(); if (show !== current) { - const update = async () => { + const up = async () => { if (cached) await remove(cached); - cached = show ? render(renderFn) : (isFn(fallback) ? render(fallback) : fallback); + cached = show ? render(t) : (isFn(f) ? render(f) : f); if (isNode(cached)) { - if (transitions.on) cached.$on = transitions.on; - if (transitions.off) cached.$off = transitions.off; + if (trans.on) cached.$on = trans.on; + if (trans.off) cached.$off = trans.off; } current = show; }; - update(); + up(); } return cached; } @@ -234,54 +218,49 @@ export const For = (list, key, renderFn) => { const items = isFn(list) ? list() : (list.value || list); const res = items.map((item, i) => { const id = isFn(key) ? key(item, i) : (key ? item[id] : item); - let node = cache.get(id); - if (!node) node = render(renderFn, item, i); - next.set(id, node); - return node; + let n = cache.get(id); + if (!n) n = render(renderFn, item, i); + next.set(id, n); + return n; }); - cache.forEach(async (node, id) => { if (!next.has(id)) await remove(node); }); + cache.forEach(async (n, id) => { if (!next.has(id)) await remove(n); }); cache = next; return res; } } -export const Router = (routes) => { +export const Router = (routes, trans = {}) => { const path = signal(window.location.hash.slice(1) || '/'); window.onhashchange = () => path.value = window.location.hash.slice(1) || '/'; return h('div', { class: 'router-view' }, () => { - const route = routes.find(r => r.path === path.value) || routes.find(r => r.path === '*'); - return route ? h(route.component) : null; + const p = path.value; + const r = routes.find(x => x.path === p) || routes.find(x => x.path === '*'); + return If(() => !!r, () => h(r.component, { path: p }), null, trans); }); }; -export const Component = ({ is, ...props }, { children }) => () => h(isFn(is) ? is() : is, props, children); - export const Transition = ({ enter: e, idle, leave: l }, { children: [c] }) => { const decorate = (el) => { if (!isNode(el)) return el; - const addClass = c => c && el.classList.add(...c.split(' ')); - const removeClass = c => c && el.classList.remove(...c.split(' ')); + const add = (cl) => cl && el.classList.add(...cl.split(' ')); + const rem = (cl) => cl && el.classList.remove(...cl.split(' ')); el.$on = () => { if (!e) return; requestAnimationFrame(() => { - addClass(e[1]); + add(e[1]); requestAnimationFrame(() => { - addClass(e[0]); removeClass(e[1]); addClass(e[2]); - el.addEventListener('transitionend', () => { - removeClass(e[2]); removeClass(e[0]); addClass(idle); - }, { once: true }); + add(e[0]); rem(e[1]); add(e[2]); + el.addEventListener('transitionend', () => { rem(e[2]); rem(e[0]); add(idle); }, { once: true }); }); }); }; - el.$off = (node) => { - if (!l) return node.remove(); + el.$off = () => { + if (!l) return el.remove(); return new Promise(res => { - removeClass(idle); addClass(l[1]); + rem(idle); add(l[1]); requestAnimationFrame(() => { - addClass(l[0]); removeClass(l[1]); addClass(l[2]); - el.addEventListener('transitionend', () => { - removeClass(l[2]); removeClass(l[0]); res(); - }, { once: true }); + add(l[0]); rem(l[1]); add(l[2]); + el.addEventListener('transitionend', () => { rem(l[2]); rem(l[0]); res(); }, { once: true }); }); }); }; @@ -292,24 +271,12 @@ export const Transition = ({ enter: e, idle, leave: l }, { children: [c] }) => { export const mount = (root, target, props = {}) => { const container = typeof target === 'string' ? document.querySelector(target) : target; - - if (container.firstElementChild) { - remove(container.firstElementChild); - } - + if (container.firstElementChild) remove(container.firstElementChild); const el = h(root, props); container.appendChild(el); - if (el.$on) el.$on(el); if (el.$c) el.$c.m.forEach(f => f()); - return () => remove(el); }; -export default (target, root, props) => { - const el = h(root, props); - target.appendChild(el); - if (el.$on) el.$on(el); - if (el.$c) el.$c.m.forEach(f => f()); - return () => remove(el); -} \ No newline at end of file +export default { signal, effect, reactive, computed, watch, persist, storage, h, mount, If, For, Router, Transition, onMount, onUnmount, provide, inject }; \ No newline at end of file