Añadir sigpro-lite.js
This commit is contained in:
192
sigpro-lite.js
Normal file
192
sigpro-lite.js
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
let currentContext = null
|
||||||
|
const getContext = () => currentContext
|
||||||
|
const runInContext = (ctx, fn) => {
|
||||||
|
const prev = currentContext
|
||||||
|
currentContext = ctx
|
||||||
|
try { return fn() }
|
||||||
|
finally { currentContext = prev }
|
||||||
|
}
|
||||||
|
const onCleanup = (fn) => {
|
||||||
|
const ctx = getContext()
|
||||||
|
if (!ctx) throw new Error('onCleanup must be called within a reactive root')
|
||||||
|
ctx.cleanups.add(fn)
|
||||||
|
}
|
||||||
|
const onMount = (fn) => {
|
||||||
|
const ctx = getContext()
|
||||||
|
if (!ctx) throw new Error('onMount must be called within a reactive root')
|
||||||
|
queueMicrotask(() => runInContext(ctx, fn))
|
||||||
|
}
|
||||||
|
const createRoot = (fn) => {
|
||||||
|
const ctx = { cleanups: new Set(), parent: currentContext }
|
||||||
|
return runInContext(ctx, () => {
|
||||||
|
const result = fn(ctx)
|
||||||
|
const destroy = () => { ctx.cleanups.forEach(c => c()); ctx.cleanups.clear() }
|
||||||
|
if (result instanceof Node) result._destroy = destroy
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const createComponent = (fn) => (...args) => {
|
||||||
|
const parent = getContext()
|
||||||
|
const ctx = { cleanups: new Set(), parent }
|
||||||
|
if (parent) parent.cleanups.add(() => { ctx.cleanups.forEach(c => c()); ctx.cleanups.clear() })
|
||||||
|
return runInContext(ctx, () => fn(...args))
|
||||||
|
}
|
||||||
|
const createSignal = (initialValue) => {
|
||||||
|
const subscribers = new Set()
|
||||||
|
let value = initialValue
|
||||||
|
const signal = (...args) => {
|
||||||
|
if (args.length === 0) {
|
||||||
|
const ctx = getContext()
|
||||||
|
if (ctx?.activeEffect) {
|
||||||
|
subscribers.add(ctx.activeEffect)
|
||||||
|
ctx.activeEffect.deps.add(subscribers)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
const next = typeof args[0] === 'function' ? args[0](value) : args[0]
|
||||||
|
if (!Object.is(value, next)) {
|
||||||
|
value = next
|
||||||
|
;[...subscribers].forEach(e => e.run())
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return signal
|
||||||
|
}
|
||||||
|
const createEffect = (fn) => {
|
||||||
|
const ctx = getContext()
|
||||||
|
if (!ctx) throw new Error('createEffect must be called within a reactive root')
|
||||||
|
const effect = {
|
||||||
|
deps: new Set(),
|
||||||
|
run: () => {
|
||||||
|
effect.deps.forEach(d => d.delete(effect))
|
||||||
|
effect.deps.clear()
|
||||||
|
const prev = ctx.activeEffect
|
||||||
|
ctx.activeEffect = effect
|
||||||
|
try { fn() }
|
||||||
|
finally { ctx.activeEffect = prev }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCleanup(() => {
|
||||||
|
effect.deps.forEach(d => d.delete(effect))
|
||||||
|
effect.deps.clear()
|
||||||
|
})
|
||||||
|
effect.run()
|
||||||
|
}
|
||||||
|
const mount = (component, selector) => {
|
||||||
|
const root = document.querySelector(selector)
|
||||||
|
if (!root) throw new Error(`Selector "${selector}" not found`)
|
||||||
|
if (root._destroy) root._destroy()
|
||||||
|
root.innerHTML = ''
|
||||||
|
const app = createRoot(component)
|
||||||
|
root.appendChild(app)
|
||||||
|
root._destroy = app._destroy
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
const h = (tag, props, ...children) => {
|
||||||
|
const el = document.createElement(tag)
|
||||||
|
if (props) {
|
||||||
|
Object.entries(props).forEach(([k, v]) => {
|
||||||
|
if (k.startsWith('on')) {
|
||||||
|
const event = k.slice(2).toLowerCase()
|
||||||
|
el.addEventListener(event, v)
|
||||||
|
onCleanup(() => el.removeEventListener(event, v))
|
||||||
|
} else if (k === 'style' && typeof v === 'object') {
|
||||||
|
Object.assign(el.style, v)
|
||||||
|
} else {
|
||||||
|
el.setAttribute(k, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
children.flat().forEach(c => {
|
||||||
|
if (c instanceof Node) el.appendChild(c)
|
||||||
|
else el.appendChild(document.createTextNode(String(c ?? '')))
|
||||||
|
})
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
const For = createComponent(({ each, children }) => {
|
||||||
|
const container = h('div', { style: 'display:contents' })
|
||||||
|
const marker = document.createTextNode('')
|
||||||
|
container.appendChild(marker)
|
||||||
|
let cache = new Map()
|
||||||
|
const source = typeof each === 'function' ? each : () => each
|
||||||
|
createEffect(() => {
|
||||||
|
const items = source() || []
|
||||||
|
const newCache = new Map()
|
||||||
|
const order = []
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i]
|
||||||
|
const key = typeof item === 'object' && item !== null && 'id' in item ? item.id : i
|
||||||
|
let view = cache.get(key)
|
||||||
|
if (!view) view = createRoot(() => children(item, i))
|
||||||
|
newCache.set(key, view)
|
||||||
|
order.push(key)
|
||||||
|
cache.delete(key)
|
||||||
|
}
|
||||||
|
cache.forEach(v => v._destroy ? v._destroy() : (v.remove?.(), v._destroy?.()))
|
||||||
|
cache = newCache
|
||||||
|
let anchor = marker
|
||||||
|
for (let i = order.length - 1; i >= 0; i--) {
|
||||||
|
const view = newCache.get(order[i])
|
||||||
|
if (view instanceof Node && view.nextSibling !== anchor)
|
||||||
|
container.insertBefore(view, anchor)
|
||||||
|
anchor = view
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCleanup(() => cache.forEach(v => v._destroy ? v._destroy() : v.remove?.()))
|
||||||
|
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 getHash = () => window.location.hash.slice(1) || '/'
|
||||||
|
const path = createSignal(getHash())
|
||||||
|
const handler = () => path(getHash())
|
||||||
|
window.addEventListener('hashchange', handler)
|
||||||
|
onCleanup(() => window.removeEventListener('hashchange', handler))
|
||||||
|
const outlet = h('div', {})
|
||||||
|
let currentView = null
|
||||||
|
createEffect(() => {
|
||||||
|
const cur = path()
|
||||||
|
const match = routes.find(r => {
|
||||||
|
const rParts = r.path.split('/').filter(Boolean)
|
||||||
|
const pParts = cur.split('/').filter(Boolean)
|
||||||
|
return rParts.length === pParts.length && rParts.every((p, i) => p.startsWith(':') || p === pParts[i])
|
||||||
|
}) || routes.find(r => r.path === '*')
|
||||||
|
if (match) {
|
||||||
|
if (currentView?._destroy) currentView._destroy()
|
||||||
|
else if (currentView) currentView.remove()
|
||||||
|
const params = {}
|
||||||
|
match.path.split('/').filter(Boolean).forEach((p, i) => {
|
||||||
|
if (p.startsWith(':')) params[p.slice(1)] = cur.split('/').filter(Boolean)[i]
|
||||||
|
})
|
||||||
|
currentView = createRoot(() => match.component(params))
|
||||||
|
outlet.appendChild(currentView)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCleanup(() => currentView?._destroy?.())
|
||||||
|
return outlet
|
||||||
|
})
|
||||||
|
Router.to = (path) => window.location.hash = path.replace(/^#?\/?/, '#/')
|
||||||
|
Router.back = () => window.history.back()
|
||||||
|
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)
|
||||||
|
export { createRoot, createComponent, createSignal, createEffect, onCleanup, onMount, mount, h, For, If, Router }
|
||||||
|
export default SigPro
|
||||||
Reference in New Issue
Block a user