Actualizar sigpro-lite.js
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
|
// ============================================================
|
||||||
|
// SIGPRO CORE · Simple, Fast, Memory‑Safe
|
||||||
|
// ============================================================
|
||||||
let currentContext = null
|
let currentContext = null
|
||||||
|
|
||||||
const getContext = () => currentContext
|
const getContext = () => currentContext
|
||||||
const runInContext = (ctx, fn) => {
|
const runInContext = (ctx, fn) => {
|
||||||
const prev = currentContext
|
const prev = currentContext
|
||||||
@@ -6,16 +10,19 @@ const runInContext = (ctx, fn) => {
|
|||||||
try { return fn() }
|
try { return fn() }
|
||||||
finally { currentContext = prev }
|
finally { currentContext = prev }
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCleanup = (fn) => {
|
const onCleanup = (fn) => {
|
||||||
const ctx = getContext()
|
const ctx = getContext()
|
||||||
if (!ctx) throw new Error('onCleanup must be called within a reactive root')
|
if (!ctx) throw new Error('onCleanup must be called within a reactive root')
|
||||||
ctx.cleanups.add(fn)
|
ctx.cleanups.add(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMount = (fn) => {
|
const onMount = (fn) => {
|
||||||
const ctx = getContext()
|
const ctx = getContext()
|
||||||
if (!ctx) throw new Error('onMount must be called within a reactive root')
|
if (!ctx) throw new Error('onMount must be called within a reactive root')
|
||||||
queueMicrotask(() => runInContext(ctx, fn))
|
queueMicrotask(() => runInContext(ctx, fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRoot = (fn) => {
|
const createRoot = (fn) => {
|
||||||
const ctx = { cleanups: new Set(), parent: currentContext }
|
const ctx = { cleanups: new Set(), parent: currentContext }
|
||||||
return runInContext(ctx, () => {
|
return runInContext(ctx, () => {
|
||||||
@@ -25,12 +32,14 @@ const createRoot = (fn) => {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createComponent = (fn) => (...args) => {
|
const createComponent = (fn) => (...args) => {
|
||||||
const parent = getContext()
|
const parent = getContext()
|
||||||
const ctx = { cleanups: new Set(), parent }
|
const ctx = { cleanups: new Set(), parent }
|
||||||
if (parent) parent.cleanups.add(() => { ctx.cleanups.forEach(c => c()); ctx.cleanups.clear() })
|
if (parent) parent.cleanups.add(() => { ctx.cleanups.forEach(c => c()); ctx.cleanups.clear() })
|
||||||
return runInContext(ctx, () => fn(...args))
|
return runInContext(ctx, () => fn(...args))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createSignal = (initialValue) => {
|
const createSignal = (initialValue) => {
|
||||||
const subscribers = new Set()
|
const subscribers = new Set()
|
||||||
let value = initialValue
|
let value = initialValue
|
||||||
@@ -52,6 +61,7 @@ const createSignal = (initialValue) => {
|
|||||||
}
|
}
|
||||||
return signal
|
return signal
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEffect = (fn) => {
|
const createEffect = (fn) => {
|
||||||
const ctx = getContext()
|
const ctx = getContext()
|
||||||
if (!ctx) throw new Error('createEffect must be called within a reactive root')
|
if (!ctx) throw new Error('createEffect must be called within a reactive root')
|
||||||
@@ -72,6 +82,7 @@ const createEffect = (fn) => {
|
|||||||
})
|
})
|
||||||
effect.run()
|
effect.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
const mount = (component, selector) => {
|
const mount = (component, selector) => {
|
||||||
const root = document.querySelector(selector)
|
const root = document.querySelector(selector)
|
||||||
if (!root) throw new Error(`Selector "${selector}" not found`)
|
if (!root) throw new Error(`Selector "${selector}" not found`)
|
||||||
@@ -82,8 +93,14 @@ const mount = (component, selector) => {
|
|||||||
root._destroy = app._destroy
|
root._destroy = app._destroy
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper para crear elementos (con soporte SVG)
|
||||||
const h = (tag, props, ...children) => {
|
const h = (tag, props, ...children) => {
|
||||||
const el = document.createElement(tag)
|
const isSVG = /^(svg|path|circle|rect|line|polyline|polygon|g|defs|text|tspan|use)$/i.test(tag)
|
||||||
|
const el = isSVG
|
||||||
|
? document.createElementNS('http://www.w3.org/2000/svg', tag)
|
||||||
|
: document.createElement(tag)
|
||||||
|
|
||||||
if (props) {
|
if (props) {
|
||||||
Object.entries(props).forEach(([k, v]) => {
|
Object.entries(props).forEach(([k, v]) => {
|
||||||
if (k.startsWith('on')) {
|
if (k.startsWith('on')) {
|
||||||
@@ -97,32 +114,53 @@ const h = (tag, props, ...children) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
children.flat().forEach(c => {
|
children.flat().forEach(c => {
|
||||||
if (c instanceof Node) el.appendChild(c)
|
if (c instanceof Node) el.appendChild(c)
|
||||||
else el.appendChild(document.createTextNode(String(c ?? '')))
|
else el.appendChild(document.createTextNode(String(c ?? '')))
|
||||||
})
|
})
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
const For = createComponent(({ each, children }) => {
|
|
||||||
|
// ===== IF / FOR / ROUTER (API IDÉNTICA A B) =====
|
||||||
|
const If = createComponent((cond, thenFn) => {
|
||||||
|
const anchor = document.createTextNode('')
|
||||||
|
const container = h('div', { style: 'display:contents' }, anchor)
|
||||||
|
let current = null
|
||||||
|
createEffect(() => {
|
||||||
|
const show = !!cond()
|
||||||
|
if (show && !current) {
|
||||||
|
current = createRoot(() => thenFn())
|
||||||
|
container.insertBefore(current, anchor)
|
||||||
|
} else if (!show && current) {
|
||||||
|
if (current._destroy) current._destroy()
|
||||||
|
else current.remove()
|
||||||
|
current = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCleanup(() => current?._destroy?.())
|
||||||
|
return container
|
||||||
|
})
|
||||||
|
|
||||||
|
const For = createComponent((source, itemFn) => {
|
||||||
const container = h('div', { style: 'display:contents' })
|
const container = h('div', { style: 'display:contents' })
|
||||||
const marker = document.createTextNode('')
|
const marker = document.createTextNode('')
|
||||||
container.appendChild(marker)
|
container.appendChild(marker)
|
||||||
let cache = new Map()
|
let cache = new Map()
|
||||||
const source = typeof each === 'function' ? each : () => each
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const items = source() || []
|
const items = source() || []
|
||||||
const newCache = new Map()
|
const newCache = new Map()
|
||||||
const order = []
|
const order = []
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i]
|
const item = items[i]
|
||||||
const key = typeof item === 'object' && item !== null && 'id' in item ? item.id : i
|
const key = (item && typeof item === 'object' && 'id' in item) ? item.id : i
|
||||||
let view = cache.get(key)
|
let view = cache.get(key)
|
||||||
if (!view) view = createRoot(() => children(item, i))
|
if (!view) view = createRoot(() => itemFn(item, i))
|
||||||
newCache.set(key, view)
|
newCache.set(key, view)
|
||||||
order.push(key)
|
order.push(key)
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
}
|
}
|
||||||
cache.forEach(v => v._destroy ? v._destroy() : (v.remove?.(), v._destroy?.()))
|
cache.forEach(v => v._destroy ? v._destroy() : v.remove?.())
|
||||||
cache = newCache
|
cache = newCache
|
||||||
let anchor = marker
|
let anchor = marker
|
||||||
for (let i = order.length - 1; i >= 0; i--) {
|
for (let i = order.length - 1; i >= 0; i--) {
|
||||||
@@ -135,25 +173,7 @@ const For = createComponent(({ each, children }) => {
|
|||||||
onCleanup(() => cache.forEach(v => v._destroy ? v._destroy() : v.remove?.()))
|
onCleanup(() => cache.forEach(v => v._destroy ? v._destroy() : v.remove?.()))
|
||||||
return container
|
return container
|
||||||
})
|
})
|
||||||
const If = createComponent(({ when, children }) => {
|
|
||||||
const anchor = document.createTextNode('')
|
|
||||||
const container = h('div', { style: 'display:contents' }, anchor)
|
|
||||||
let current = null
|
|
||||||
const cond = typeof when === 'function' ? when : () => when
|
|
||||||
createEffect(() => {
|
|
||||||
const show = !!cond()
|
|
||||||
if (show && !current) {
|
|
||||||
current = createRoot(() => children())
|
|
||||||
container.insertBefore(current, anchor)
|
|
||||||
} else if (!show && current) {
|
|
||||||
if (current._destroy) current._destroy()
|
|
||||||
else current.remove()
|
|
||||||
current = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
onCleanup(() => current?._destroy?.())
|
|
||||||
return container
|
|
||||||
})
|
|
||||||
const Router = createComponent(({ routes }) => {
|
const Router = createComponent(({ routes }) => {
|
||||||
const getHash = () => window.location.hash.slice(1) || '/'
|
const getHash = () => window.location.hash.slice(1) || '/'
|
||||||
const path = createSignal(getHash())
|
const path = createSignal(getHash())
|
||||||
@@ -186,7 +206,27 @@ const Router = createComponent(({ routes }) => {
|
|||||||
Router.to = (path) => window.location.hash = path.replace(/^#?\/?/, '#/')
|
Router.to = (path) => window.location.hash = path.replace(/^#?\/?/, '#/')
|
||||||
Router.back = () => window.history.back()
|
Router.back = () => window.history.back()
|
||||||
Router.path = () => window.location.hash.slice(1) || '/'
|
Router.path = () => window.location.hash.slice(1) || '/'
|
||||||
const SigPro = { createRoot, createComponent, createSignal, createEffect, onCleanup, onMount, mount, h, For, If, Router }
|
|
||||||
if (typeof window !== 'undefined') Object.assign(window, SigPro)
|
// ===== API PÚBLICA =====
|
||||||
export { createRoot, createComponent, createSignal, createEffect, onCleanup, onMount, mount, h, For, If, Router }
|
const SigPro = {
|
||||||
|
createRoot, createComponent, createSignal, createEffect,
|
||||||
|
onCleanup, onMount, mount, h,
|
||||||
|
If, For, Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== EXPOSICIÓN GLOBAL (OPCIONAL, COMO EN B) =====
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
Object.assign(window, SigPro)
|
||||||
|
const tags = '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(' ')
|
||||||
|
tags.forEach(t => {
|
||||||
|
const helper = t[0].toUpperCase() + t.slice(1)
|
||||||
|
window[helper] = (props, ...children) => h(t, props, children)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createRoot, createComponent, createSignal, createEffect,
|
||||||
|
onCleanup, onMount, mount, h,
|
||||||
|
If, For, Router
|
||||||
|
}
|
||||||
export default SigPro
|
export default SigPro
|
||||||
Reference in New Issue
Block a user