Fase2
This commit is contained in:
509
sigpro2.js
509
sigpro2.js
@@ -1,324 +1,247 @@
|
|||||||
/**
|
/**
|
||||||
* SigPro - Fine-Grained Reactive UI Framework
|
* SigPro v3.3 - Stable Integrated Engine
|
||||||
* Zero-Dependency Single-File Architecture
|
|
||||||
*/
|
*/
|
||||||
|
const SigPro = (() => {
|
||||||
|
const doc = typeof document !== "undefined" ? document : null;
|
||||||
|
const isArr = Array.isArray, assign = Object.assign, isFunc = (f) => typeof f === "function", isObj = (o) => typeof o === "object" && o !== null;
|
||||||
|
const ensureNode = (n) => n?._isRuntime ? n.container : (n instanceof Node ? n : doc.createTextNode(String(n ?? "")));
|
||||||
|
|
||||||
// --- 1. CORE HELPERS (DRY) ---
|
// --- INTERNAL STATE & CLEANUP ---
|
||||||
const doc = typeof document !== "undefined" ? document : null;
|
let activeEffect = null, currentOwner = null, isFlushing = false;
|
||||||
const isArr = Array.isArray;
|
const effectQueue = new Set(), MOUNTED_NODES = new WeakMap();
|
||||||
const assign = Object.assign;
|
|
||||||
const isFunc = (f) => typeof f === "function";
|
|
||||||
const isObj = (o) => typeof o === "object" && o !== null;
|
|
||||||
|
|
||||||
const createEl = (t) => doc?.createElement(t);
|
const runCleanups = (s) => { s?.forEach(f => f()); s?.clear(); };
|
||||||
const createText = (t) => doc?.createTextNode(String(t ?? ""));
|
const clearDeps = (e) => { e._deps.forEach(d => d.delete(e)); e._deps.clear(); };
|
||||||
const ensureNode = (n) => n?._isRuntime ? n.container : (n instanceof Node ? n : createText(n));
|
const onUnmount = (fn) => currentOwner && currentOwner.cleanups.add(fn);
|
||||||
|
|
||||||
// Funciones DRY para reducir repetición en memoria y reactividad
|
const cleanupNode = (node) => {
|
||||||
const runCleanups = (set) => { set?.forEach(fn => fn()); set?.clear(); };
|
if (node._cleanups) runCleanups(node._cleanups);
|
||||||
const clearDeps = (effect) => { effect._deps.forEach(d => d.delete(effect)); effect._deps.clear(); };
|
node.childNodes?.forEach(cleanupNode);
|
||||||
|
};
|
||||||
|
|
||||||
// --- 2. INTERNAL STATE ---
|
// --- SCHEDULER ---
|
||||||
let activeEffect = null;
|
const runWithContext = (e, cb) => {
|
||||||
let currentOwner = null;
|
const p = activeEffect; activeEffect = e;
|
||||||
const effectQueue = new Set();
|
try { return cb(); } finally { activeEffect = p; }
|
||||||
let isFlushing = false;
|
};
|
||||||
const MOUNTED_NODES = new WeakMap();
|
|
||||||
|
|
||||||
// --- 3. SCHEDULER & MEMORY ---
|
const flush = () => {
|
||||||
const runWithContext = (effect, callback) => {
|
if (isFlushing) return; isFlushing = true;
|
||||||
const prev = activeEffect;
|
const sorted = Array.from(effectQueue).sort((a, b) => (a.depth || 0) - (b.depth || 0));
|
||||||
activeEffect = effect;
|
effectQueue.clear();
|
||||||
try { return callback(); }
|
sorted.forEach(e => !e._deleted && e());
|
||||||
finally { activeEffect = prev; }
|
isFlushing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanupNode = (node) => {
|
const trackUpdate = (subs, trigger = false) => {
|
||||||
runCleanups(node._cleanups);
|
if (!trigger && activeEffect && !activeEffect._deleted) {
|
||||||
node.childNodes?.forEach(cleanupNode);
|
subs.add(activeEffect); activeEffect._deps.add(subs);
|
||||||
};
|
} else if (trigger) {
|
||||||
|
subs.forEach(e => {
|
||||||
|
if (e === activeEffect || e._deleted) return;
|
||||||
|
if (e._isComputed) { e.markDirty(); if (e._subs) trackUpdate(e._subs, true); }
|
||||||
|
else effectQueue.add(e);
|
||||||
|
});
|
||||||
|
if (!isFlushing) queueMicrotask(flush);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const flushEffects = () => {
|
// --- CORE API ---
|
||||||
if (isFlushing) return;
|
const untrack = (fn) => {
|
||||||
isFlushing = true;
|
const p = activeEffect; activeEffect = null;
|
||||||
const sorted = Array.from(effectQueue).sort((a, b) => (a.depth || 0) - (b.depth || 0));
|
try { return fn(); } finally { activeEffect = p; }
|
||||||
effectQueue.clear();
|
};
|
||||||
sorted.forEach(effect => !effect._deleted && effect());
|
|
||||||
isFlushing = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const trackUpdate = (subscribers, isTrigger = false) => {
|
const $ = (val, key = null) => {
|
||||||
if (!isTrigger && activeEffect && !activeEffect._deleted) {
|
const subs = new Set();
|
||||||
subscribers.add(activeEffect);
|
if (isFunc(val)) {
|
||||||
activeEffect._deps.add(subscribers);
|
let cache, dirty = true;
|
||||||
} else if (isTrigger) {
|
const e = () => {
|
||||||
subscribers.forEach((eff) => {
|
if (e._deleted) return;
|
||||||
if (eff === activeEffect || eff._deleted) return;
|
clearDeps(e);
|
||||||
if (eff._isComputed) { eff.markDirty(); if (eff._subs) trackUpdate(eff._subs, true); }
|
runWithContext(e, () => {
|
||||||
else effectQueue.add(eff);
|
const next = val();
|
||||||
});
|
if (!Object.is(cache, next) || dirty) { cache = next; dirty = false; trackUpdate(subs, true); }
|
||||||
if (!isFlushing) queueMicrotask(flushEffects);
|
});
|
||||||
}
|
};
|
||||||
};
|
assign(e, { _deps: new Set(), _isComputed: true, _subs: subs, _deleted: false, markDirty: () => (dirty = true),
|
||||||
|
stop: () => { e._deleted = true; clearDeps(e); subs.clear(); } });
|
||||||
// --- 4. REACTIVITY ---
|
onUnmount(e.stop);
|
||||||
const $ = (initialValue, storageKey = null) => {
|
return () => { if (dirty) e(); trackUpdate(subs); return cache; };
|
||||||
const subscribers = new Set();
|
}
|
||||||
|
if (key) try { val = JSON.parse(localStorage.getItem(key)) ?? val; } catch(e){}
|
||||||
if (isFunc(initialValue)) { // Computed
|
return (...args) => {
|
||||||
let cachedValue, isDirty = true;
|
if (args.length) {
|
||||||
const effect = () => {
|
const next = isFunc(args[0]) ? args[0](val) : args[0];
|
||||||
if (effect._deleted) return;
|
if (!Object.is(val, next)) {
|
||||||
clearDeps(effect);
|
val = next; if (key) localStorage.setItem(key, JSON.stringify(val));
|
||||||
runWithContext(effect, () => {
|
trackUpdate(subs, true);
|
||||||
const newVal = initialValue();
|
|
||||||
if (!Object.is(cachedValue, newVal) || isDirty) {
|
|
||||||
cachedValue = newVal; isDirty = false; trackUpdate(subscribers, true);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
trackUpdate(subs); return val;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const $$ = (obj, cache = new WeakMap()) => {
|
||||||
|
if (!isObj(obj)) return obj;
|
||||||
|
if (cache.has(obj)) return cache.get(obj);
|
||||||
|
const subs = {};
|
||||||
|
const proxy = new Proxy(obj, {
|
||||||
|
get: (t, k) => { trackUpdate(subs[k] ??= new Set()); return isObj(t[k]) ? $$(t[k], cache) : t[k]; },
|
||||||
|
set: (t, k, v) => { if (!Object.is(t[k], v)) { t[k] = v; if (subs[k]) trackUpdate(subs[k], true); } return true; }
|
||||||
|
});
|
||||||
|
cache.set(obj, proxy); return proxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Watch = (target, cb) => {
|
||||||
|
const explicit = isArr(target), runner = () => {
|
||||||
|
if (runner._deleted) return;
|
||||||
|
clearDeps(runner); runCleanups(runner._cleanups);
|
||||||
|
runner.depth = activeEffect ? activeEffect.depth + 1 : 0;
|
||||||
|
runWithContext(runner, () => {
|
||||||
|
const prev = currentOwner; currentOwner = { cleanups: runner._cleanups };
|
||||||
|
explicit ? (untrack(cb), target.forEach(d => isFunc(d) && d())) : cb();
|
||||||
|
currentOwner = prev;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
assign(effect, {
|
assign(runner, { _deps: new Set(), _cleanups: new Set(), _deleted: false,
|
||||||
_deps: new Set(), _isComputed: true, _subs: subscribers, _deleted: false,
|
stop: () => { runner._deleted = true; clearDeps(runner); runCleanups(runner._cleanups); } });
|
||||||
markDirty: () => (isDirty = true),
|
onUnmount(runner.stop);
|
||||||
stop: () => { effect._deleted = true; clearDeps(effect); subscribers.clear(); }
|
runner(); return runner.stop;
|
||||||
});
|
};
|
||||||
if (currentOwner) currentOwner.cleanups.add(effect.stop);
|
|
||||||
return () => { if (isDirty) effect(); trackUpdate(subscribers); return cachedValue; };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal
|
const Tag = (tag, props = {}, children = []) => {
|
||||||
let value = initialValue;
|
if (props instanceof Node || isArr(props) || !isObj(props)) { children = props; props = {}; }
|
||||||
if (storageKey) try { value = JSON.parse(localStorage.getItem(storageKey) || "null") ?? value; } catch(e){}
|
const isSVG = /^(svg|path|circle|rect|line|polyline|polygon|g|defs|text|tspan|use)$/.test(tag);
|
||||||
|
const el = isSVG ? doc.createElementNS("http://www.w3.org/2000/svg", tag) : doc.createElement(tag);
|
||||||
|
el._cleanups = new Set();
|
||||||
|
|
||||||
return (...args) => {
|
for (let [k, v] of Object.entries(props)) {
|
||||||
if (args.length) {
|
if (k === "ref") { isFunc(v) ? v(el) : (v.current = el); continue; }
|
||||||
const next = isFunc(args[0]) ? args[0](value) : args[0];
|
if (k.startsWith("on")) {
|
||||||
if (!Object.is(value, next)) {
|
const ev = k.slice(2).toLowerCase(); el.addEventListener(ev, v);
|
||||||
value = next;
|
el._cleanups.add(() => el.removeEventListener(ev, v));
|
||||||
if (storageKey) localStorage.setItem(storageKey, JSON.stringify(value));
|
} else if (isFunc(v)) {
|
||||||
trackUpdate(subscribers, true);
|
el._cleanups.add(Watch(() => {
|
||||||
|
const val = v(), safe = (k === 'src' || k === 'href') && String(val).includes('javascript:') ? '#' : val;
|
||||||
|
k === "class" ? (el.className = safe || "") : (safe == null || safe === false ? el.removeAttribute(k) : el.setAttribute(k, safe === true ? "" : safe));
|
||||||
|
}));
|
||||||
|
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
|
||||||
|
el.addEventListener(k === "checked" ? "change" : "input", (e) => v(e.target[k]));
|
||||||
|
}
|
||||||
|
} else el.setAttribute(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const append = (c) => {
|
||||||
|
if (isArr(c)) return c.forEach(append);
|
||||||
|
if (isFunc(c)) {
|
||||||
|
const m = doc.createTextNode(""); el.appendChild(m); let curr = [];
|
||||||
|
el._cleanups.add(Watch(() => {
|
||||||
|
const res = c(), next = (isArr(res) ? res : [res]).map(ensureNode);
|
||||||
|
curr.forEach(n => { if (n instanceof Node) { cleanupNode(n); n.remove(); } });
|
||||||
|
next.forEach(n => m.parentNode?.insertBefore(n, m)); curr = next;
|
||||||
|
}));
|
||||||
|
} else el.appendChild(ensureNode(c));
|
||||||
|
};
|
||||||
|
append(children); return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Render = (fn) => {
|
||||||
|
const cleanups = new Set(), prev = currentOwner, container = doc.createElement("div");
|
||||||
|
container.style.display = "contents"; currentOwner = { cleanups };
|
||||||
|
const res = fn({ onCleanup: (f) => cleanups.add(f) });
|
||||||
|
(isArr(res) ? res : [res]).forEach(r => container.appendChild(ensureNode(r)));
|
||||||
|
currentOwner = prev;
|
||||||
|
return { _isRuntime: true, container, destroy: () => { runCleanups(cleanups); cleanupNode(container); container.remove(); } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const If = (cond, t, f = null, trans = null) => {
|
||||||
|
const m = doc.createTextNode(""), root = Tag("div", { style: "display:contents" }, [m]);
|
||||||
|
let view = null, last = null;
|
||||||
|
Watch(() => {
|
||||||
|
const s = !!(isFunc(cond) ? cond() : cond);
|
||||||
|
if (s === last) return; last = s;
|
||||||
|
const dispose = () => { if(view) { view.destroy(); view = null; } };
|
||||||
|
if (view && !s && trans?.out) trans.out(view.container, dispose); else dispose();
|
||||||
|
const b = s ? t : f;
|
||||||
|
if (b) {
|
||||||
|
view = Render(() => isFunc(b) ? b() : b);
|
||||||
|
root.insertBefore(view.container, m);
|
||||||
|
if (trans?.in) trans.in(view.container);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
trackUpdate(subscribers);
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const $$ = (object, cache = new WeakMap()) => {
|
|
||||||
if (!isObj(object)) return object;
|
|
||||||
if (cache.has(object)) return cache.get(object);
|
|
||||||
const subs = {};
|
|
||||||
const proxy = new Proxy(object, {
|
|
||||||
get: (t, k) => { trackUpdate(subs[k] ??= new Set()); const v = t[k]; return isObj(v) ? $$(v, cache) : v; },
|
|
||||||
set: (t, k, v) => { if (!Object.is(t[k], v)) { t[k] = v; if (subs[k]) trackUpdate(subs[k], true); } return true; }
|
|
||||||
});
|
|
||||||
cache.set(object, proxy); return proxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Watch = (target, callbackFn) => {
|
|
||||||
const isExplicit = isArr(target);
|
|
||||||
const callback = isExplicit ? callbackFn : target;
|
|
||||||
if (!isFunc(callback)) return () => {};
|
|
||||||
|
|
||||||
const owner = currentOwner;
|
|
||||||
const runner = () => {
|
|
||||||
if (runner._deleted) return;
|
|
||||||
clearDeps(runner); runCleanups(runner._cleanups);
|
|
||||||
runner.depth = activeEffect ? activeEffect.depth + 1 : 0;
|
|
||||||
|
|
||||||
runWithContext(runner, () => {
|
|
||||||
const prevOwner = currentOwner;
|
|
||||||
currentOwner = { cleanups: runner._cleanups };
|
|
||||||
if (isExplicit) { runWithContext(null, callback); target.forEach(d => isFunc(d) && d()); }
|
|
||||||
else callback();
|
|
||||||
currentOwner = prevOwner;
|
|
||||||
});
|
});
|
||||||
|
return root;
|
||||||
};
|
};
|
||||||
|
|
||||||
assign(runner, {
|
const For = (src, itemFn, keyFn) => {
|
||||||
_deps: new Set(), _cleanups: new Set(), _deleted: false,
|
const m = doc.createTextNode(""), root = Tag("div", { style: "display:contents" }, [m]);
|
||||||
stop: () => {
|
let cache = new Map();
|
||||||
if (runner._deleted) return;
|
Watch(() => {
|
||||||
runner._deleted = true; effectQueue.delete(runner);
|
const items = (isFunc(src) ? src() : src) || [], next = new Map(), order = [];
|
||||||
clearDeps(runner); runCleanups(runner._cleanups);
|
items.forEach((item, i) => {
|
||||||
if (owner) owner.cleanups.delete(runner.stop);
|
const k = keyFn ? keyFn(item, i) : i;
|
||||||
}
|
let v = cache.get(k) || Render(() => itemFn(item, i));
|
||||||
});
|
cache.delete(k); next.set(k, v); order.push(k);
|
||||||
|
});
|
||||||
if (owner) owner.cleanups.add(runner.stop);
|
cache.forEach(v => v.destroy());
|
||||||
runner(); return runner.stop;
|
let anchor = m;
|
||||||
};
|
for (let i = order.length - 1; i >= 0; i--) {
|
||||||
|
const v = next.get(order[i]);
|
||||||
// --- 5. DOM & COMPONENTS ---
|
if (v.container.nextSibling !== anchor) root.insertBefore(v.container, anchor);
|
||||||
const Render = (renderFn) => {
|
anchor = v.container;
|
||||||
const cleanups = new Set(), prev = currentOwner, container = createEl("div");
|
|
||||||
container.style.display = "contents";
|
|
||||||
currentOwner = { cleanups };
|
|
||||||
|
|
||||||
const process = (res) => {
|
|
||||||
if (!res) return;
|
|
||||||
if (res._isRuntime) { cleanups.add(res.destroy); container.appendChild(res.container); }
|
|
||||||
else if (isArr(res)) res.forEach(process);
|
|
||||||
else container.appendChild(ensureNode(res));
|
|
||||||
};
|
|
||||||
|
|
||||||
try { process(renderFn({ onCleanup: (fn) => cleanups.add(fn) })); }
|
|
||||||
finally { currentOwner = prev; }
|
|
||||||
|
|
||||||
return { _isRuntime: true, container, destroy: () => { runCleanups(cleanups); cleanupNode(container); container.remove(); } };
|
|
||||||
};
|
|
||||||
|
|
||||||
const Tag = (tag, props = {}, children = []) => {
|
|
||||||
if (props instanceof Node || isArr(props) || !isObj(props)) { children = props; props = {}; }
|
|
||||||
|
|
||||||
const isSVG = /^(svg|path|circle|rect|line|polyline|polygon|g|defs|text|tspan|use)$/.test(tag);
|
|
||||||
const el = isSVG ? doc.createElementNS("http://www.w3.org/2000/svg", tag) : createEl(tag);
|
|
||||||
el._cleanups = new Set(); el.onUnmount = (fn) => el._cleanups.add(fn);
|
|
||||||
|
|
||||||
for (let [k, v] of Object.entries(props)) {
|
|
||||||
if (k === "ref") { isFunc(v) ? v(el) : (v.current = el); continue; }
|
|
||||||
const isSig = isFunc(v);
|
|
||||||
|
|
||||||
if (k.startsWith("on")) {
|
|
||||||
const evt = k.slice(2).toLowerCase().split(".")[0];
|
|
||||||
el.addEventListener(evt, v); el._cleanups.add(() => el.removeEventListener(evt, v));
|
|
||||||
} else if (isSig) {
|
|
||||||
el._cleanups.add(Watch(() => {
|
|
||||||
const val = v(), clean = (k === 'src' || k === 'href') && String(val).toLowerCase().includes('javascript:') ? '#' : val;
|
|
||||||
k === "class" ? (el.className = clean || "") : clean == null || clean === false ? el.removeAttribute(k) : el.setAttribute(k, clean === true ? "" : clean);
|
|
||||||
}));
|
|
||||||
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
|
|
||||||
const evt = k === "checked" ? "change" : "input", handler = (e) => v(e.target[k]);
|
|
||||||
el.addEventListener(evt, handler); el._cleanups.add(() => el.removeEventListener(evt, handler));
|
|
||||||
}
|
}
|
||||||
} else el.setAttribute(k, v);
|
cache = next;
|
||||||
}
|
|
||||||
|
|
||||||
const append = (c) => {
|
|
||||||
if (isArr(c)) return c.forEach(append);
|
|
||||||
if (isFunc(c)) {
|
|
||||||
const marker = createText(""); el.appendChild(marker);
|
|
||||||
let curr = [];
|
|
||||||
el._cleanups.add(Watch(() => {
|
|
||||||
const res = c(), next = (isArr(res) ? res : [res]).map(ensureNode);
|
|
||||||
curr.forEach(n => { cleanupNode(n); n.remove(); });
|
|
||||||
next.forEach(n => marker.parentNode?.insertBefore(n, marker));
|
|
||||||
curr = next;
|
|
||||||
}));
|
|
||||||
} else el.appendChild(ensureNode(c));
|
|
||||||
};
|
|
||||||
append(children); return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 6. CONTROL FLOW ---
|
|
||||||
const If = (condition, thenVal, otherwiseVal = null, transition = null) => {
|
|
||||||
const marker = createText(""), container = Tag("div", { style: "display:contents" }, [marker]);
|
|
||||||
let currentView = null, lastState = null;
|
|
||||||
|
|
||||||
Watch(() => {
|
|
||||||
const state = !!(isFunc(condition) ? condition() : condition);
|
|
||||||
if (state === lastState) return;
|
|
||||||
lastState = state;
|
|
||||||
|
|
||||||
const dispose = () => { currentView?.destroy(); currentView = null; };
|
|
||||||
if (currentView && !state && transition?.out) transition.out(currentView.container, dispose); else dispose();
|
|
||||||
|
|
||||||
const branch = state ? thenVal : otherwiseVal;
|
|
||||||
if (branch) {
|
|
||||||
currentView = Render(() => isFunc(branch) ? branch() : branch);
|
|
||||||
container.insertBefore(currentView.container, marker);
|
|
||||||
if (state && transition?.in) transition.in(currentView.container);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return container;
|
|
||||||
};
|
|
||||||
|
|
||||||
const For = (source, renderFn, keyFn, tag = "div", props = { style: "display:contents" }) => {
|
|
||||||
const marker = createText(""), container = Tag(tag, props, [marker]);
|
|
||||||
let viewCache = new Map();
|
|
||||||
|
|
||||||
Watch(() => {
|
|
||||||
const items = (isFunc(source) ? source() : source) || [], nextCache = new Map(), order = [];
|
|
||||||
|
|
||||||
items.forEach((item, i) => {
|
|
||||||
const key = keyFn ? keyFn(item, i) : i;
|
|
||||||
let view = viewCache.get(key);
|
|
||||||
if (!view) {
|
|
||||||
const res = renderFn(item, i);
|
|
||||||
view = res instanceof Node ? { container: res, destroy: () => { cleanupNode(res); res.remove(); } } : Render(() => res);
|
|
||||||
}
|
|
||||||
viewCache.delete(key); nextCache.set(key, view); order.push(key);
|
|
||||||
});
|
});
|
||||||
|
return root;
|
||||||
|
};
|
||||||
|
|
||||||
viewCache.forEach(v => v.destroy());
|
// --- ROUTER SYSTEM ---
|
||||||
let anchor = marker;
|
const Router = (routes) => {
|
||||||
for (let i = order.length - 1; i >= 0; i--) {
|
const getHash = () => window.location.hash.replace(/^#/, "") || "/";
|
||||||
const view = nextCache.get(order[i]);
|
const path = $(getHash());
|
||||||
if (view.container.nextSibling !== anchor) container.insertBefore(view.container, anchor);
|
window.addEventListener("hashchange", () => path(getHash()));
|
||||||
anchor = view.container;
|
const outlet = Tag("div", { class: "router-outlet" });
|
||||||
}
|
let currentView = null;
|
||||||
viewCache = nextCache;
|
|
||||||
});
|
|
||||||
return container;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 7. ROUTER & MOUNT ---
|
Watch([path], async () => {
|
||||||
const Router = (routes) => {
|
const cur = path(), route = routes.find(r => {
|
||||||
const path = $(window.location.hash.replace(/^#/, "") || "/");
|
const p1 = r.path.split("/").filter(Boolean), p2 = cur.split("/").filter(Boolean);
|
||||||
window.addEventListener("hashchange", () => path(window.location.hash.replace(/^#/, "") || "/"));
|
return p1.length === p2.length && p1.every((p, i) => p.startsWith(":") || p === p2[i]);
|
||||||
const outlet = Tag("div", { class: "router-transition" });
|
}) || routes.find(r => r.path === "*");
|
||||||
let currentView = null;
|
|
||||||
|
|
||||||
Watch([path], async () => {
|
if (route) {
|
||||||
const current = path();
|
let comp = route.component;
|
||||||
const route = routes.find(r => {
|
if (isFunc(comp) && comp.toString().includes('import')) comp = (await comp()).default;
|
||||||
const p1 = r.path.split("/").filter(Boolean), p2 = current.split("/").filter(Boolean);
|
const params = {};
|
||||||
return p1.length === p2.length && p1.every((part, i) => part.startsWith(":") || part === p2[i]);
|
route.path.split("/").filter(Boolean).forEach((p, i) => { if (p.startsWith(":")) params[p.slice(1)] = cur.split("/").filter(Boolean)[i]; });
|
||||||
}) || routes.find(r => r.path === "*");
|
currentView?.destroy();
|
||||||
|
Router.params(params);
|
||||||
|
currentView = Render(() => isFunc(comp) ? comp(params) : comp);
|
||||||
|
outlet.appendChild(currentView.container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return outlet;
|
||||||
|
};
|
||||||
|
|
||||||
if (route) {
|
// router utils
|
||||||
let comp = route.component;
|
Router.params = $({});
|
||||||
if (isFunc(comp) && comp.toString().includes('import')) comp = (await comp()).default || await comp();
|
Router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/");
|
||||||
|
Router.back = () => window.history.back();
|
||||||
|
Router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
||||||
|
|
||||||
const params = {};
|
const Mount = (comp, target) => {
|
||||||
route.path.split("/").filter(Boolean).forEach((p, i) => { if (p.startsWith(":")) params[p.slice(1)] = current.split("/").filter(Boolean)[i]; });
|
const t = typeof target === "string" ? doc.querySelector(target) : target;
|
||||||
|
if (!t) return; if (MOUNTED_NODES.has(t)) MOUNTED_NODES.get(t).destroy();
|
||||||
|
const inst = Render(isFunc(comp) ? comp : () => comp);
|
||||||
|
t.replaceChildren(inst.container); MOUNTED_NODES.set(t, inst); return inst;
|
||||||
|
};
|
||||||
|
|
||||||
currentView?.destroy();
|
return { $, $$, Watch, Tag, Render, If, For, Router, Mount, untrack, onUnmount };
|
||||||
if (Router.params) Router.params(params);
|
})();
|
||||||
|
|
||||||
currentView = Render(() => { try { return isFunc(comp) ? comp(params) : comp; } catch (e) { return Tag("div", {}, "Error"); } });
|
|
||||||
outlet.appendChild(currentView.container);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return outlet;
|
|
||||||
};
|
|
||||||
|
|
||||||
Router.params = $({});
|
|
||||||
Router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/");
|
|
||||||
Router.back = () => window.history.back();
|
|
||||||
Router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
|
||||||
|
|
||||||
const Mount = (comp, target) => {
|
|
||||||
const el = typeof target === "string" ? doc.querySelector(target) : target;
|
|
||||||
if (!el) return;
|
|
||||||
if (MOUNTED_NODES.has(el)) MOUNTED_NODES.get(el).destroy();
|
|
||||||
const instance = Render(isFunc(comp) ? comp : () => comp);
|
|
||||||
el.replaceChildren(instance.container);
|
|
||||||
MOUNTED_NODES.set(el, instance);
|
|
||||||
return instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount };
|
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
assign(window, SigPro);
|
Object.assign(window, SigPro);
|
||||||
"div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter"
|
"div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer ul ol li a em strong pre code form label input textarea select button img svg"
|
||||||
.split(" ").forEach(t => {
|
.split(" ").forEach(t => window[t[0].toUpperCase() + t.slice(1)] = (p, c) => SigPro.Tag(t, p, c));
|
||||||
const h = t[0].toUpperCase() + t.slice(1);
|
|
||||||
if (!(h in window)) window[h] = (p, c) => Tag(t, p, c);
|
|
||||||
});
|
|
||||||
window.SigPro = Object.freeze(SigPro);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { $, $$, Render, Watch, Tag, If, For, Router, Mount };
|
|
||||||
export default SigPro;
|
export default SigPro;
|
||||||
Reference in New Issue
Block a user