@getforma/core
Advanced tools
| 'use strict'; | ||
| var chunkBARF67I6_cjs = require('./chunk-BARF67I6.cjs'); | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
| // src/dom/element.ts | ||
| var Fragment = /* @__PURE__ */ Symbol.for("forma.fragment"); | ||
| var SVG_NS = "http://www.w3.org/2000/svg"; | ||
| var XLINK_NS = "http://www.w3.org/1999/xlink"; | ||
| var SVG_TAGS = /* @__PURE__ */ new Set([ | ||
| "svg", | ||
| "path", | ||
| "circle", | ||
| "rect", | ||
| "line", | ||
| "polyline", | ||
| "polygon", | ||
| "ellipse", | ||
| "g", | ||
| "text", | ||
| "tspan", | ||
| "textPath", | ||
| "defs", | ||
| "use", | ||
| "symbol", | ||
| "clipPath", | ||
| "mask", | ||
| "pattern", | ||
| "marker", | ||
| "linearGradient", | ||
| "radialGradient", | ||
| "stop", | ||
| "filter", | ||
| "feGaussianBlur", | ||
| "feColorMatrix", | ||
| "feOffset", | ||
| "feBlend", | ||
| "feMerge", | ||
| "feMergeNode", | ||
| "feComposite", | ||
| "feFlood", | ||
| "feMorphology", | ||
| "feTurbulence", | ||
| "feDisplacementMap", | ||
| "feImage", | ||
| "foreignObject", | ||
| "animate", | ||
| "animateTransform", | ||
| "animateMotion", | ||
| "set", | ||
| "image", | ||
| "switch", | ||
| "desc", | ||
| "title", | ||
| "metadata" | ||
| ]); | ||
| var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([ | ||
| "disabled", | ||
| "checked", | ||
| "readonly", | ||
| "required", | ||
| "autofocus", | ||
| "autoplay", | ||
| "controls", | ||
| "default", | ||
| "defer", | ||
| "formnovalidate", | ||
| "hidden", | ||
| "ismap", | ||
| "loop", | ||
| "multiple", | ||
| "muted", | ||
| "nomodule", | ||
| "novalidate", | ||
| "open", | ||
| "playsinline", | ||
| "reversed", | ||
| "selected", | ||
| "async" | ||
| ]); | ||
| var ELEMENT_PROTOS = null; | ||
| function getProto(tag) { | ||
| if (!ELEMENT_PROTOS) { | ||
| ELEMENT_PROTOS = /* @__PURE__ */ Object.create(null); | ||
| for (const t of [ | ||
| "div", | ||
| "span", | ||
| "p", | ||
| "a", | ||
| "li", | ||
| "ul", | ||
| "ol", | ||
| "button", | ||
| "input", | ||
| "label", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "section", | ||
| "header", | ||
| "footer", | ||
| "main", | ||
| "nav", | ||
| "table", | ||
| "tr", | ||
| "td", | ||
| "th", | ||
| "tbody", | ||
| "img", | ||
| "form", | ||
| "select", | ||
| "option", | ||
| "textarea", | ||
| "i", | ||
| "b", | ||
| "strong", | ||
| "em", | ||
| "small", | ||
| "article", | ||
| "aside", | ||
| "details", | ||
| "summary" | ||
| ]) { | ||
| ELEMENT_PROTOS[t] = document.createElement(t); | ||
| } | ||
| } | ||
| return ELEMENT_PROTOS[tag] ?? (ELEMENT_PROTOS[tag] = document.createElement(tag)); | ||
| } | ||
| var EVENT_NAMES = /* @__PURE__ */ Object.create(null); | ||
| function eventName(key) { | ||
| return EVENT_NAMES[key] ?? (EVENT_NAMES[key] = key.slice(2).toLowerCase()); | ||
| } | ||
| var ABORT_SYM = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| function getAbortController(el) { | ||
| let controller = el[ABORT_SYM]; | ||
| if (!controller) { | ||
| controller = new AbortController(); | ||
| el[ABORT_SYM] = controller; | ||
| } | ||
| return controller; | ||
| } | ||
| function cleanup(el) { | ||
| const controller = el[ABORT_SYM]; | ||
| if (controller) { | ||
| controller.abort(); | ||
| delete el[ABORT_SYM]; | ||
| } | ||
| } | ||
| var CACHE_SYM = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function getCache(el) { | ||
| return el[CACHE_SYM] ?? (el[CACHE_SYM] = /* @__PURE__ */ Object.create(null)); | ||
| } | ||
| function handleClass(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache["class"] === v) return; | ||
| cache["class"] = v; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = v; | ||
| } else { | ||
| el.setAttribute("class", v); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache["class"] === value) return; | ||
| cache["class"] = value; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| } | ||
| } | ||
| function handleStyle(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| let prevKeys = []; | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (typeof v === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === v) return; | ||
| cache["style"] = v; | ||
| prevKeys = []; | ||
| el.style.cssText = v; | ||
| } else if (v && typeof v === "object") { | ||
| const style = el.style; | ||
| const nextKeys = Object.keys(v); | ||
| for (const k of prevKeys) { | ||
| if (!(k in v)) { | ||
| style.removeProperty(k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())); | ||
| } | ||
| } | ||
| Object.assign(style, v); | ||
| prevKeys = nextKeys; | ||
| } | ||
| }); | ||
| } else if (typeof value === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === value) return; | ||
| cache["style"] = value; | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| } | ||
| function handleEvent(el, key, value) { | ||
| const controller = getAbortController(el); | ||
| el.addEventListener( | ||
| eventName(key), | ||
| value, | ||
| { signal: controller.signal } | ||
| ); | ||
| } | ||
| function handleInnerHTML(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const resolved = value(); | ||
| if (resolved == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof resolved !== "object" || !("__html" in resolved)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof resolved | ||
| ); | ||
| } | ||
| const html = resolved.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| const cache = getCache(el); | ||
| if (cache["innerHTML"] === html) return; | ||
| cache["innerHTML"] = html; | ||
| el.innerHTML = html; | ||
| }); | ||
| } else { | ||
| if (value == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| } | ||
| } | ||
| function handleXLink(el, key, value) { | ||
| const localName = key.slice(6); | ||
| if (typeof value === "function") { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(v)); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| } | ||
| } | ||
| } | ||
| function handleBooleanAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache[key] === v) return; | ||
| cache[key] = v; | ||
| if (v) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache[key] === value) return; | ||
| cache[key] = value; | ||
| if (value) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| } | ||
| } | ||
| function handleGenericAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(v); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(value); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| } | ||
| } | ||
| var PROP_HANDLERS = /* @__PURE__ */ new Map(); | ||
| PROP_HANDLERS.set("class", handleClass); | ||
| PROP_HANDLERS.set("className", handleClass); | ||
| PROP_HANDLERS.set("style", handleStyle); | ||
| PROP_HANDLERS.set("ref", () => { | ||
| }); | ||
| PROP_HANDLERS.set("dangerouslySetInnerHTML", handleInnerHTML); | ||
| for (const attr of BOOLEAN_ATTRS) { | ||
| PROP_HANDLERS.set(attr, handleBooleanAttr); | ||
| } | ||
| function applyProp(el, key, value) { | ||
| if (key === "class") { | ||
| handleClass(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| return; | ||
| } | ||
| const handler = PROP_HANDLERS.get(key); | ||
| if (handler) { | ||
| handler(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| handleXLink(el, key, value); | ||
| return; | ||
| } | ||
| handleGenericAttr(el, key, value); | ||
| } | ||
| function applyStaticProp(el, key, value) { | ||
| if (value == null || value === false) return; | ||
| if (key === "class" || key === "className") { | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "style") { | ||
| if (typeof value === "string") { | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "dangerouslySetInnerHTML") { | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| return; | ||
| } | ||
| if (BOOLEAN_ATTRS.has(key)) { | ||
| if (value) el.setAttribute(key, ""); | ||
| return; | ||
| } | ||
| if (value === true) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.setAttribute(key, String(value)); | ||
| } | ||
| } | ||
| function appendChild(parent, child) { | ||
| if (child instanceof Node) { | ||
| parent.appendChild(child); | ||
| return; | ||
| } | ||
| if (typeof child === "string") { | ||
| parent.appendChild(new Text(child)); | ||
| return; | ||
| } | ||
| if (child == null || child === false || child === true) { | ||
| return; | ||
| } | ||
| if (typeof child === "number") { | ||
| parent.appendChild(new Text(String(child))); | ||
| return; | ||
| } | ||
| if (typeof child === "function") { | ||
| if (parent instanceof Element) { | ||
| parent[DYNAMIC_CHILD_SYM] = true; | ||
| } | ||
| let currentNode = null; | ||
| let currentFragChildren = null; | ||
| let warnedArray = false; | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const clearCurrent = () => { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| if (currentNode && currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } | ||
| currentNode = null; | ||
| }; | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = child(); | ||
| let resolved = v; | ||
| if (Array.isArray(v)) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const item of v) { | ||
| if (item instanceof Node) frag.appendChild(item); | ||
| else if (Array.isArray(item)) { | ||
| if (DEBUG) console.warn("[forma] Nested arrays in function children are not supported. Flatten the array or use createList()."); | ||
| } else if (item != null && item !== false && item !== true) { | ||
| frag.appendChild(new Text(String(item))); | ||
| } | ||
| } | ||
| resolved = frag.childNodes.length > 0 ? frag : null; | ||
| if (DEBUG && !warnedArray) { | ||
| warnedArray = true; | ||
| console.warn("[forma] Function child returned an array \u2014 auto-wrapped in DocumentFragment. Consider using createList() or wrapping in a container element for better performance."); | ||
| } | ||
| } | ||
| if (resolved instanceof Node) { | ||
| clearCurrent(); | ||
| const isNewFrag = resolved instanceof DocumentFragment; | ||
| if (isNewFrag) { | ||
| currentFragChildren = Array.from(resolved.childNodes); | ||
| } | ||
| parent.appendChild(resolved); | ||
| currentNode = isNewFrag ? null : resolved; | ||
| } else if (resolved == null || resolved === false || resolved === true) { | ||
| clearCurrent(); | ||
| } else { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| const text = typeof resolved === "symbol" ? String(resolved) : String(resolved ?? ""); | ||
| if (!currentNode) { | ||
| currentNode = new Text(text); | ||
| parent.appendChild(currentNode); | ||
| } else if (currentNode.nodeType === 3) { | ||
| currentNode.data = text; | ||
| } else { | ||
| const tn = new Text(text); | ||
| parent.replaceChild(tn, currentNode); | ||
| currentNode = tn; | ||
| } | ||
| } | ||
| }); | ||
| return; | ||
| } | ||
| if (Array.isArray(child)) { | ||
| for (const item of child) { | ||
| appendChild(parent, item); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| function h(tag, props, ...children) { | ||
| if (typeof tag === "function" && tag !== Fragment) { | ||
| const mergedProps = { ...props ?? {}, children }; | ||
| return tag(mergedProps); | ||
| } | ||
| if (tag === Fragment) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| const tagName = tag; | ||
| if (hydrating) { | ||
| return { type: "element", tag: tagName, props: props ?? null, children }; | ||
| } | ||
| let el; | ||
| if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) { | ||
| el = ELEMENT_PROTOS[tagName].cloneNode(false); | ||
| } else if (SVG_TAGS.has(tagName)) { | ||
| el = document.createElementNS(SVG_NS, tagName); | ||
| } else { | ||
| el = getProto(tagName).cloneNode(false); | ||
| } | ||
| if (props) { | ||
| let hasDynamic = false; | ||
| for (const key in props) { | ||
| if (key === "ref") continue; | ||
| const value = props[key]; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| continue; | ||
| } | ||
| if (typeof value === "function") { | ||
| if (!hasDynamic) { | ||
| el[CACHE_SYM] = /* @__PURE__ */ Object.create(null); | ||
| hasDynamic = true; | ||
| } | ||
| applyProp(el, key, value); | ||
| continue; | ||
| } | ||
| applyStaticProp(el, key, value); | ||
| } | ||
| } | ||
| const childLen = children.length; | ||
| if (childLen === 1) { | ||
| const only = children[0]; | ||
| if (typeof only === "string") { | ||
| el.textContent = only; | ||
| } else if (typeof only === "number") { | ||
| el.textContent = String(only); | ||
| } else { | ||
| appendChild(el, only); | ||
| } | ||
| } else if (childLen > 1) { | ||
| for (const child of children) { | ||
| appendChild(el, child); | ||
| } | ||
| } | ||
| if (props && typeof props["ref"] === "function") { | ||
| props["ref"](el); | ||
| } | ||
| return el; | ||
| } | ||
| function fragment(...children) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| // src/dom/show.ts | ||
| function createShow(when, thenFn, elseFn = () => null) { | ||
| if (hydrating) { | ||
| const branch = when() ? thenFn() : elseFn(); | ||
| return { | ||
| type: "show", | ||
| condition: when, | ||
| whenTrue: thenFn, | ||
| whenFalse: elseFn, | ||
| initialBranch: branch | ||
| }; | ||
| } | ||
| const startMarker = document.createComment("forma-show"); | ||
| const endMarker = document.createComment("/forma-show"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let currentNode = null; | ||
| let lastTruthy = null; | ||
| let currentDispose = null; | ||
| const showDispose = chunkBARF67I6_cjs.internalEffect(() => { | ||
| const truthy = !!when(); | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const DEBUG_LABEL = DEBUG ? thenFn.toString().slice(0, 60) : ""; | ||
| if (truthy === lastTruthy) { | ||
| if (DEBUG) console.log("[forma:show] skip (same)", truthy, DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show]", lastTruthy, "\u2192", truthy, DEBUG_LABEL); | ||
| lastTruthy = truthy; | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| if (DEBUG) console.warn("[forma:show] parentNode is null! skipping.", DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show] parent:", parent.nodeName, "inDoc:", document.contains(parent)); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| if (currentNode) { | ||
| if (currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } else { | ||
| while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) { | ||
| parent.removeChild(startMarker.nextSibling); | ||
| } | ||
| } | ||
| } | ||
| const branchFn = truthy ? thenFn : elseFn; | ||
| if (branchFn) { | ||
| let branchDispose; | ||
| currentNode = chunkBARF67I6_cjs.createRoot((dispose) => { | ||
| branchDispose = dispose; | ||
| return chunkBARF67I6_cjs.untrack(() => branchFn()); | ||
| }); | ||
| currentDispose = branchDispose; | ||
| } else { | ||
| currentNode = null; | ||
| } | ||
| if (currentNode) { | ||
| parent.insertBefore(currentNode, endMarker); | ||
| } | ||
| }); | ||
| fragment2.__showDispose = () => { | ||
| showDispose(); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| }; | ||
| return fragment2; | ||
| } | ||
| // src/dom/hydrate.ts | ||
| var ABORT_SYM2 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var hydrating = false; | ||
| function setHydrating(value) { | ||
| hydrating = value; | ||
| } | ||
| function isDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "element"; | ||
| } | ||
| function isShowDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "show"; | ||
| } | ||
| function isListDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "list"; | ||
| } | ||
| function applyDynamicProps(el, props) { | ||
| if (!props) return; | ||
| for (const key in props) { | ||
| const value = props[key]; | ||
| if (typeof value !== "function") continue; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| let ac = el[ABORT_SYM2]; | ||
| if (!ac) { | ||
| ac = new AbortController(); | ||
| el[ABORT_SYM2] = ac; | ||
| } | ||
| el.addEventListener(key.slice(2).toLowerCase(), value, { signal: ac.signal }); | ||
| continue; | ||
| } | ||
| const fn = value; | ||
| const attrKey = key; | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const v = fn(); | ||
| if (v === false || v == null) { | ||
| el.removeAttribute(attrKey); | ||
| } else if (v === true) { | ||
| el.setAttribute(attrKey, ""); | ||
| } else { | ||
| el.setAttribute(attrKey, String(v)); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function ensureNode(value) { | ||
| if (value instanceof Node) return value; | ||
| if (value == null || value === false || value === true) return null; | ||
| if (typeof value === "string") return new Text(value); | ||
| if (typeof value === "number") return new Text(String(value)); | ||
| if (isDescriptor(value)) return descriptorToElement(value); | ||
| if (isShowDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createShow( | ||
| value.condition, | ||
| () => ensureNode(value.whenTrue()) ?? document.createComment("empty"), | ||
| value.whenFalse ? () => ensureNode(value.whenFalse()) ?? document.createComment("empty") : void 0 | ||
| ); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| if (isListDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createList(value.items, value.keyFn, value.renderFn, value.options); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function descriptorToElement(desc) { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const children = desc.children.map((child) => { | ||
| if (isDescriptor(child)) return descriptorToElement(child); | ||
| if (isShowDescriptor(child)) return ensureNode(child); | ||
| if (isListDescriptor(child)) return ensureNode(child); | ||
| return child; | ||
| }); | ||
| return h(desc.tag, desc.props, ...children); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| function isIslandStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 105; | ||
| } | ||
| function isShowStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 115; | ||
| } | ||
| function isTextStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 116; | ||
| } | ||
| function isListStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 108; | ||
| } | ||
| function findClosingMarker(start) { | ||
| const closing = "/" + start.data; | ||
| let node = start.nextSibling; | ||
| while (node) { | ||
| if (node.nodeType === 8 && node.data === closing) { | ||
| return node; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function findTextBetween(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 3) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function nextElementBetweenMarkers(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return void 0; | ||
| } | ||
| function extractContentBetweenMarkers(start, end) { | ||
| const frag = document.createDocumentFragment(); | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| const next = node.nextSibling; | ||
| frag.appendChild(node); | ||
| node = next; | ||
| } | ||
| return frag; | ||
| } | ||
| function setupShowEffect(desc, marker) { | ||
| let currentCondition = !!desc.condition(); | ||
| let thenFragment = null; | ||
| let elseFragment = null; | ||
| const hasSSRContent = marker.start.nextSibling !== marker.end; | ||
| if (!hasSSRContent && currentCondition) { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn("[forma] Hydration: show condition mismatch \u2014 SSR empty but client condition is true"); | ||
| const trueBranch = desc.whenTrue(); | ||
| if (trueBranch instanceof Node) { | ||
| marker.start.parentNode.insertBefore(trueBranch, marker.end); | ||
| } | ||
| } | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const next = !!desc.condition(); | ||
| if (next === currentCondition) return; | ||
| currentCondition = next; | ||
| const parent = marker.start.parentNode; | ||
| if (!parent) return; | ||
| const current = extractContentBetweenMarkers(marker.start, marker.end); | ||
| if (!next) { | ||
| thenFragment = current; | ||
| } else { | ||
| elseFragment = current; | ||
| } | ||
| let branch = next ? thenFragment ?? desc.whenTrue() : desc.whenFalse ? elseFragment ?? desc.whenFalse() : null; | ||
| if (next && thenFragment) thenFragment = null; | ||
| if (!next && elseFragment) elseFragment = null; | ||
| if (branch != null && !(branch instanceof Node)) { | ||
| branch = ensureNode(branch); | ||
| } | ||
| if (branch instanceof Node) { | ||
| parent.insertBefore(branch, marker.end); | ||
| } | ||
| }); | ||
| } | ||
| function adoptBranchContent(desc, regionStart, regionEnd) { | ||
| if (isDescriptor(desc)) { | ||
| const el = nextElementBetweenMarkers(regionStart, regionEnd); | ||
| if (el) adoptNode(desc, el); | ||
| } else if (isShowDescriptor(desc)) { | ||
| let node = regionStart.nextSibling; | ||
| while (node && node !== regionEnd) { | ||
| if (node.nodeType === 8 && isShowStart(node.data)) { | ||
| const innerStart = node; | ||
| const innerEnd = findClosingMarker(innerStart); | ||
| if (innerEnd) { | ||
| if (desc.initialBranch) { | ||
| adoptBranchContent(desc.initialBranch, innerStart, innerEnd); | ||
| } | ||
| setupShowEffect(desc, { start: innerStart, end: innerEnd}); | ||
| } | ||
| break; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| function adoptNode(desc, ssrEl) { | ||
| if (!ssrEl || ssrEl.tagName !== desc.tag.toUpperCase()) { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`Hydration mismatch: expected <${desc.tag}>, got <${ssrEl?.tagName?.toLowerCase() ?? "nothing"}>`); | ||
| const fresh = descriptorToElement(desc); | ||
| if (ssrEl) ssrEl.replaceWith(fresh); | ||
| return; | ||
| } | ||
| applyDynamicProps(ssrEl, desc.props); | ||
| let cursor = ssrEl.firstChild; | ||
| for (const child of desc.children) { | ||
| if (child === false || child == null) continue; | ||
| if (isDescriptor(child)) { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| while (cursor && cursor.nodeType === 1 && cursor.hasAttribute("data-forma-island")) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (!cursor) { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| continue; | ||
| } | ||
| if (cursor.nodeType === 1) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(child, el); | ||
| } else if (cursor.nodeType === 8 && isIslandStart(cursor.data)) { | ||
| const end = findClosingMarker(cursor); | ||
| const fresh = descriptorToElement(child); | ||
| if (end) { | ||
| end.parentNode.insertBefore(fresh, end); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| ssrEl.appendChild(fresh); | ||
| cursor = null; | ||
| } | ||
| } else { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| } | ||
| } else if (isShowDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isShowStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| if (child.initialBranch) { | ||
| adoptBranchContent(child.initialBranch, start, end); | ||
| } | ||
| setupShowEffect(child, { start, end}); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (isListDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isListStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| const ssrKeyMap = /* @__PURE__ */ new Map(); | ||
| const ssrElements = []; | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) { | ||
| const el = node; | ||
| ssrElements.push(el); | ||
| const key = el.getAttribute("data-forma-key"); | ||
| if (key != null) { | ||
| ssrKeyMap.set(key, el); | ||
| } | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| const currentItems = chunkBARF67I6_cjs.untrack(() => child.items()); | ||
| const listKeyFn = child.keyFn; | ||
| const listRenderFn = child.renderFn; | ||
| const useIndexFallback = ssrKeyMap.size === 0 && ssrElements.length > 0; | ||
| const adoptedNodes = []; | ||
| const adoptedItems = []; | ||
| const usedIndices = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < currentItems.length; i++) { | ||
| const item = currentItems[i]; | ||
| const key = listKeyFn(item); | ||
| let ssrNode; | ||
| if (useIndexFallback) { | ||
| if (i < ssrElements.length) { | ||
| ssrNode = ssrElements[i]; | ||
| usedIndices.add(i); | ||
| } | ||
| } else { | ||
| ssrNode = ssrKeyMap.get(String(key)); | ||
| if (ssrNode) ssrKeyMap.delete(String(key)); | ||
| } | ||
| if (ssrNode) { | ||
| adoptedNodes.push(ssrNode); | ||
| adoptedItems.push(item); | ||
| } else { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[FormaJS] Hydration: list item key "${key}" not found in SSR \u2014 rendering fresh`); | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const [getIndex] = chunkQLPCVK7C_cjs.createSignal(i); | ||
| const fresh = listRenderFn(item, getIndex); | ||
| end.parentNode.insertBefore(fresh, end); | ||
| adoptedNodes.push(fresh); | ||
| adoptedItems.push(item); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| } | ||
| if (useIndexFallback) { | ||
| for (let i = 0; i < ssrElements.length; i++) { | ||
| if (!usedIndices.has(i) && ssrElements[i].parentNode) { | ||
| ssrElements[i].parentNode.removeChild(ssrElements[i]); | ||
| } | ||
| } | ||
| } else { | ||
| for (const [unusedKey, unusedNode] of ssrKeyMap) { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[FormaJS] Hydration: removing extra SSR list item with key "${unusedKey}"`); | ||
| if (unusedNode.parentNode) { | ||
| unusedNode.parentNode.removeChild(unusedNode); | ||
| } | ||
| } | ||
| } | ||
| const parent = start.parentNode; | ||
| for (const adoptedNode of adoptedNodes) { | ||
| parent.insertBefore(adoptedNode, end); | ||
| } | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < adoptedItems.length; i++) { | ||
| const item = adoptedItems[i]; | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = chunkQLPCVK7C_cjs.createSignal(i); | ||
| cache.set(key, { | ||
| element: adoptedNodes[i], | ||
| item, | ||
| getIndex, | ||
| setIndex | ||
| }); | ||
| } | ||
| let reconcileNodes = adoptedNodes.slice(); | ||
| let reconcileItems = adoptedItems.slice(); | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const newItems = child.items(); | ||
| const parent2 = start.parentNode; | ||
| if (!parent2) return; | ||
| const result = reconcileList( | ||
| parent2, | ||
| reconcileItems, | ||
| newItems, | ||
| reconcileNodes, | ||
| listKeyFn, | ||
| (item) => { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = chunkQLPCVK7C_cjs.createSignal(0); | ||
| const element = chunkBARF67I6_cjs.untrack(() => listRenderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| }, | ||
| (_node, item) => { | ||
| const key = listKeyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }, | ||
| end | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| const key = listKeyFn(newItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| reconcileNodes = result.nodes; | ||
| reconcileItems = result.items; | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (typeof child === "function") { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor && cursor.nodeType === 1) { | ||
| const initial = child(); | ||
| if (isDescriptor(initial)) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(initial, el); | ||
| continue; | ||
| } | ||
| } | ||
| if (cursor && cursor.nodeType === 8) { | ||
| const data = cursor.data; | ||
| if (isTextStart(data)) { | ||
| const endMarker = findClosingMarker(cursor); | ||
| let textNode = cursor.nextSibling; | ||
| if (!textNode || textNode.nodeType !== 3) { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node for marker ${data} \u2014 SSR walker should emit content between markers`); | ||
| const created = document.createTextNode(""); | ||
| cursor.parentNode.insertBefore(created, endMarker || cursor.nextSibling); | ||
| textNode = created; | ||
| } | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = endMarker ? endMarker.nextSibling : textNode.nextSibling; | ||
| } else if (isShowStart(data)) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| let textNode = findTextBetween(start, end); | ||
| if (!textNode) { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node for show marker ${start.data} \u2014 SSR walker should emit content between markers`); | ||
| textNode = document.createTextNode(""); | ||
| start.parentNode.insertBefore(textNode, end); | ||
| } | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else if (cursor && cursor.nodeType === 3) { | ||
| const textNode = cursor; | ||
| cursor = cursor.nextSibling; | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } else { | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node in empty <${ssrEl.tagName.toLowerCase()}> \u2014 IR may not cover this component`); | ||
| const textNode = document.createTextNode(""); | ||
| ssrEl.appendChild(textNode); | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } | ||
| } else if (typeof child === "string" || typeof child === "number") { | ||
| if (cursor && cursor.nodeType === 3) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function hydrateIsland(component, target) { | ||
| const hasSSRContent = target.childElementCount > 0 || target.childNodes.length > 0 && Array.from(target.childNodes).some((n) => n.nodeType === 1 || n.nodeType === 3 && n.data.trim()); | ||
| if (!hasSSRContent) { | ||
| if (chunkBARF67I6_cjs.__DEV__) { | ||
| const name = target.getAttribute("data-forma-component") || "unknown"; | ||
| console.warn( | ||
| `[forma] Island "${name}" has no SSR content \u2014 falling back to CSR. This means the IR walker did not render content between ISLAND_START and ISLAND_END.` | ||
| ); | ||
| } | ||
| const result = component(); | ||
| if (result instanceof Element) { | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (attr.name.startsWith("data-forma-")) { | ||
| result.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| target.replaceWith(result); | ||
| return result; | ||
| } else if (result instanceof Node) { | ||
| target.appendChild(result); | ||
| } | ||
| return target; | ||
| } | ||
| setHydrating(true); | ||
| let descriptor; | ||
| try { | ||
| descriptor = component(); | ||
| } finally { | ||
| setHydrating(false); | ||
| } | ||
| if (!descriptor || !isDescriptor(descriptor)) { | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| if (target.hasAttribute("data-forma-island")) { | ||
| adoptNode(descriptor, target); | ||
| } else { | ||
| adoptNode(descriptor, target.children[0]); | ||
| } | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| // src/dom/list.ts | ||
| function longestIncreasingSubsequence(arr) { | ||
| const n = arr.length; | ||
| if (n === 0) return []; | ||
| const tails = new Int32Array(n); | ||
| const tailIndices = new Int32Array(n); | ||
| const predecessor = new Int32Array(n).fill(-1); | ||
| let tailsLen = 0; | ||
| for (let i = 0; i < n; i++) { | ||
| const val = arr[i]; | ||
| let lo = 0, hi = tailsLen; | ||
| while (lo < hi) { | ||
| const mid = lo + hi >> 1; | ||
| if (tails[mid] < val) lo = mid + 1; | ||
| else hi = mid; | ||
| } | ||
| tails[lo] = val; | ||
| tailIndices[lo] = i; | ||
| if (lo > 0) predecessor[i] = tailIndices[lo - 1]; | ||
| if (lo >= tailsLen) tailsLen++; | ||
| } | ||
| const result = new Array(tailsLen); | ||
| let idx = tailIndices[tailsLen - 1]; | ||
| for (let i = tailsLen - 1; i >= 0; i--) { | ||
| result[i] = idx; | ||
| idx = predecessor[idx]; | ||
| } | ||
| return result; | ||
| } | ||
| var SMALL_LIST_THRESHOLD = 32; | ||
| var ABORT_SYM3 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var CACHE_SYM2 = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM2 = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function canPatchStaticElement(target, source) { | ||
| return target instanceof HTMLElement && source instanceof HTMLElement && target.tagName === source.tagName && !target[ABORT_SYM3] && !target[CACHE_SYM2] && !target[DYNAMIC_CHILD_SYM2] && !source[ABORT_SYM3] && !source[CACHE_SYM2] && !source[DYNAMIC_CHILD_SYM2]; | ||
| } | ||
| function patchStaticElement(target, source) { | ||
| const sourceAttrNames = /* @__PURE__ */ new Set(); | ||
| for (const attr of Array.from(source.attributes)) { | ||
| sourceAttrNames.add(attr.name); | ||
| if (target.getAttribute(attr.name) !== attr.value) { | ||
| target.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (!sourceAttrNames.has(attr.name)) { | ||
| target.removeAttribute(attr.name); | ||
| } | ||
| } | ||
| target.replaceChildren(...Array.from(source.childNodes)); | ||
| } | ||
| function reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| const oldKeys = new Array(oldLen); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeys[i] = keyFn(oldItems[i]); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| let found = -1; | ||
| for (let j = 0; j < oldLen; j++) { | ||
| if (!oldUsed[j] && oldKeys[j] === key) { | ||
| found = j; | ||
| oldUsed[j] = 1; | ||
| break; | ||
| } | ||
| } | ||
| oldIndices[i] = found; | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function reconcileList(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| if (newLen === 0) { | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| return { nodes: [], items: [] }; | ||
| } | ||
| if (oldLen === 0) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = createFn(newItems[i]); | ||
| if (beforeNode) { | ||
| parent.insertBefore(node, beforeNode); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| hooks?.onInsert?.(node); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| if (oldLen < SMALL_LIST_THRESHOLD) { | ||
| return reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks); | ||
| } | ||
| const oldKeyMap = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeyMap.set(keyFn(oldItems[i]), i); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| const oldIdx = oldKeyMap.get(key); | ||
| if (oldIdx !== void 0) { | ||
| oldIndices[i] = oldIdx; | ||
| oldUsed[oldIdx] = 1; | ||
| } else { | ||
| oldIndices[i] = -1; | ||
| } | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function createList(items, keyFn, renderFn, options) { | ||
| if (hydrating) { | ||
| return { type: "list", items, keyFn, renderFn, options }; | ||
| } | ||
| const startMarker = document.createComment("forma-list-start"); | ||
| const endMarker = document.createComment("forma-list-end"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| let currentNodes = []; | ||
| let currentItems = []; | ||
| const updateOnItemChange = options?.updateOnItemChange ?? "none"; | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const newItems = items(); | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(newItems)) { | ||
| if (chunkBARF67I6_cjs.__DEV__) { | ||
| console.warn("[forma] createList: value is not an array, treating as empty"); | ||
| } | ||
| for (const node of currentNodes) { | ||
| if (node.parentNode === parent) parent.removeChild(node); | ||
| } | ||
| cache = /* @__PURE__ */ new Map(); | ||
| currentNodes = []; | ||
| currentItems = []; | ||
| return; | ||
| } | ||
| let cleanItems = newItems; | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| if (newItems[i] == null) { | ||
| cleanItems = newItems.filter((item) => item != null); | ||
| break; | ||
| } | ||
| } | ||
| if (chunkBARF67I6_cjs.__DEV__) { | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const item of cleanItems) { | ||
| const key = keyFn(item); | ||
| if (seen.has(key)) { | ||
| console.warn("[forma] createList: duplicate key detected:", key); | ||
| } | ||
| seen.add(key); | ||
| } | ||
| } | ||
| const updateRow = updateOnItemChange === "rerender" ? (node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (!cached) return; | ||
| if (cached.item === item) return; | ||
| cached.item = item; | ||
| if (!(node instanceof HTMLElement)) return; | ||
| if (node[ABORT_SYM3] || node[CACHE_SYM2] || node[DYNAMIC_CHILD_SYM2]) { | ||
| return; | ||
| } | ||
| const next = chunkBARF67I6_cjs.untrack(() => renderFn(item, cached.getIndex)); | ||
| if (canPatchStaticElement(node, next)) { | ||
| patchStaticElement(node, next); | ||
| cached.element = node; | ||
| } | ||
| } : (_node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }; | ||
| const result = reconcileList( | ||
| parent, | ||
| currentItems, | ||
| cleanItems, | ||
| currentNodes, | ||
| keyFn, | ||
| // createFn: create element + cache entry | ||
| (item) => { | ||
| const key = keyFn(item); | ||
| const [getIndex, setIndex] = chunkQLPCVK7C_cjs.createSignal(0); | ||
| const element = chunkBARF67I6_cjs.untrack(() => renderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| }, | ||
| updateRow, | ||
| // beforeNode: insert items before the end marker | ||
| endMarker | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < cleanItems.length; i++) { | ||
| const key = keyFn(cleanItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| currentNodes = result.nodes; | ||
| currentItems = result.items; | ||
| }); | ||
| return fragment2; | ||
| } | ||
| exports.Fragment = Fragment; | ||
| exports.cleanup = cleanup; | ||
| exports.createList = createList; | ||
| exports.createShow = createShow; | ||
| exports.fragment = fragment; | ||
| exports.h = h; | ||
| exports.hydrateIsland = hydrateIsland; | ||
| exports.reconcileList = reconcileList; | ||
| //# sourceMappingURL=chunk-7L3KHGEA.cjs.map | ||
| //# sourceMappingURL=chunk-7L3KHGEA.cjs.map |
Sorry, the diff of this file is too big to display
| import { internalEffect, createRoot, untrack, __DEV__ } from './chunk-TBWZZ3SI.js'; | ||
| import { createSignal } from './chunk-HLM5BZZQ.js'; | ||
| // src/dom/element.ts | ||
| var Fragment = /* @__PURE__ */ Symbol.for("forma.fragment"); | ||
| var SVG_NS = "http://www.w3.org/2000/svg"; | ||
| var XLINK_NS = "http://www.w3.org/1999/xlink"; | ||
| var SVG_TAGS = /* @__PURE__ */ new Set([ | ||
| "svg", | ||
| "path", | ||
| "circle", | ||
| "rect", | ||
| "line", | ||
| "polyline", | ||
| "polygon", | ||
| "ellipse", | ||
| "g", | ||
| "text", | ||
| "tspan", | ||
| "textPath", | ||
| "defs", | ||
| "use", | ||
| "symbol", | ||
| "clipPath", | ||
| "mask", | ||
| "pattern", | ||
| "marker", | ||
| "linearGradient", | ||
| "radialGradient", | ||
| "stop", | ||
| "filter", | ||
| "feGaussianBlur", | ||
| "feColorMatrix", | ||
| "feOffset", | ||
| "feBlend", | ||
| "feMerge", | ||
| "feMergeNode", | ||
| "feComposite", | ||
| "feFlood", | ||
| "feMorphology", | ||
| "feTurbulence", | ||
| "feDisplacementMap", | ||
| "feImage", | ||
| "foreignObject", | ||
| "animate", | ||
| "animateTransform", | ||
| "animateMotion", | ||
| "set", | ||
| "image", | ||
| "switch", | ||
| "desc", | ||
| "title", | ||
| "metadata" | ||
| ]); | ||
| var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([ | ||
| "disabled", | ||
| "checked", | ||
| "readonly", | ||
| "required", | ||
| "autofocus", | ||
| "autoplay", | ||
| "controls", | ||
| "default", | ||
| "defer", | ||
| "formnovalidate", | ||
| "hidden", | ||
| "ismap", | ||
| "loop", | ||
| "multiple", | ||
| "muted", | ||
| "nomodule", | ||
| "novalidate", | ||
| "open", | ||
| "playsinline", | ||
| "reversed", | ||
| "selected", | ||
| "async" | ||
| ]); | ||
| var ELEMENT_PROTOS = null; | ||
| function getProto(tag) { | ||
| if (!ELEMENT_PROTOS) { | ||
| ELEMENT_PROTOS = /* @__PURE__ */ Object.create(null); | ||
| for (const t of [ | ||
| "div", | ||
| "span", | ||
| "p", | ||
| "a", | ||
| "li", | ||
| "ul", | ||
| "ol", | ||
| "button", | ||
| "input", | ||
| "label", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "section", | ||
| "header", | ||
| "footer", | ||
| "main", | ||
| "nav", | ||
| "table", | ||
| "tr", | ||
| "td", | ||
| "th", | ||
| "tbody", | ||
| "img", | ||
| "form", | ||
| "select", | ||
| "option", | ||
| "textarea", | ||
| "i", | ||
| "b", | ||
| "strong", | ||
| "em", | ||
| "small", | ||
| "article", | ||
| "aside", | ||
| "details", | ||
| "summary" | ||
| ]) { | ||
| ELEMENT_PROTOS[t] = document.createElement(t); | ||
| } | ||
| } | ||
| return ELEMENT_PROTOS[tag] ?? (ELEMENT_PROTOS[tag] = document.createElement(tag)); | ||
| } | ||
| var EVENT_NAMES = /* @__PURE__ */ Object.create(null); | ||
| function eventName(key) { | ||
| return EVENT_NAMES[key] ?? (EVENT_NAMES[key] = key.slice(2).toLowerCase()); | ||
| } | ||
| var ABORT_SYM = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| function getAbortController(el) { | ||
| let controller = el[ABORT_SYM]; | ||
| if (!controller) { | ||
| controller = new AbortController(); | ||
| el[ABORT_SYM] = controller; | ||
| } | ||
| return controller; | ||
| } | ||
| function cleanup(el) { | ||
| const controller = el[ABORT_SYM]; | ||
| if (controller) { | ||
| controller.abort(); | ||
| delete el[ABORT_SYM]; | ||
| } | ||
| } | ||
| var CACHE_SYM = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function getCache(el) { | ||
| return el[CACHE_SYM] ?? (el[CACHE_SYM] = /* @__PURE__ */ Object.create(null)); | ||
| } | ||
| function handleClass(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache["class"] === v) return; | ||
| cache["class"] = v; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = v; | ||
| } else { | ||
| el.setAttribute("class", v); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache["class"] === value) return; | ||
| cache["class"] = value; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| } | ||
| } | ||
| function handleStyle(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| let prevKeys = []; | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (typeof v === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === v) return; | ||
| cache["style"] = v; | ||
| prevKeys = []; | ||
| el.style.cssText = v; | ||
| } else if (v && typeof v === "object") { | ||
| const style = el.style; | ||
| const nextKeys = Object.keys(v); | ||
| for (const k of prevKeys) { | ||
| if (!(k in v)) { | ||
| style.removeProperty(k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())); | ||
| } | ||
| } | ||
| Object.assign(style, v); | ||
| prevKeys = nextKeys; | ||
| } | ||
| }); | ||
| } else if (typeof value === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === value) return; | ||
| cache["style"] = value; | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| } | ||
| function handleEvent(el, key, value) { | ||
| const controller = getAbortController(el); | ||
| el.addEventListener( | ||
| eventName(key), | ||
| value, | ||
| { signal: controller.signal } | ||
| ); | ||
| } | ||
| function handleInnerHTML(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const resolved = value(); | ||
| if (resolved == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof resolved !== "object" || !("__html" in resolved)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof resolved | ||
| ); | ||
| } | ||
| const html = resolved.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| const cache = getCache(el); | ||
| if (cache["innerHTML"] === html) return; | ||
| cache["innerHTML"] = html; | ||
| el.innerHTML = html; | ||
| }); | ||
| } else { | ||
| if (value == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| } | ||
| } | ||
| function handleXLink(el, key, value) { | ||
| const localName = key.slice(6); | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(v)); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| } | ||
| } | ||
| } | ||
| function handleBooleanAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache[key] === v) return; | ||
| cache[key] = v; | ||
| if (v) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache[key] === value) return; | ||
| cache[key] = value; | ||
| if (value) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| } | ||
| } | ||
| function handleGenericAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(v); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(value); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| } | ||
| } | ||
| var PROP_HANDLERS = /* @__PURE__ */ new Map(); | ||
| PROP_HANDLERS.set("class", handleClass); | ||
| PROP_HANDLERS.set("className", handleClass); | ||
| PROP_HANDLERS.set("style", handleStyle); | ||
| PROP_HANDLERS.set("ref", () => { | ||
| }); | ||
| PROP_HANDLERS.set("dangerouslySetInnerHTML", handleInnerHTML); | ||
| for (const attr of BOOLEAN_ATTRS) { | ||
| PROP_HANDLERS.set(attr, handleBooleanAttr); | ||
| } | ||
| function applyProp(el, key, value) { | ||
| if (key === "class") { | ||
| handleClass(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| return; | ||
| } | ||
| const handler = PROP_HANDLERS.get(key); | ||
| if (handler) { | ||
| handler(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| handleXLink(el, key, value); | ||
| return; | ||
| } | ||
| handleGenericAttr(el, key, value); | ||
| } | ||
| function applyStaticProp(el, key, value) { | ||
| if (value == null || value === false) return; | ||
| if (key === "class" || key === "className") { | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "style") { | ||
| if (typeof value === "string") { | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "dangerouslySetInnerHTML") { | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| return; | ||
| } | ||
| if (BOOLEAN_ATTRS.has(key)) { | ||
| if (value) el.setAttribute(key, ""); | ||
| return; | ||
| } | ||
| if (value === true) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.setAttribute(key, String(value)); | ||
| } | ||
| } | ||
| function appendChild(parent, child) { | ||
| if (child instanceof Node) { | ||
| parent.appendChild(child); | ||
| return; | ||
| } | ||
| if (typeof child === "string") { | ||
| parent.appendChild(new Text(child)); | ||
| return; | ||
| } | ||
| if (child == null || child === false || child === true) { | ||
| return; | ||
| } | ||
| if (typeof child === "number") { | ||
| parent.appendChild(new Text(String(child))); | ||
| return; | ||
| } | ||
| if (typeof child === "function") { | ||
| if (parent instanceof Element) { | ||
| parent[DYNAMIC_CHILD_SYM] = true; | ||
| } | ||
| let currentNode = null; | ||
| let currentFragChildren = null; | ||
| let warnedArray = false; | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const clearCurrent = () => { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| if (currentNode && currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } | ||
| currentNode = null; | ||
| }; | ||
| internalEffect(() => { | ||
| const v = child(); | ||
| let resolved = v; | ||
| if (Array.isArray(v)) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const item of v) { | ||
| if (item instanceof Node) frag.appendChild(item); | ||
| else if (Array.isArray(item)) { | ||
| if (DEBUG) console.warn("[forma] Nested arrays in function children are not supported. Flatten the array or use createList()."); | ||
| } else if (item != null && item !== false && item !== true) { | ||
| frag.appendChild(new Text(String(item))); | ||
| } | ||
| } | ||
| resolved = frag.childNodes.length > 0 ? frag : null; | ||
| if (DEBUG && !warnedArray) { | ||
| warnedArray = true; | ||
| console.warn("[forma] Function child returned an array \u2014 auto-wrapped in DocumentFragment. Consider using createList() or wrapping in a container element for better performance."); | ||
| } | ||
| } | ||
| if (resolved instanceof Node) { | ||
| clearCurrent(); | ||
| const isNewFrag = resolved instanceof DocumentFragment; | ||
| if (isNewFrag) { | ||
| currentFragChildren = Array.from(resolved.childNodes); | ||
| } | ||
| parent.appendChild(resolved); | ||
| currentNode = isNewFrag ? null : resolved; | ||
| } else if (resolved == null || resolved === false || resolved === true) { | ||
| clearCurrent(); | ||
| } else { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| const text = typeof resolved === "symbol" ? String(resolved) : String(resolved ?? ""); | ||
| if (!currentNode) { | ||
| currentNode = new Text(text); | ||
| parent.appendChild(currentNode); | ||
| } else if (currentNode.nodeType === 3) { | ||
| currentNode.data = text; | ||
| } else { | ||
| const tn = new Text(text); | ||
| parent.replaceChild(tn, currentNode); | ||
| currentNode = tn; | ||
| } | ||
| } | ||
| }); | ||
| return; | ||
| } | ||
| if (Array.isArray(child)) { | ||
| for (const item of child) { | ||
| appendChild(parent, item); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| function h(tag, props, ...children) { | ||
| if (typeof tag === "function" && tag !== Fragment) { | ||
| const mergedProps = { ...props ?? {}, children }; | ||
| return tag(mergedProps); | ||
| } | ||
| if (tag === Fragment) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| const tagName = tag; | ||
| if (hydrating) { | ||
| return { type: "element", tag: tagName, props: props ?? null, children }; | ||
| } | ||
| let el; | ||
| if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) { | ||
| el = ELEMENT_PROTOS[tagName].cloneNode(false); | ||
| } else if (SVG_TAGS.has(tagName)) { | ||
| el = document.createElementNS(SVG_NS, tagName); | ||
| } else { | ||
| el = getProto(tagName).cloneNode(false); | ||
| } | ||
| if (props) { | ||
| let hasDynamic = false; | ||
| for (const key in props) { | ||
| if (key === "ref") continue; | ||
| const value = props[key]; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| continue; | ||
| } | ||
| if (typeof value === "function") { | ||
| if (!hasDynamic) { | ||
| el[CACHE_SYM] = /* @__PURE__ */ Object.create(null); | ||
| hasDynamic = true; | ||
| } | ||
| applyProp(el, key, value); | ||
| continue; | ||
| } | ||
| applyStaticProp(el, key, value); | ||
| } | ||
| } | ||
| const childLen = children.length; | ||
| if (childLen === 1) { | ||
| const only = children[0]; | ||
| if (typeof only === "string") { | ||
| el.textContent = only; | ||
| } else if (typeof only === "number") { | ||
| el.textContent = String(only); | ||
| } else { | ||
| appendChild(el, only); | ||
| } | ||
| } else if (childLen > 1) { | ||
| for (const child of children) { | ||
| appendChild(el, child); | ||
| } | ||
| } | ||
| if (props && typeof props["ref"] === "function") { | ||
| props["ref"](el); | ||
| } | ||
| return el; | ||
| } | ||
| function fragment(...children) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| // src/dom/show.ts | ||
| function createShow(when, thenFn, elseFn = () => null) { | ||
| if (hydrating) { | ||
| const branch = when() ? thenFn() : elseFn(); | ||
| return { | ||
| type: "show", | ||
| condition: when, | ||
| whenTrue: thenFn, | ||
| whenFalse: elseFn, | ||
| initialBranch: branch | ||
| }; | ||
| } | ||
| const startMarker = document.createComment("forma-show"); | ||
| const endMarker = document.createComment("/forma-show"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let currentNode = null; | ||
| let lastTruthy = null; | ||
| let currentDispose = null; | ||
| const showDispose = internalEffect(() => { | ||
| const truthy = !!when(); | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const DEBUG_LABEL = DEBUG ? thenFn.toString().slice(0, 60) : ""; | ||
| if (truthy === lastTruthy) { | ||
| if (DEBUG) console.log("[forma:show] skip (same)", truthy, DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show]", lastTruthy, "\u2192", truthy, DEBUG_LABEL); | ||
| lastTruthy = truthy; | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| if (DEBUG) console.warn("[forma:show] parentNode is null! skipping.", DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show] parent:", parent.nodeName, "inDoc:", document.contains(parent)); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| if (currentNode) { | ||
| if (currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } else { | ||
| while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) { | ||
| parent.removeChild(startMarker.nextSibling); | ||
| } | ||
| } | ||
| } | ||
| const branchFn = truthy ? thenFn : elseFn; | ||
| if (branchFn) { | ||
| let branchDispose; | ||
| currentNode = createRoot((dispose) => { | ||
| branchDispose = dispose; | ||
| return untrack(() => branchFn()); | ||
| }); | ||
| currentDispose = branchDispose; | ||
| } else { | ||
| currentNode = null; | ||
| } | ||
| if (currentNode) { | ||
| parent.insertBefore(currentNode, endMarker); | ||
| } | ||
| }); | ||
| fragment2.__showDispose = () => { | ||
| showDispose(); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| }; | ||
| return fragment2; | ||
| } | ||
| // src/dom/hydrate.ts | ||
| var ABORT_SYM2 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var hydrating = false; | ||
| function setHydrating(value) { | ||
| hydrating = value; | ||
| } | ||
| function isDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "element"; | ||
| } | ||
| function isShowDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "show"; | ||
| } | ||
| function isListDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "list"; | ||
| } | ||
| function applyDynamicProps(el, props) { | ||
| if (!props) return; | ||
| for (const key in props) { | ||
| const value = props[key]; | ||
| if (typeof value !== "function") continue; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| let ac = el[ABORT_SYM2]; | ||
| if (!ac) { | ||
| ac = new AbortController(); | ||
| el[ABORT_SYM2] = ac; | ||
| } | ||
| el.addEventListener(key.slice(2).toLowerCase(), value, { signal: ac.signal }); | ||
| continue; | ||
| } | ||
| const fn = value; | ||
| const attrKey = key; | ||
| internalEffect(() => { | ||
| const v = fn(); | ||
| if (v === false || v == null) { | ||
| el.removeAttribute(attrKey); | ||
| } else if (v === true) { | ||
| el.setAttribute(attrKey, ""); | ||
| } else { | ||
| el.setAttribute(attrKey, String(v)); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function ensureNode(value) { | ||
| if (value instanceof Node) return value; | ||
| if (value == null || value === false || value === true) return null; | ||
| if (typeof value === "string") return new Text(value); | ||
| if (typeof value === "number") return new Text(String(value)); | ||
| if (isDescriptor(value)) return descriptorToElement(value); | ||
| if (isShowDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createShow( | ||
| value.condition, | ||
| () => ensureNode(value.whenTrue()) ?? document.createComment("empty"), | ||
| value.whenFalse ? () => ensureNode(value.whenFalse()) ?? document.createComment("empty") : void 0 | ||
| ); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| if (isListDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createList(value.items, value.keyFn, value.renderFn, value.options); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function descriptorToElement(desc) { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const children = desc.children.map((child) => { | ||
| if (isDescriptor(child)) return descriptorToElement(child); | ||
| if (isShowDescriptor(child)) return ensureNode(child); | ||
| if (isListDescriptor(child)) return ensureNode(child); | ||
| return child; | ||
| }); | ||
| return h(desc.tag, desc.props, ...children); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| function isIslandStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 105; | ||
| } | ||
| function isShowStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 115; | ||
| } | ||
| function isTextStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 116; | ||
| } | ||
| function isListStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 108; | ||
| } | ||
| function findClosingMarker(start) { | ||
| const closing = "/" + start.data; | ||
| let node = start.nextSibling; | ||
| while (node) { | ||
| if (node.nodeType === 8 && node.data === closing) { | ||
| return node; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function findTextBetween(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 3) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function nextElementBetweenMarkers(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return void 0; | ||
| } | ||
| function extractContentBetweenMarkers(start, end) { | ||
| const frag = document.createDocumentFragment(); | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| const next = node.nextSibling; | ||
| frag.appendChild(node); | ||
| node = next; | ||
| } | ||
| return frag; | ||
| } | ||
| function setupShowEffect(desc, marker) { | ||
| let currentCondition = !!desc.condition(); | ||
| let thenFragment = null; | ||
| let elseFragment = null; | ||
| const hasSSRContent = marker.start.nextSibling !== marker.end; | ||
| if (!hasSSRContent && currentCondition) { | ||
| if (__DEV__) console.warn("[forma] Hydration: show condition mismatch \u2014 SSR empty but client condition is true"); | ||
| const trueBranch = desc.whenTrue(); | ||
| if (trueBranch instanceof Node) { | ||
| marker.start.parentNode.insertBefore(trueBranch, marker.end); | ||
| } | ||
| } | ||
| internalEffect(() => { | ||
| const next = !!desc.condition(); | ||
| if (next === currentCondition) return; | ||
| currentCondition = next; | ||
| const parent = marker.start.parentNode; | ||
| if (!parent) return; | ||
| const current = extractContentBetweenMarkers(marker.start, marker.end); | ||
| if (!next) { | ||
| thenFragment = current; | ||
| } else { | ||
| elseFragment = current; | ||
| } | ||
| let branch = next ? thenFragment ?? desc.whenTrue() : desc.whenFalse ? elseFragment ?? desc.whenFalse() : null; | ||
| if (next && thenFragment) thenFragment = null; | ||
| if (!next && elseFragment) elseFragment = null; | ||
| if (branch != null && !(branch instanceof Node)) { | ||
| branch = ensureNode(branch); | ||
| } | ||
| if (branch instanceof Node) { | ||
| parent.insertBefore(branch, marker.end); | ||
| } | ||
| }); | ||
| } | ||
| function adoptBranchContent(desc, regionStart, regionEnd) { | ||
| if (isDescriptor(desc)) { | ||
| const el = nextElementBetweenMarkers(regionStart, regionEnd); | ||
| if (el) adoptNode(desc, el); | ||
| } else if (isShowDescriptor(desc)) { | ||
| let node = regionStart.nextSibling; | ||
| while (node && node !== regionEnd) { | ||
| if (node.nodeType === 8 && isShowStart(node.data)) { | ||
| const innerStart = node; | ||
| const innerEnd = findClosingMarker(innerStart); | ||
| if (innerEnd) { | ||
| if (desc.initialBranch) { | ||
| adoptBranchContent(desc.initialBranch, innerStart, innerEnd); | ||
| } | ||
| setupShowEffect(desc, { start: innerStart, end: innerEnd}); | ||
| } | ||
| break; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| function adoptNode(desc, ssrEl) { | ||
| if (!ssrEl || ssrEl.tagName !== desc.tag.toUpperCase()) { | ||
| if (__DEV__) console.warn(`Hydration mismatch: expected <${desc.tag}>, got <${ssrEl?.tagName?.toLowerCase() ?? "nothing"}>`); | ||
| const fresh = descriptorToElement(desc); | ||
| if (ssrEl) ssrEl.replaceWith(fresh); | ||
| return; | ||
| } | ||
| applyDynamicProps(ssrEl, desc.props); | ||
| let cursor = ssrEl.firstChild; | ||
| for (const child of desc.children) { | ||
| if (child === false || child == null) continue; | ||
| if (isDescriptor(child)) { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| while (cursor && cursor.nodeType === 1 && cursor.hasAttribute("data-forma-island")) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (!cursor) { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| continue; | ||
| } | ||
| if (cursor.nodeType === 1) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(child, el); | ||
| } else if (cursor.nodeType === 8 && isIslandStart(cursor.data)) { | ||
| const end = findClosingMarker(cursor); | ||
| const fresh = descriptorToElement(child); | ||
| if (end) { | ||
| end.parentNode.insertBefore(fresh, end); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| ssrEl.appendChild(fresh); | ||
| cursor = null; | ||
| } | ||
| } else { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| } | ||
| } else if (isShowDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isShowStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| if (child.initialBranch) { | ||
| adoptBranchContent(child.initialBranch, start, end); | ||
| } | ||
| setupShowEffect(child, { start, end}); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (isListDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isListStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| const ssrKeyMap = /* @__PURE__ */ new Map(); | ||
| const ssrElements = []; | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) { | ||
| const el = node; | ||
| ssrElements.push(el); | ||
| const key = el.getAttribute("data-forma-key"); | ||
| if (key != null) { | ||
| ssrKeyMap.set(key, el); | ||
| } | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| const currentItems = untrack(() => child.items()); | ||
| const listKeyFn = child.keyFn; | ||
| const listRenderFn = child.renderFn; | ||
| const useIndexFallback = ssrKeyMap.size === 0 && ssrElements.length > 0; | ||
| const adoptedNodes = []; | ||
| const adoptedItems = []; | ||
| const usedIndices = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < currentItems.length; i++) { | ||
| const item = currentItems[i]; | ||
| const key = listKeyFn(item); | ||
| let ssrNode; | ||
| if (useIndexFallback) { | ||
| if (i < ssrElements.length) { | ||
| ssrNode = ssrElements[i]; | ||
| usedIndices.add(i); | ||
| } | ||
| } else { | ||
| ssrNode = ssrKeyMap.get(String(key)); | ||
| if (ssrNode) ssrKeyMap.delete(String(key)); | ||
| } | ||
| if (ssrNode) { | ||
| adoptedNodes.push(ssrNode); | ||
| adoptedItems.push(item); | ||
| } else { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: list item key "${key}" not found in SSR \u2014 rendering fresh`); | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const [getIndex] = createSignal(i); | ||
| const fresh = listRenderFn(item, getIndex); | ||
| end.parentNode.insertBefore(fresh, end); | ||
| adoptedNodes.push(fresh); | ||
| adoptedItems.push(item); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| } | ||
| if (useIndexFallback) { | ||
| for (let i = 0; i < ssrElements.length; i++) { | ||
| if (!usedIndices.has(i) && ssrElements[i].parentNode) { | ||
| ssrElements[i].parentNode.removeChild(ssrElements[i]); | ||
| } | ||
| } | ||
| } else { | ||
| for (const [unusedKey, unusedNode] of ssrKeyMap) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: removing extra SSR list item with key "${unusedKey}"`); | ||
| if (unusedNode.parentNode) { | ||
| unusedNode.parentNode.removeChild(unusedNode); | ||
| } | ||
| } | ||
| } | ||
| const parent = start.parentNode; | ||
| for (const adoptedNode of adoptedNodes) { | ||
| parent.insertBefore(adoptedNode, end); | ||
| } | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < adoptedItems.length; i++) { | ||
| const item = adoptedItems[i]; | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = createSignal(i); | ||
| cache.set(key, { | ||
| element: adoptedNodes[i], | ||
| item, | ||
| getIndex, | ||
| setIndex | ||
| }); | ||
| } | ||
| let reconcileNodes = adoptedNodes.slice(); | ||
| let reconcileItems = adoptedItems.slice(); | ||
| internalEffect(() => { | ||
| const newItems = child.items(); | ||
| const parent2 = start.parentNode; | ||
| if (!parent2) return; | ||
| const result = reconcileList( | ||
| parent2, | ||
| reconcileItems, | ||
| newItems, | ||
| reconcileNodes, | ||
| listKeyFn, | ||
| (item) => { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = createSignal(0); | ||
| const element = untrack(() => listRenderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| }, | ||
| (_node, item) => { | ||
| const key = listKeyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }, | ||
| end | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| const key = listKeyFn(newItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| reconcileNodes = result.nodes; | ||
| reconcileItems = result.items; | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (typeof child === "function") { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor && cursor.nodeType === 1) { | ||
| const initial = child(); | ||
| if (isDescriptor(initial)) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(initial, el); | ||
| continue; | ||
| } | ||
| } | ||
| if (cursor && cursor.nodeType === 8) { | ||
| const data = cursor.data; | ||
| if (isTextStart(data)) { | ||
| const endMarker = findClosingMarker(cursor); | ||
| let textNode = cursor.nextSibling; | ||
| if (!textNode || textNode.nodeType !== 3) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node for marker ${data} \u2014 SSR walker should emit content between markers`); | ||
| const created = document.createTextNode(""); | ||
| cursor.parentNode.insertBefore(created, endMarker || cursor.nextSibling); | ||
| textNode = created; | ||
| } | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = endMarker ? endMarker.nextSibling : textNode.nextSibling; | ||
| } else if (isShowStart(data)) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| let textNode = findTextBetween(start, end); | ||
| if (!textNode) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node for show marker ${start.data} \u2014 SSR walker should emit content between markers`); | ||
| textNode = document.createTextNode(""); | ||
| start.parentNode.insertBefore(textNode, end); | ||
| } | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else if (cursor && cursor.nodeType === 3) { | ||
| const textNode = cursor; | ||
| cursor = cursor.nextSibling; | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } else { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node in empty <${ssrEl.tagName.toLowerCase()}> \u2014 IR may not cover this component`); | ||
| const textNode = document.createTextNode(""); | ||
| ssrEl.appendChild(textNode); | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } | ||
| } else if (typeof child === "string" || typeof child === "number") { | ||
| if (cursor && cursor.nodeType === 3) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function hydrateIsland(component, target) { | ||
| const hasSSRContent = target.childElementCount > 0 || target.childNodes.length > 0 && Array.from(target.childNodes).some((n) => n.nodeType === 1 || n.nodeType === 3 && n.data.trim()); | ||
| if (!hasSSRContent) { | ||
| if (__DEV__) { | ||
| const name = target.getAttribute("data-forma-component") || "unknown"; | ||
| console.warn( | ||
| `[forma] Island "${name}" has no SSR content \u2014 falling back to CSR. This means the IR walker did not render content between ISLAND_START and ISLAND_END.` | ||
| ); | ||
| } | ||
| const result = component(); | ||
| if (result instanceof Element) { | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (attr.name.startsWith("data-forma-")) { | ||
| result.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| target.replaceWith(result); | ||
| return result; | ||
| } else if (result instanceof Node) { | ||
| target.appendChild(result); | ||
| } | ||
| return target; | ||
| } | ||
| setHydrating(true); | ||
| let descriptor; | ||
| try { | ||
| descriptor = component(); | ||
| } finally { | ||
| setHydrating(false); | ||
| } | ||
| if (!descriptor || !isDescriptor(descriptor)) { | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| if (target.hasAttribute("data-forma-island")) { | ||
| adoptNode(descriptor, target); | ||
| } else { | ||
| adoptNode(descriptor, target.children[0]); | ||
| } | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| // src/dom/list.ts | ||
| function longestIncreasingSubsequence(arr) { | ||
| const n = arr.length; | ||
| if (n === 0) return []; | ||
| const tails = new Int32Array(n); | ||
| const tailIndices = new Int32Array(n); | ||
| const predecessor = new Int32Array(n).fill(-1); | ||
| let tailsLen = 0; | ||
| for (let i = 0; i < n; i++) { | ||
| const val = arr[i]; | ||
| let lo = 0, hi = tailsLen; | ||
| while (lo < hi) { | ||
| const mid = lo + hi >> 1; | ||
| if (tails[mid] < val) lo = mid + 1; | ||
| else hi = mid; | ||
| } | ||
| tails[lo] = val; | ||
| tailIndices[lo] = i; | ||
| if (lo > 0) predecessor[i] = tailIndices[lo - 1]; | ||
| if (lo >= tailsLen) tailsLen++; | ||
| } | ||
| const result = new Array(tailsLen); | ||
| let idx = tailIndices[tailsLen - 1]; | ||
| for (let i = tailsLen - 1; i >= 0; i--) { | ||
| result[i] = idx; | ||
| idx = predecessor[idx]; | ||
| } | ||
| return result; | ||
| } | ||
| var SMALL_LIST_THRESHOLD = 32; | ||
| var ABORT_SYM3 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var CACHE_SYM2 = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM2 = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function canPatchStaticElement(target, source) { | ||
| return target instanceof HTMLElement && source instanceof HTMLElement && target.tagName === source.tagName && !target[ABORT_SYM3] && !target[CACHE_SYM2] && !target[DYNAMIC_CHILD_SYM2] && !source[ABORT_SYM3] && !source[CACHE_SYM2] && !source[DYNAMIC_CHILD_SYM2]; | ||
| } | ||
| function patchStaticElement(target, source) { | ||
| const sourceAttrNames = /* @__PURE__ */ new Set(); | ||
| for (const attr of Array.from(source.attributes)) { | ||
| sourceAttrNames.add(attr.name); | ||
| if (target.getAttribute(attr.name) !== attr.value) { | ||
| target.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (!sourceAttrNames.has(attr.name)) { | ||
| target.removeAttribute(attr.name); | ||
| } | ||
| } | ||
| target.replaceChildren(...Array.from(source.childNodes)); | ||
| } | ||
| function reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| const oldKeys = new Array(oldLen); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeys[i] = keyFn(oldItems[i]); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| let found = -1; | ||
| for (let j = 0; j < oldLen; j++) { | ||
| if (!oldUsed[j] && oldKeys[j] === key) { | ||
| found = j; | ||
| oldUsed[j] = 1; | ||
| break; | ||
| } | ||
| } | ||
| oldIndices[i] = found; | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function reconcileList(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| if (newLen === 0) { | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| return { nodes: [], items: [] }; | ||
| } | ||
| if (oldLen === 0) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = createFn(newItems[i]); | ||
| if (beforeNode) { | ||
| parent.insertBefore(node, beforeNode); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| hooks?.onInsert?.(node); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| if (oldLen < SMALL_LIST_THRESHOLD) { | ||
| return reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks); | ||
| } | ||
| const oldKeyMap = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeyMap.set(keyFn(oldItems[i]), i); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| const oldIdx = oldKeyMap.get(key); | ||
| if (oldIdx !== void 0) { | ||
| oldIndices[i] = oldIdx; | ||
| oldUsed[oldIdx] = 1; | ||
| } else { | ||
| oldIndices[i] = -1; | ||
| } | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function createList(items, keyFn, renderFn, options) { | ||
| if (hydrating) { | ||
| return { type: "list", items, keyFn, renderFn, options }; | ||
| } | ||
| const startMarker = document.createComment("forma-list-start"); | ||
| const endMarker = document.createComment("forma-list-end"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| let currentNodes = []; | ||
| let currentItems = []; | ||
| const updateOnItemChange = options?.updateOnItemChange ?? "none"; | ||
| internalEffect(() => { | ||
| const newItems = items(); | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(newItems)) { | ||
| if (__DEV__) { | ||
| console.warn("[forma] createList: value is not an array, treating as empty"); | ||
| } | ||
| for (const node of currentNodes) { | ||
| if (node.parentNode === parent) parent.removeChild(node); | ||
| } | ||
| cache = /* @__PURE__ */ new Map(); | ||
| currentNodes = []; | ||
| currentItems = []; | ||
| return; | ||
| } | ||
| let cleanItems = newItems; | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| if (newItems[i] == null) { | ||
| cleanItems = newItems.filter((item) => item != null); | ||
| break; | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const item of cleanItems) { | ||
| const key = keyFn(item); | ||
| if (seen.has(key)) { | ||
| console.warn("[forma] createList: duplicate key detected:", key); | ||
| } | ||
| seen.add(key); | ||
| } | ||
| } | ||
| const updateRow = updateOnItemChange === "rerender" ? (node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (!cached) return; | ||
| if (cached.item === item) return; | ||
| cached.item = item; | ||
| if (!(node instanceof HTMLElement)) return; | ||
| if (node[ABORT_SYM3] || node[CACHE_SYM2] || node[DYNAMIC_CHILD_SYM2]) { | ||
| return; | ||
| } | ||
| const next = untrack(() => renderFn(item, cached.getIndex)); | ||
| if (canPatchStaticElement(node, next)) { | ||
| patchStaticElement(node, next); | ||
| cached.element = node; | ||
| } | ||
| } : (_node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }; | ||
| const result = reconcileList( | ||
| parent, | ||
| currentItems, | ||
| cleanItems, | ||
| currentNodes, | ||
| keyFn, | ||
| // createFn: create element + cache entry | ||
| (item) => { | ||
| const key = keyFn(item); | ||
| const [getIndex, setIndex] = createSignal(0); | ||
| const element = untrack(() => renderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| }, | ||
| updateRow, | ||
| // beforeNode: insert items before the end marker | ||
| endMarker | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < cleanItems.length; i++) { | ||
| const key = keyFn(cleanItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| currentNodes = result.nodes; | ||
| currentItems = result.items; | ||
| }); | ||
| return fragment2; | ||
| } | ||
| export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList }; | ||
| //# sourceMappingURL=chunk-7Q7LIV23.js.map | ||
| //# sourceMappingURL=chunk-7Q7LIV23.js.map |
Sorry, the diff of this file is too big to display
| 'use strict'; | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
| var alienSignals = require('alien-signals'); | ||
| var currentRoot = null; | ||
| var rootStack = []; | ||
| function createRoot(fn) { | ||
| const scope = { disposers: [], scopeDispose: null }; | ||
| rootStack.push(currentRoot); | ||
| currentRoot = scope; | ||
| const dispose = () => { | ||
| if (scope.scopeDispose) { | ||
| try { | ||
| scope.scopeDispose(); | ||
| } catch { | ||
| } | ||
| scope.scopeDispose = null; | ||
| } | ||
| for (const d of scope.disposers) { | ||
| try { | ||
| d(); | ||
| } catch { | ||
| } | ||
| } | ||
| scope.disposers.length = 0; | ||
| }; | ||
| let result; | ||
| try { | ||
| scope.scopeDispose = alienSignals.effectScope(() => { | ||
| result = fn(dispose); | ||
| }); | ||
| } finally { | ||
| currentRoot = rootStack.pop() ?? null; | ||
| } | ||
| return result; | ||
| } | ||
| function registerDisposer(dispose) { | ||
| if (currentRoot) { | ||
| currentRoot.disposers.push(dispose); | ||
| } | ||
| } | ||
| function hasActiveRoot() { | ||
| return currentRoot !== null; | ||
| } | ||
| // src/reactive/cleanup.ts | ||
| var currentCleanupCollector = null; | ||
| function onCleanup(fn) { | ||
| currentCleanupCollector?.(fn); | ||
| } | ||
| function setCleanupCollector(collector) { | ||
| const prev = currentCleanupCollector; | ||
| currentCleanupCollector = collector; | ||
| return prev; | ||
| } | ||
| // src/reactive/dev.ts | ||
| var __DEV__ = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true; | ||
| var _errorHandler = null; | ||
| function onError(handler) { | ||
| _errorHandler = handler; | ||
| } | ||
| function reportError(error, source) { | ||
| if (_errorHandler) { | ||
| try { | ||
| _errorHandler(error, source ? { source } : {}); | ||
| } catch { | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| console.error(`[forma] ${source ?? "Unknown"} error:`, error); | ||
| } | ||
| } | ||
| var POOL_SIZE = 32; | ||
| var MAX_REENTRANT_RUNS = 100; | ||
| var pool = []; | ||
| for (let i = 0; i < POOL_SIZE; i++) pool.push([]); | ||
| var poolIdx = POOL_SIZE; | ||
| function acquireArray() { | ||
| if (poolIdx > 0) { | ||
| const arr = pool[--poolIdx]; | ||
| arr.length = 0; | ||
| return arr; | ||
| } | ||
| return []; | ||
| } | ||
| function releaseArray(arr) { | ||
| arr.length = 0; | ||
| if (poolIdx < POOL_SIZE) { | ||
| pool[poolIdx++] = arr; | ||
| } | ||
| } | ||
| function runCleanup(fn) { | ||
| if (fn === void 0) return; | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| function runCleanups(bag) { | ||
| if (bag === void 0) return; | ||
| for (let i = 0; i < bag.length; i++) { | ||
| try { | ||
| bag[i](); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| } | ||
| function internalEffect(fn) { | ||
| const dispose = alienSignals.effect(fn); | ||
| if (hasActiveRoot()) { | ||
| registerDisposer(dispose); | ||
| } | ||
| return dispose; | ||
| } | ||
| function createEffect(fn) { | ||
| const shouldRegister = hasActiveRoot(); | ||
| let cleanup; | ||
| let cleanupBag; | ||
| let nextCleanup; | ||
| let nextCleanupBag; | ||
| const addCleanup = (cb) => { | ||
| if (nextCleanupBag !== void 0) { | ||
| nextCleanupBag.push(cb); | ||
| return; | ||
| } | ||
| if (nextCleanup !== void 0) { | ||
| const bag = acquireArray(); | ||
| bag.push(nextCleanup, cb); | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = bag; | ||
| return; | ||
| } | ||
| nextCleanup = cb; | ||
| }; | ||
| let skipCleanupInfra = false; | ||
| let firstRun = true; | ||
| let running = false; | ||
| let rerunRequested = false; | ||
| const runOnce = () => { | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| if (skipCleanupInfra) { | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| } | ||
| return; | ||
| } | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = void 0; | ||
| const prevCollector = setCleanupCollector(addCleanup); | ||
| try { | ||
| const result = fn(); | ||
| if (typeof result === "function") { | ||
| addCleanup(result); | ||
| } | ||
| if (nextCleanup === void 0 && nextCleanupBag === void 0) { | ||
| if (firstRun) skipCleanupInfra = true; | ||
| return; | ||
| } | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } finally { | ||
| setCleanupCollector(prevCollector); | ||
| firstRun = false; | ||
| } | ||
| }; | ||
| const safeFn = () => { | ||
| if (running) { | ||
| rerunRequested = true; | ||
| return; | ||
| } | ||
| running = true; | ||
| try { | ||
| let reentrantRuns = 0; | ||
| do { | ||
| rerunRequested = false; | ||
| runOnce(); | ||
| if (rerunRequested) { | ||
| reentrantRuns++; | ||
| if (reentrantRuns >= MAX_REENTRANT_RUNS) { | ||
| reportError( | ||
| new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`), | ||
| "effect" | ||
| ); | ||
| rerunRequested = false; | ||
| } | ||
| } | ||
| } while (rerunRequested); | ||
| } finally { | ||
| running = false; | ||
| } | ||
| }; | ||
| const dispose = alienSignals.effect(safeFn); | ||
| let disposed = false; | ||
| const wrappedDispose = () => { | ||
| if (disposed) return; | ||
| disposed = true; | ||
| dispose(); | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| }; | ||
| if (shouldRegister) { | ||
| registerDisposer(wrappedDispose); | ||
| } | ||
| return wrappedDispose; | ||
| } | ||
| // src/reactive/memo.ts | ||
| var createMemo = chunkQLPCVK7C_cjs.createComputed; | ||
| function batch(fn) { | ||
| alienSignals.startBatch(); | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| alienSignals.endBatch(); | ||
| } | ||
| } | ||
| function untrack(fn) { | ||
| const prev = alienSignals.setActiveSub(void 0); | ||
| try { | ||
| return fn(); | ||
| } finally { | ||
| alienSignals.setActiveSub(prev); | ||
| } | ||
| } | ||
| // src/reactive/on.ts | ||
| function on(deps, fn, options) { | ||
| let prev; | ||
| let isFirst = true; | ||
| return () => { | ||
| const value2 = deps(); | ||
| if (options?.defer && isFirst) { | ||
| isFirst = false; | ||
| prev = value2; | ||
| return void 0; | ||
| } | ||
| const result = untrack(() => fn(value2, prev)); | ||
| prev = value2; | ||
| return result; | ||
| }; | ||
| } | ||
| // src/reactive/ref.ts | ||
| function createRef(initialValue) { | ||
| return { current: initialValue }; | ||
| } | ||
| // src/reactive/reducer.ts | ||
| function createReducer(reducer, initialState) { | ||
| const [state, setState] = chunkQLPCVK7C_cjs.createSignal(initialState); | ||
| const dispatch = (action) => { | ||
| setState((prev) => reducer(prev, action)); | ||
| }; | ||
| return [state, dispatch]; | ||
| } | ||
| // src/reactive/suspense-context.ts | ||
| var currentSuspenseContext = null; | ||
| var suspenseStack = []; | ||
| function pushSuspenseContext(ctx) { | ||
| suspenseStack.push(currentSuspenseContext); | ||
| currentSuspenseContext = ctx; | ||
| } | ||
| function popSuspenseContext() { | ||
| currentSuspenseContext = suspenseStack.pop() ?? null; | ||
| } | ||
| function getSuspenseContext() { | ||
| return currentSuspenseContext; | ||
| } | ||
| // src/reactive/resource.ts | ||
| function createResource(source, fetcher, options) { | ||
| const [data, setData] = chunkQLPCVK7C_cjs.createSignal(options?.initialValue); | ||
| const [loading, setLoading] = chunkQLPCVK7C_cjs.createSignal(false); | ||
| const [error, setError] = chunkQLPCVK7C_cjs.createSignal(void 0); | ||
| const suspenseCtx = getSuspenseContext(); | ||
| let abortController = null; | ||
| let fetchVersion = 0; | ||
| const doFetch = () => { | ||
| const sourceValue = untrack(source); | ||
| if (abortController) { | ||
| abortController.abort(); | ||
| } | ||
| const controller = new AbortController(); | ||
| abortController = controller; | ||
| const version = ++fetchVersion; | ||
| const isLatest = () => version === fetchVersion; | ||
| let suspensePending = false; | ||
| if (suspenseCtx) { | ||
| suspenseCtx.increment(); | ||
| suspensePending = true; | ||
| } | ||
| setLoading(true); | ||
| setError(void 0); | ||
| Promise.resolve(fetcher(sourceValue)).then((result) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| setData(() => result); | ||
| } | ||
| }).catch((err) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| if (err?.name !== "AbortError") { | ||
| setError(err); | ||
| } | ||
| } | ||
| }).finally(() => { | ||
| if (suspensePending) suspenseCtx?.decrement(); | ||
| if (isLatest()) { | ||
| setLoading(false); | ||
| if (abortController === controller) { | ||
| abortController = null; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| internalEffect(() => { | ||
| source(); | ||
| doFetch(); | ||
| }); | ||
| const resource = (() => data()); | ||
| resource.loading = loading; | ||
| resource.error = error; | ||
| resource.refetch = doFetch; | ||
| resource.mutate = (value2) => setData(() => value2); | ||
| return resource; | ||
| } | ||
| Object.defineProperty(exports, "getBatchDepth", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.getBatchDepth; } | ||
| }); | ||
| Object.defineProperty(exports, "isComputed", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isComputed; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffect", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isEffect; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffectScope", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isEffectScope; } | ||
| }); | ||
| Object.defineProperty(exports, "isSignal", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isSignal; } | ||
| }); | ||
| Object.defineProperty(exports, "trigger", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.trigger; } | ||
| }); | ||
| exports.__DEV__ = __DEV__; | ||
| exports.batch = batch; | ||
| exports.createEffect = createEffect; | ||
| exports.createMemo = createMemo; | ||
| exports.createReducer = createReducer; | ||
| exports.createRef = createRef; | ||
| exports.createResource = createResource; | ||
| exports.createRoot = createRoot; | ||
| exports.internalEffect = internalEffect; | ||
| exports.on = on; | ||
| exports.onCleanup = onCleanup; | ||
| exports.onError = onError; | ||
| exports.popSuspenseContext = popSuspenseContext; | ||
| exports.pushSuspenseContext = pushSuspenseContext; | ||
| exports.reportError = reportError; | ||
| exports.untrack = untrack; | ||
| //# sourceMappingURL=chunk-BARF67I6.cjs.map | ||
| //# sourceMappingURL=chunk-BARF67I6.cjs.map |
| {"version":3,"sources":["../src/reactive/root.ts","../src/reactive/cleanup.ts","../src/reactive/dev.ts","../src/reactive/effect.ts","../src/reactive/memo.ts","../src/reactive/batch.ts","../src/reactive/untrack.ts","../src/reactive/on.ts","../src/reactive/ref.ts","../src/reactive/reducer.ts","../src/reactive/suspense-context.ts","../src/reactive/resource.ts"],"names":["rawEffectScope","rawEffect","createComputed","startBatch","endBatch","setActiveSub","value","createSignal"],"mappings":"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","file":"chunk-BARF67I6.cjs","sourcesContent":["/**\n * Forma Reactive - Root\n *\n * Explicit reactive ownership scope. All effects created inside a root\n * are automatically disposed when the root is torn down.\n *\n * Uses alien-signals' `effectScope` under the hood for native graph-level\n * effect tracking, with a userland disposer list for non-effect cleanup\n * (e.g., event listeners, DOM references, timers).\n */\n\nimport { effectScope as rawEffectScope } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Root scope tracking\n// ---------------------------------------------------------------------------\n\nlet currentRoot: RootScope | null = null;\nconst rootStack: (RootScope | null)[] = [];\n\ninterface RootScope {\n /** Userland disposers (event listeners, DOM refs, timers, etc.) */\n disposers: (() => void)[];\n /** alien-signals effect scope dispose — tears down all reactive effects */\n scopeDispose: (() => void) | null;\n}\n\n/**\n * Create a reactive root scope.\n *\n * All effects created (via `createEffect`) inside the callback are tracked\n * at both the reactive graph level (via alien-signals effectScope) and the\n * userland level (via registerDisposer). The returned `dispose` function\n * tears down everything.\n *\n * ```ts\n * const dispose = createRoot(() => {\n * createEffect(() => console.log(count()));\n * createEffect(() => console.log(name()));\n * });\n * // later: dispose() stops both effects\n * ```\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const scope: RootScope = { disposers: [], scopeDispose: null };\n\n rootStack.push(currentRoot);\n currentRoot = scope;\n\n const dispose = () => {\n // Dispose alien-signals effect scope first (reactive graph cleanup)\n if (scope.scopeDispose) {\n try { scope.scopeDispose(); } catch { /* ensure userland disposers still run */ }\n scope.scopeDispose = null;\n }\n // Then run userland disposers\n for (const d of scope.disposers) {\n try { d(); } catch { /* ensure all disposers run */ }\n }\n scope.disposers.length = 0;\n };\n\n let result: T;\n try {\n // Wrap in alien-signals effectScope for native effect tracking\n scope.scopeDispose = rawEffectScope(() => {\n result = fn(dispose);\n });\n } finally {\n currentRoot = rootStack.pop() ?? null;\n }\n\n return result!;\n}\n\n/**\n * @internal — called by createEffect to register disposers in the current root.\n */\nexport function registerDisposer(dispose: () => void): void {\n if (currentRoot) {\n currentRoot.disposers.push(dispose);\n }\n}\n\n/**\n * @internal — check if we're inside a root scope.\n */\nexport function hasActiveRoot(): boolean {\n return currentRoot !== null;\n}\n","/**\n * Forma Reactive - Cleanup\n *\n * Register cleanup functions within reactive scopes.\n * Inspired by SolidJS onCleanup().\n */\n\n// ---------------------------------------------------------------------------\n// Cleanup context tracking\n// ---------------------------------------------------------------------------\n\ntype CleanupCollector = ((fn: () => void) => void) | null;\n\nlet currentCleanupCollector: CleanupCollector = null;\n\n/**\n * Register a cleanup function in the current reactive scope.\n * The cleanup runs before the effect re-executes and on disposal.\n *\n * More composable than returning a cleanup from the effect function,\n * since it can be called from helper functions.\n *\n * ```ts\n * createEffect(() => {\n * const timer = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timer));\n * });\n * ```\n */\nexport function onCleanup(fn: () => void): void {\n currentCleanupCollector?.(fn);\n}\n\n/**\n * @internal — Set the cleanup collector for the current effect execution.\n */\nexport function setCleanupCollector(collector: CleanupCollector): CleanupCollector {\n const prev = currentCleanupCollector;\n currentCleanupCollector = collector;\n return prev;\n}\n","/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by bundler (tsup define). Defaults to true for unbundled usage.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\nexport const __DEV__: boolean = typeof process !== 'undefined'\n ? (process!.env?.NODE_ENV !== 'production')\n : true;\n\n// ---------------------------------------------------------------------------\n// Global error handler\n// ---------------------------------------------------------------------------\n\n/** Callback signature for the global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nlet _errorHandler: ErrorHandler | null = null;\n\n/**\n * Install a global error handler for FormaJS reactive errors.\n * Called when effects, computeds, or event handlers throw.\n *\n * ```ts\n * onError((err, info) => {\n * console.error(`[${info?.source}]`, err);\n * Sentry.captureException(err);\n * });\n * ```\n */\nexport function onError(handler: ErrorHandler): void {\n _errorHandler = handler;\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n if (_errorHandler) {\n try { _errorHandler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (__DEV__) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Effect\n *\n * Side-effectful reactive computation that auto-tracks signal dependencies.\n * Backed by alien-signals for automatic dependency tracking.\n *\n * TC39 Signals equivalent: Signal.subtle.Watcher (effect is a userland concept)\n */\n\nimport { effect as rawEffect } from 'alien-signals';\nimport { hasActiveRoot, registerDisposer } from './root.js';\nimport { setCleanupCollector } from './cleanup.js';\nimport { reportError } from './dev.js';\n\n// ---------------------------------------------------------------------------\n// Cleanup array pool — avoids allocating a new array every effect re-run\n// ---------------------------------------------------------------------------\n\nconst POOL_SIZE = 32;\nconst MAX_REENTRANT_RUNS = 100;\nconst pool: (() => void)[][] = [];\nfor (let i = 0; i < POOL_SIZE; i++) pool.push([]);\nlet poolIdx = POOL_SIZE;\n\nfunction acquireArray(): (() => void)[] {\n if (poolIdx > 0) {\n const arr = pool[--poolIdx]!;\n arr.length = 0;\n return arr;\n }\n return [];\n}\n\nfunction releaseArray(arr: (() => void)[]): void {\n arr.length = 0;\n if (poolIdx < POOL_SIZE) {\n pool[poolIdx++] = arr;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified cleanup runner — single function for both re-run and dispose paths\n// ---------------------------------------------------------------------------\n\nfunction runCleanup(fn: (() => void) | undefined): void {\n if (fn === undefined) return;\n try {\n fn();\n } catch (e) {\n reportError(e, 'effect cleanup');\n }\n}\n\nfunction runCleanups(bag: (() => void)[] | undefined): void {\n if (bag === undefined) return;\n for (let i = 0; i < bag.length; i++) {\n try { bag[i]!(); } catch (e) { reportError(e, 'effect cleanup'); }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive effect that auto-tracks signal dependencies.\n *\n * The provided function runs immediately and re-runs whenever any signal it\n * reads changes. If the function returns a cleanup function, that cleanup is\n * called before each re-run and on disposal.\n *\n * Additionally, `onCleanup()` can be called inside the effect to register\n * cleanup functions composably.\n *\n * Returns a dispose function that stops the effect.\n */\n/**\n * @internal — Lightweight effect for Forma's internal DOM bindings.\n *\n * Bypasses createEffect's cleanup infrastructure (pool, collector, error\n * reporting, engine compression) since internal effects never use\n * onCleanup() or return cleanup functions. This saves ~4 function calls\n * per effect creation and per re-run.\n *\n * ONLY use for effects that:\n * 1. Never call onCleanup()\n * 2. Never return a cleanup function\n * 3. Contain simple \"read signal → write DOM\" logic\n */\nexport function internalEffect(fn: () => void): () => void {\n const dispose = rawEffect(fn);\n if (hasActiveRoot()) {\n registerDisposer(dispose);\n }\n return dispose;\n}\n\nexport function createEffect(fn: () => void | (() => void)): () => void {\n const shouldRegister = hasActiveRoot();\n\n // Most effects have zero or one cleanup. Track the single-cleanup case\n // without array allocation; only promote to pooled array when needed.\n let cleanup: (() => void) | undefined;\n let cleanupBag: (() => void)[] | undefined;\n let nextCleanup: (() => void) | undefined;\n let nextCleanupBag: (() => void)[] | undefined;\n\n const addCleanup = (cb: () => void) => {\n if (nextCleanupBag !== undefined) {\n nextCleanupBag.push(cb);\n return;\n }\n if (nextCleanup !== undefined) {\n const bag = acquireArray();\n bag.push(nextCleanup, cb);\n nextCleanup = undefined;\n nextCleanupBag = bag;\n return;\n }\n nextCleanup = cb;\n };\n\n // \"Engine Compression\" exploit: most effects never use cleanup (onCleanup()\n // or return value). After the first clean run, we know this effect is\n // \"cleanup-free\" and can skip the entire cleanup infrastructure on re-runs.\n // This saves 4 function calls per re-run: acquireArray, setCleanupCollector,\n // releaseArray, and bag inspection. Like the engine compression ratio exploit —\n // the \"rules\" say we should always prepare for cleanup, but we measure at\n // \"room temperature\" (first run) and exploit the gap.\n let skipCleanupInfra = false;\n let firstRun = true;\n let running = false;\n let rerunRequested = false;\n\n const runOnce = () => {\n // Run and clear previous cleanups (only if any exist).\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n\n // Ultra-fast path: this effect has proven it doesn't use cleanup.\n // Skip acquireArray + setCleanupCollector + bag checks + releaseArray.\n if (skipCleanupInfra) {\n try { fn(); } catch (e) { reportError(e, 'effect'); }\n return;\n }\n\n nextCleanup = undefined;\n nextCleanupBag = undefined;\n\n // Full path: install collector for onCleanup() calls.\n const prevCollector = setCleanupCollector(addCleanup);\n\n try {\n const result = fn();\n\n if (typeof result === 'function') {\n addCleanup(result as () => void);\n }\n\n // Hot path: no cleanups registered or returned — enable ultra-fast path.\n if (nextCleanup === undefined && nextCleanupBag === undefined) {\n // First clean run → enable ultra-fast path for all subsequent runs\n if (firstRun) skipCleanupInfra = true;\n return;\n }\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } catch (e) {\n reportError(e, 'effect');\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } finally {\n setCleanupCollector(prevCollector);\n firstRun = false;\n }\n };\n\n const safeFn = () => {\n if (running) {\n rerunRequested = true;\n return;\n }\n\n running = true;\n try {\n let reentrantRuns = 0;\n do {\n rerunRequested = false;\n runOnce();\n if (rerunRequested) {\n reentrantRuns++;\n if (reentrantRuns >= MAX_REENTRANT_RUNS) {\n reportError(\n new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`),\n 'effect',\n );\n rerunRequested = false;\n }\n }\n } while (rerunRequested);\n } finally {\n running = false;\n }\n };\n\n const dispose = rawEffect(safeFn);\n\n // Wrap dispose to also run final cleanups\n let disposed = false;\n const wrappedDispose = () => {\n if (disposed) return;\n disposed = true;\n dispose();\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n };\n\n // Register in current root scope (if any)\n if (shouldRegister) {\n registerDisposer(wrappedDispose);\n }\n\n return wrappedDispose;\n}\n","/**\n * Forma Reactive - Memo\n *\n * Alias for createComputed with SolidJS/React-familiar naming.\n * A memoized derived value that only recomputes when its dependencies change.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { createComputed } from './computed.js';\n\n/**\n * Create a memoized computed value.\n * Identical to `createComputed` — provided for React/SolidJS familiarity.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), both createComputed and createMemo in FormaJS are lazy\n * cached derivations — equivalent to SolidJS's createMemo. They are\n * identical.\n *\n * The computation runs lazily and caches the result. It only recomputes\n * when a signal it reads during computation changes.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createMemo(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n */\nexport const createMemo: typeof createComputed = createComputed;\n","/**\n * Forma Reactive - Batch\n *\n * Groups multiple signal updates and defers effect execution until the\n * outermost batch completes. Prevents intermediate re-renders.\n * Backed by alien-signals.\n *\n * TC39 Signals: no built-in batch — this is a userland optimization.\n */\n\nimport { startBatch, endBatch } from 'alien-signals';\n\n/**\n * Group multiple signal updates so that effects only run once after all\n * updates have been applied.\n *\n * ```ts\n * batch(() => {\n * setA(1);\n * setB(2);\n * // effects that depend on A and B won't run yet\n * });\n * // effects run here, once\n * ```\n *\n * Batches are nestable — only the outermost batch triggers the flush.\n */\nexport function batch(fn: () => void): void {\n startBatch();\n try {\n fn();\n } finally {\n endBatch();\n }\n}\n","/**\n * Forma Reactive - Untrack\n *\n * Read signals without subscribing to them in the reactive graph.\n * Essential for reading values inside effects without creating dependencies.\n *\n * TC39 Signals equivalent: Signal.subtle.untrack()\n */\n\nimport { setActiveSub } from 'alien-signals';\n\n/**\n * Execute a function without tracking signal reads.\n * Any signals read inside `fn` will NOT become dependencies of the\n * surrounding effect or computed.\n *\n * ```ts\n * createEffect(() => {\n * const a = count(); // tracked — effect re-runs when count changes\n * const b = untrack(() => other()); // NOT tracked — effect ignores other changes\n * });\n * ```\n */\nexport function untrack<T>(fn: () => T): T {\n const prev = setActiveSub(undefined);\n try {\n return fn();\n } finally {\n setActiveSub(prev);\n }\n}\n","/**\n * Forma Reactive - On\n *\n * Explicit dependency tracking for effects.\n * Only re-runs when the specified signals change, ignoring all other reads.\n *\n * SolidJS equivalent: on()\n * Vue equivalent: watch() with explicit deps\n * React equivalent: useEffect dependency array\n */\n\nimport { untrack } from './untrack.js';\n\n/**\n * Create a tracked effect body that only fires when specific dependencies change.\n *\n * Wraps a function so that only the `deps` signals are tracked.\n * All signal reads inside `fn` are untracked (won't cause re-runs).\n *\n * Use with `createEffect`:\n *\n * ```ts\n * const [a, setA] = createSignal(1);\n * const [b, setB] = createSignal(2);\n *\n * // Only re-runs when `a` changes, NOT when `b` changes:\n * createEffect(on(a, (value, prev) => {\n * console.log(`a changed: ${prev} → ${value}, b is ${b()}`);\n * }));\n *\n * setA(10); // fires: \"a changed: 1 → 10, b is 2\"\n * setB(20); // does NOT fire\n * ```\n *\n * Multiple dependencies:\n * ```ts\n * createEffect(on(\n * () => [a(), b()] as const,\n * ([aVal, bVal], prev) => { ... }\n * ));\n * ```\n */\nexport function on<T, U>(\n deps: () => T,\n fn: (value: T, prev: T | undefined) => U,\n options?: { defer?: boolean },\n): () => U | undefined {\n let prev: T | undefined;\n let isFirst = true;\n\n return () => {\n // Track only the deps\n const value = deps();\n\n if (options?.defer && isFirst) {\n isFirst = false;\n prev = value;\n return undefined;\n }\n\n // Run the body untracked so it doesn't add extra dependencies\n const result = untrack(() => fn(value, prev));\n prev = value;\n return result;\n };\n}\n","/**\n * Forma Reactive - Ref\n *\n * Mutable container that does NOT trigger reactivity.\n * Use for DOM references, previous values, instance variables —\n * anything that needs to persist across effect re-runs without\n * causing re-execution.\n *\n * React equivalent: useRef\n * SolidJS equivalent: (none — uses plain variables in setup)\n */\n\n/** A mutable container that does NOT trigger reactivity when written to. */\nexport interface Ref<T> {\n /** The stored value. Mutating this does not notify effects. */\n current: T;\n}\n\n/**\n * Create a mutable ref container.\n *\n * Unlike signals, writing to `.current` does NOT trigger effects.\n * Use when you need a stable reference across reactive scopes.\n *\n * ```ts\n * const timerRef = createRef<number | null>(null);\n *\n * createEffect(() => {\n * timerRef.current = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timerRef.current!));\n * });\n *\n * // DOM ref pattern:\n * const elRef = createRef<HTMLElement | null>(null);\n * h('div', { ref: (el) => { elRef.current = el; } });\n * ```\n */\nexport function createRef<T>(initialValue: T): Ref<T> {\n return { current: initialValue };\n}\n","/**\n * Forma Reactive - Reducer\n *\n * State machine pattern — dispatch actions to a pure reducer function.\n * Fine-grained: only the resulting state signal is reactive.\n *\n * React equivalent: useReducer\n * SolidJS equivalent: (none — uses createSignal + helpers)\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\n\n/** A function that dispatches an action to a reducer. */\nexport type Dispatch<A> = (action: A) => void;\n\n/**\n * Create a reducer — predictable state updates via dispatched actions.\n *\n * The reducer function must be pure: `(state, action) => newState`.\n * Returns a [state, dispatch] tuple.\n *\n * ```ts\n * type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };\n *\n * const [count, dispatch] = createReducer(\n * (state: number, action: Action) => {\n * switch (action.type) {\n * case 'increment': return state + 1;\n * case 'decrement': return state - 1;\n * case 'reset': return 0;\n * }\n * },\n * 0,\n * );\n *\n * dispatch({ type: 'increment' }); // count() === 1\n * dispatch({ type: 'increment' }); // count() === 2\n * dispatch({ type: 'reset' }); // count() === 0\n * ```\n */\nexport function createReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initialState: S,\n): [state: SignalGetter<S>, dispatch: Dispatch<A>] {\n const [state, setState] = createSignal(initialState);\n\n const dispatch: Dispatch<A> = (action) => {\n setState((prev) => reducer(prev, action));\n };\n\n return [state, dispatch];\n}\n","/**\n * Forma Reactive - Suspense Context\n *\n * Shared context stack for Suspense boundaries. Lives in the reactive layer\n * (not DOM) so that createResource can import it without circular dependencies.\n *\n * The pattern mirrors the lifecycle context stack in component/define.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Context object for a Suspense boundary, tracking pending async resources. */\nexport interface SuspenseContext {\n /** Increment when a resource starts loading inside this boundary. */\n increment(): void;\n /** Decrement when a resource resolves/rejects inside this boundary. */\n decrement(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Context stack\n// ---------------------------------------------------------------------------\n\nlet currentSuspenseContext: SuspenseContext | null = null;\nconst suspenseStack: (SuspenseContext | null)[] = [];\n\nexport function pushSuspenseContext(ctx: SuspenseContext): void {\n suspenseStack.push(currentSuspenseContext);\n currentSuspenseContext = ctx;\n}\n\nexport function popSuspenseContext(): void {\n currentSuspenseContext = suspenseStack.pop() ?? null;\n}\n\n/** Get the current Suspense context (if any). Called by createResource. */\nexport function getSuspenseContext(): SuspenseContext | null {\n return currentSuspenseContext;\n}\n","/**\n * Forma Reactive - Resource\n *\n * Async data fetching primitive with reactive loading/error state.\n * Tracks a source signal and refetches when it changes.\n *\n * SolidJS equivalent: createResource\n * React equivalent: use() + Suspense (React 19), or useSWR/react-query\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\nimport { internalEffect } from './effect.js';\nimport { untrack } from './untrack.js';\nimport { getSuspenseContext } from './suspense-context.js';\n\n/** An async data resource with reactive loading and error state. */\nexport interface Resource<T> {\n /** The resolved data (or undefined while loading). */\n (): T | undefined;\n /** True while the fetcher is running. */\n loading: SignalGetter<boolean>;\n /** The error if the fetcher rejected (or undefined). */\n error: SignalGetter<unknown>;\n /** Manually refetch with the current source value. */\n refetch: () => void;\n /** Manually set the data (overrides fetcher result). */\n mutate: (value: T | undefined) => void;\n}\n\n/** Options for {@link createResource}. */\nexport interface ResourceOptions<T> {\n /** Initial value before first fetch resolves. */\n initialValue?: T;\n}\n\n/**\n * Create an async resource that fetches data reactively.\n *\n * When `source` changes, the fetcher re-runs automatically.\n * Provides reactive `loading` and `error` signals.\n *\n * ```ts\n * const [userId, setUserId] = createSignal(1);\n *\n * const user = createResource(\n * userId, // source signal\n * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher\n * );\n *\n * internalEffect(() => {\n * if (user.loading()) console.log('Loading...');\n * else if (user.error()) console.log('Error:', user.error());\n * else console.log('User:', user());\n * });\n *\n * setUserId(2); // automatically refetches\n * ```\n *\n * Without a source signal (static fetch):\n * ```ts\n * const posts = createResource(\n * () => true, // constant source — fetches once\n * () => fetch('/api/posts').then(r => r.json()),\n * );\n * ```\n */\nexport function createResource<T, S = true>(\n source: SignalGetter<S>,\n fetcher: (source: S) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> {\n const [data, setData] = createSignal<T | undefined>(options?.initialValue);\n const [loading, setLoading] = createSignal(false);\n const [error, setError] = createSignal<unknown>(undefined);\n\n // Capture the Suspense context at creation time (not at fetch time).\n // This is critical because the Suspense boundary pushes/pops its context\n // synchronously during children() execution.\n const suspenseCtx = getSuspenseContext();\n\n let abortController: AbortController | null = null;\n let fetchVersion = 0;\n\n const doFetch = () => {\n // Read source outside tracking to get current value\n const sourceValue = untrack(source);\n\n // Abort previous in-flight request\n if (abortController) {\n abortController.abort();\n }\n const controller = new AbortController();\n abortController = controller;\n\n const version = ++fetchVersion;\n const isLatest = () => version === fetchVersion;\n let suspensePending = false;\n\n // Notify Suspense boundary that a fetch has started\n if (suspenseCtx) {\n suspenseCtx.increment();\n suspensePending = true;\n }\n\n setLoading(true);\n setError(undefined);\n\n Promise.resolve(fetcher(sourceValue))\n .then((result) => {\n // Only apply if this is still the latest fetch and wasn't aborted.\n if (isLatest() && !controller.signal.aborted) {\n setData(() => result);\n }\n })\n .catch((err) => {\n if (isLatest() && !controller.signal.aborted) {\n // Ignore abort errors\n if (err?.name !== 'AbortError') {\n setError(err);\n }\n }\n })\n .finally(() => {\n if (suspensePending) suspenseCtx?.decrement();\n if (isLatest()) {\n setLoading(false);\n if (abortController === controller) {\n abortController = null; // Release controller for GC\n }\n }\n });\n };\n\n // Auto-fetch when source changes\n internalEffect(() => {\n source(); // track the source signal\n doFetch();\n });\n\n // Build the resource object\n const resource = (() => data()) as Resource<T>;\n resource.loading = loading;\n resource.error = error;\n resource.refetch = doFetch;\n resource.mutate = (value) => setData(() => value);\n\n return resource;\n}\n"]} |
| import { signal, computed, setActiveSub } from 'alien-signals'; | ||
| // src/reactive/signal.ts | ||
| function applySignalSet(s, v, equals) { | ||
| if (typeof v !== "function") { | ||
| if (equals) { | ||
| const prevSub2 = setActiveSub(void 0); | ||
| const prev2 = s(); | ||
| setActiveSub(prevSub2); | ||
| if (equals(prev2, v)) return; | ||
| } | ||
| s(v); | ||
| return; | ||
| } | ||
| const prevSub = setActiveSub(void 0); | ||
| const prev = s(); | ||
| setActiveSub(prevSub); | ||
| const next = v(prev); | ||
| if (equals && equals(prev, next)) return; | ||
| s(next); | ||
| } | ||
| function createSignal(initialValue, options) { | ||
| const s = signal(initialValue); | ||
| const getter = s; | ||
| const eq = options?.equals; | ||
| const setter = (v) => applySignalSet(s, v, eq); | ||
| return [getter, setter]; | ||
| } | ||
| function createComputed(fn) { | ||
| return computed(fn); | ||
| } | ||
| export { createComputed, createSignal }; | ||
| //# sourceMappingURL=chunk-HLM5BZZQ.js.map | ||
| //# sourceMappingURL=chunk-HLM5BZZQ.js.map |
| {"version":3,"sources":["../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","prev","createRawSignal","rawComputed"],"mappings":";;;AAoEA,SAAS,cAAA,CACP,CAAA,EACA,CAAA,EACA,MAAA,EACM;AACN,EAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC3B,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,MAAMA,QAAAA,GAAU,aAAa,MAAS,CAAA;AACtC,MAAA,MAAMC,QAAO,CAAA,EAAE;AACf,MAAA,YAAA,CAAaD,QAAO,CAAA;AACpB,MAAA,IAAI,MAAA,CAAOC,KAAAA,EAAM,CAAC,CAAA,EAAG;AAAA,IACvB;AACA,IAAA,CAAA,CAAE,CAAC,CAAA;AACH,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,OAAA,GAAU,aAAa,MAAS,CAAA;AACtC,EAAA,MAAM,OAAO,CAAA,EAAE;AACf,EAAA,YAAA,CAAa,OAAO,CAAA;AACpB,EAAA,MAAM,IAAA,GAAQ,EAAqB,IAAI,CAAA;AACvC,EAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAM,IAAI,CAAA,EAAG;AAClC,EAAA,CAAA,CAAE,IAAI,CAAA;AACR;AAqBO,SAAS,YAAA,CAAgB,cAAiB,OAAA,EAA0E;AACzH,EAAA,MAAM,CAAA,GAAIC,OAAmB,YAAY,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,CAAA;AACf,EAAA,MAAM,KAAK,OAAA,EAAS,MAAA;AACpB,EAAA,MAAM,SAA0B,CAAC,CAAA,KAA4B,cAAA,CAAe,CAAA,EAAG,GAAG,EAAE,CAAA;AAEpF,EAAA,OAAO,CAAC,QAAQ,MAAM,CAAA;AACxB;AC/EO,SAAS,eAAkB,EAAA,EAAuC;AACvE,EAAA,OAAOC,SAAY,EAAE,CAAA;AACvB","file":"chunk-HLM5BZZQ.js","sourcesContent":["/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n if (typeof v !== 'function') {\n if (equals) {\n // Read current value without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // skip — values are equal\n }\n s(v);\n return;\n }\n\n // Functional update: read prev without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // skip — values are equal\n s(next);\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n return rawComputed(fn);\n}\n"]} |
| 'use strict'; | ||
| var alienSignals = require('alien-signals'); | ||
| // src/reactive/signal.ts | ||
| function applySignalSet(s, v, equals) { | ||
| if (typeof v !== "function") { | ||
| if (equals) { | ||
| const prevSub2 = alienSignals.setActiveSub(void 0); | ||
| const prev2 = s(); | ||
| alienSignals.setActiveSub(prevSub2); | ||
| if (equals(prev2, v)) return; | ||
| } | ||
| s(v); | ||
| return; | ||
| } | ||
| const prevSub = alienSignals.setActiveSub(void 0); | ||
| const prev = s(); | ||
| alienSignals.setActiveSub(prevSub); | ||
| const next = v(prev); | ||
| if (equals && equals(prev, next)) return; | ||
| s(next); | ||
| } | ||
| function createSignal(initialValue, options) { | ||
| const s = alienSignals.signal(initialValue); | ||
| const getter = s; | ||
| const eq = options?.equals; | ||
| const setter = (v) => applySignalSet(s, v, eq); | ||
| return [getter, setter]; | ||
| } | ||
| function createComputed(fn) { | ||
| return alienSignals.computed(fn); | ||
| } | ||
| exports.createComputed = createComputed; | ||
| exports.createSignal = createSignal; | ||
| //# sourceMappingURL=chunk-QLPCVK7C.cjs.map | ||
| //# sourceMappingURL=chunk-QLPCVK7C.cjs.map |
| {"version":3,"sources":["../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","setActiveSub","prev","createRawSignal","rawComputed"],"mappings":";;;;;AAoEA,SAAS,cAAA,CACP,CAAA,EACA,CAAA,EACA,MAAA,EACM;AACN,EAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC3B,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,MAAMA,QAAAA,GAAUC,0BAAa,MAAS,CAAA;AACtC,MAAA,MAAMC,QAAO,CAAA,EAAE;AACf,MAAAD,yBAAA,CAAaD,QAAO,CAAA;AACpB,MAAA,IAAI,MAAA,CAAOE,KAAAA,EAAM,CAAC,CAAA,EAAG;AAAA,IACvB;AACA,IAAA,CAAA,CAAE,CAAC,CAAA;AACH,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,OAAA,GAAUD,0BAAa,MAAS,CAAA;AACtC,EAAA,MAAM,OAAO,CAAA,EAAE;AACf,EAAAA,yBAAA,CAAa,OAAO,CAAA;AACpB,EAAA,MAAM,IAAA,GAAQ,EAAqB,IAAI,CAAA;AACvC,EAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAM,IAAI,CAAA,EAAG;AAClC,EAAA,CAAA,CAAE,IAAI,CAAA;AACR;AAqBO,SAAS,YAAA,CAAgB,cAAiB,OAAA,EAA0E;AACzH,EAAA,MAAM,CAAA,GAAIE,oBAAmB,YAAY,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,CAAA;AACf,EAAA,MAAM,KAAK,OAAA,EAAS,MAAA;AACpB,EAAA,MAAM,SAA0B,CAAC,CAAA,KAA4B,cAAA,CAAe,CAAA,EAAG,GAAG,EAAE,CAAA;AAEpF,EAAA,OAAO,CAAC,QAAQ,MAAM,CAAA;AACxB;AC/EO,SAAS,eAAkB,EAAA,EAAuC;AACvE,EAAA,OAAOC,sBAAY,EAAE,CAAA;AACvB","file":"chunk-QLPCVK7C.cjs","sourcesContent":["/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n if (typeof v !== 'function') {\n if (equals) {\n // Read current value without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // skip — values are equal\n }\n s(v);\n return;\n }\n\n // Functional update: read prev without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // skip — values are equal\n s(next);\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n return rawComputed(fn);\n}\n"]} |
| import { createComputed, createSignal } from './chunk-HLM5BZZQ.js'; | ||
| import { effectScope, effect, startBatch, endBatch, setActiveSub } from 'alien-signals'; | ||
| export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals'; | ||
| var currentRoot = null; | ||
| var rootStack = []; | ||
| function createRoot(fn) { | ||
| const scope = { disposers: [], scopeDispose: null }; | ||
| rootStack.push(currentRoot); | ||
| currentRoot = scope; | ||
| const dispose = () => { | ||
| if (scope.scopeDispose) { | ||
| try { | ||
| scope.scopeDispose(); | ||
| } catch { | ||
| } | ||
| scope.scopeDispose = null; | ||
| } | ||
| for (const d of scope.disposers) { | ||
| try { | ||
| d(); | ||
| } catch { | ||
| } | ||
| } | ||
| scope.disposers.length = 0; | ||
| }; | ||
| let result; | ||
| try { | ||
| scope.scopeDispose = effectScope(() => { | ||
| result = fn(dispose); | ||
| }); | ||
| } finally { | ||
| currentRoot = rootStack.pop() ?? null; | ||
| } | ||
| return result; | ||
| } | ||
| function registerDisposer(dispose) { | ||
| if (currentRoot) { | ||
| currentRoot.disposers.push(dispose); | ||
| } | ||
| } | ||
| function hasActiveRoot() { | ||
| return currentRoot !== null; | ||
| } | ||
| // src/reactive/cleanup.ts | ||
| var currentCleanupCollector = null; | ||
| function onCleanup(fn) { | ||
| currentCleanupCollector?.(fn); | ||
| } | ||
| function setCleanupCollector(collector) { | ||
| const prev = currentCleanupCollector; | ||
| currentCleanupCollector = collector; | ||
| return prev; | ||
| } | ||
| // src/reactive/dev.ts | ||
| var __DEV__ = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true; | ||
| var _errorHandler = null; | ||
| function onError(handler) { | ||
| _errorHandler = handler; | ||
| } | ||
| function reportError(error, source) { | ||
| if (_errorHandler) { | ||
| try { | ||
| _errorHandler(error, source ? { source } : {}); | ||
| } catch { | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| console.error(`[forma] ${source ?? "Unknown"} error:`, error); | ||
| } | ||
| } | ||
| var POOL_SIZE = 32; | ||
| var MAX_REENTRANT_RUNS = 100; | ||
| var pool = []; | ||
| for (let i = 0; i < POOL_SIZE; i++) pool.push([]); | ||
| var poolIdx = POOL_SIZE; | ||
| function acquireArray() { | ||
| if (poolIdx > 0) { | ||
| const arr = pool[--poolIdx]; | ||
| arr.length = 0; | ||
| return arr; | ||
| } | ||
| return []; | ||
| } | ||
| function releaseArray(arr) { | ||
| arr.length = 0; | ||
| if (poolIdx < POOL_SIZE) { | ||
| pool[poolIdx++] = arr; | ||
| } | ||
| } | ||
| function runCleanup(fn) { | ||
| if (fn === void 0) return; | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| function runCleanups(bag) { | ||
| if (bag === void 0) return; | ||
| for (let i = 0; i < bag.length; i++) { | ||
| try { | ||
| bag[i](); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| } | ||
| function internalEffect(fn) { | ||
| const dispose = effect(fn); | ||
| if (hasActiveRoot()) { | ||
| registerDisposer(dispose); | ||
| } | ||
| return dispose; | ||
| } | ||
| function createEffect(fn) { | ||
| const shouldRegister = hasActiveRoot(); | ||
| let cleanup; | ||
| let cleanupBag; | ||
| let nextCleanup; | ||
| let nextCleanupBag; | ||
| const addCleanup = (cb) => { | ||
| if (nextCleanupBag !== void 0) { | ||
| nextCleanupBag.push(cb); | ||
| return; | ||
| } | ||
| if (nextCleanup !== void 0) { | ||
| const bag = acquireArray(); | ||
| bag.push(nextCleanup, cb); | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = bag; | ||
| return; | ||
| } | ||
| nextCleanup = cb; | ||
| }; | ||
| let skipCleanupInfra = false; | ||
| let firstRun = true; | ||
| let running = false; | ||
| let rerunRequested = false; | ||
| const runOnce = () => { | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| if (skipCleanupInfra) { | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| } | ||
| return; | ||
| } | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = void 0; | ||
| const prevCollector = setCleanupCollector(addCleanup); | ||
| try { | ||
| const result = fn(); | ||
| if (typeof result === "function") { | ||
| addCleanup(result); | ||
| } | ||
| if (nextCleanup === void 0 && nextCleanupBag === void 0) { | ||
| if (firstRun) skipCleanupInfra = true; | ||
| return; | ||
| } | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } finally { | ||
| setCleanupCollector(prevCollector); | ||
| firstRun = false; | ||
| } | ||
| }; | ||
| const safeFn = () => { | ||
| if (running) { | ||
| rerunRequested = true; | ||
| return; | ||
| } | ||
| running = true; | ||
| try { | ||
| let reentrantRuns = 0; | ||
| do { | ||
| rerunRequested = false; | ||
| runOnce(); | ||
| if (rerunRequested) { | ||
| reentrantRuns++; | ||
| if (reentrantRuns >= MAX_REENTRANT_RUNS) { | ||
| reportError( | ||
| new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`), | ||
| "effect" | ||
| ); | ||
| rerunRequested = false; | ||
| } | ||
| } | ||
| } while (rerunRequested); | ||
| } finally { | ||
| running = false; | ||
| } | ||
| }; | ||
| const dispose = effect(safeFn); | ||
| let disposed = false; | ||
| const wrappedDispose = () => { | ||
| if (disposed) return; | ||
| disposed = true; | ||
| dispose(); | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| }; | ||
| if (shouldRegister) { | ||
| registerDisposer(wrappedDispose); | ||
| } | ||
| return wrappedDispose; | ||
| } | ||
| // src/reactive/memo.ts | ||
| var createMemo = createComputed; | ||
| function batch(fn) { | ||
| startBatch(); | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| endBatch(); | ||
| } | ||
| } | ||
| function untrack(fn) { | ||
| const prev = setActiveSub(void 0); | ||
| try { | ||
| return fn(); | ||
| } finally { | ||
| setActiveSub(prev); | ||
| } | ||
| } | ||
| // src/reactive/on.ts | ||
| function on(deps, fn, options) { | ||
| let prev; | ||
| let isFirst = true; | ||
| return () => { | ||
| const value2 = deps(); | ||
| if (options?.defer && isFirst) { | ||
| isFirst = false; | ||
| prev = value2; | ||
| return void 0; | ||
| } | ||
| const result = untrack(() => fn(value2, prev)); | ||
| prev = value2; | ||
| return result; | ||
| }; | ||
| } | ||
| // src/reactive/ref.ts | ||
| function createRef(initialValue) { | ||
| return { current: initialValue }; | ||
| } | ||
| // src/reactive/reducer.ts | ||
| function createReducer(reducer, initialState) { | ||
| const [state, setState] = createSignal(initialState); | ||
| const dispatch = (action) => { | ||
| setState((prev) => reducer(prev, action)); | ||
| }; | ||
| return [state, dispatch]; | ||
| } | ||
| // src/reactive/suspense-context.ts | ||
| var currentSuspenseContext = null; | ||
| var suspenseStack = []; | ||
| function pushSuspenseContext(ctx) { | ||
| suspenseStack.push(currentSuspenseContext); | ||
| currentSuspenseContext = ctx; | ||
| } | ||
| function popSuspenseContext() { | ||
| currentSuspenseContext = suspenseStack.pop() ?? null; | ||
| } | ||
| function getSuspenseContext() { | ||
| return currentSuspenseContext; | ||
| } | ||
| // src/reactive/resource.ts | ||
| function createResource(source, fetcher, options) { | ||
| const [data, setData] = createSignal(options?.initialValue); | ||
| const [loading, setLoading] = createSignal(false); | ||
| const [error, setError] = createSignal(void 0); | ||
| const suspenseCtx = getSuspenseContext(); | ||
| let abortController = null; | ||
| let fetchVersion = 0; | ||
| const doFetch = () => { | ||
| const sourceValue = untrack(source); | ||
| if (abortController) { | ||
| abortController.abort(); | ||
| } | ||
| const controller = new AbortController(); | ||
| abortController = controller; | ||
| const version = ++fetchVersion; | ||
| const isLatest = () => version === fetchVersion; | ||
| let suspensePending = false; | ||
| if (suspenseCtx) { | ||
| suspenseCtx.increment(); | ||
| suspensePending = true; | ||
| } | ||
| setLoading(true); | ||
| setError(void 0); | ||
| Promise.resolve(fetcher(sourceValue)).then((result) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| setData(() => result); | ||
| } | ||
| }).catch((err) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| if (err?.name !== "AbortError") { | ||
| setError(err); | ||
| } | ||
| } | ||
| }).finally(() => { | ||
| if (suspensePending) suspenseCtx?.decrement(); | ||
| if (isLatest()) { | ||
| setLoading(false); | ||
| if (abortController === controller) { | ||
| abortController = null; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| internalEffect(() => { | ||
| source(); | ||
| doFetch(); | ||
| }); | ||
| const resource = (() => data()); | ||
| resource.loading = loading; | ||
| resource.error = error; | ||
| resource.refetch = doFetch; | ||
| resource.mutate = (value2) => setData(() => value2); | ||
| return resource; | ||
| } | ||
| export { __DEV__, batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, internalEffect, on, onCleanup, onError, popSuspenseContext, pushSuspenseContext, reportError, untrack }; | ||
| //# sourceMappingURL=chunk-TBWZZ3SI.js.map | ||
| //# sourceMappingURL=chunk-TBWZZ3SI.js.map |
| {"version":3,"sources":["../src/reactive/root.ts","../src/reactive/cleanup.ts","../src/reactive/dev.ts","../src/reactive/effect.ts","../src/reactive/memo.ts","../src/reactive/batch.ts","../src/reactive/untrack.ts","../src/reactive/on.ts","../src/reactive/ref.ts","../src/reactive/reducer.ts","../src/reactive/suspense-context.ts","../src/reactive/resource.ts"],"names":["rawEffectScope","rawEffect","value"],"mappings":"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","file":"chunk-TBWZZ3SI.js","sourcesContent":["/**\n * Forma Reactive - Root\n *\n * Explicit reactive ownership scope. All effects created inside a root\n * are automatically disposed when the root is torn down.\n *\n * Uses alien-signals' `effectScope` under the hood for native graph-level\n * effect tracking, with a userland disposer list for non-effect cleanup\n * (e.g., event listeners, DOM references, timers).\n */\n\nimport { effectScope as rawEffectScope } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Root scope tracking\n// ---------------------------------------------------------------------------\n\nlet currentRoot: RootScope | null = null;\nconst rootStack: (RootScope | null)[] = [];\n\ninterface RootScope {\n /** Userland disposers (event listeners, DOM refs, timers, etc.) */\n disposers: (() => void)[];\n /** alien-signals effect scope dispose — tears down all reactive effects */\n scopeDispose: (() => void) | null;\n}\n\n/**\n * Create a reactive root scope.\n *\n * All effects created (via `createEffect`) inside the callback are tracked\n * at both the reactive graph level (via alien-signals effectScope) and the\n * userland level (via registerDisposer). The returned `dispose` function\n * tears down everything.\n *\n * ```ts\n * const dispose = createRoot(() => {\n * createEffect(() => console.log(count()));\n * createEffect(() => console.log(name()));\n * });\n * // later: dispose() stops both effects\n * ```\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const scope: RootScope = { disposers: [], scopeDispose: null };\n\n rootStack.push(currentRoot);\n currentRoot = scope;\n\n const dispose = () => {\n // Dispose alien-signals effect scope first (reactive graph cleanup)\n if (scope.scopeDispose) {\n try { scope.scopeDispose(); } catch { /* ensure userland disposers still run */ }\n scope.scopeDispose = null;\n }\n // Then run userland disposers\n for (const d of scope.disposers) {\n try { d(); } catch { /* ensure all disposers run */ }\n }\n scope.disposers.length = 0;\n };\n\n let result: T;\n try {\n // Wrap in alien-signals effectScope for native effect tracking\n scope.scopeDispose = rawEffectScope(() => {\n result = fn(dispose);\n });\n } finally {\n currentRoot = rootStack.pop() ?? null;\n }\n\n return result!;\n}\n\n/**\n * @internal — called by createEffect to register disposers in the current root.\n */\nexport function registerDisposer(dispose: () => void): void {\n if (currentRoot) {\n currentRoot.disposers.push(dispose);\n }\n}\n\n/**\n * @internal — check if we're inside a root scope.\n */\nexport function hasActiveRoot(): boolean {\n return currentRoot !== null;\n}\n","/**\n * Forma Reactive - Cleanup\n *\n * Register cleanup functions within reactive scopes.\n * Inspired by SolidJS onCleanup().\n */\n\n// ---------------------------------------------------------------------------\n// Cleanup context tracking\n// ---------------------------------------------------------------------------\n\ntype CleanupCollector = ((fn: () => void) => void) | null;\n\nlet currentCleanupCollector: CleanupCollector = null;\n\n/**\n * Register a cleanup function in the current reactive scope.\n * The cleanup runs before the effect re-executes and on disposal.\n *\n * More composable than returning a cleanup from the effect function,\n * since it can be called from helper functions.\n *\n * ```ts\n * createEffect(() => {\n * const timer = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timer));\n * });\n * ```\n */\nexport function onCleanup(fn: () => void): void {\n currentCleanupCollector?.(fn);\n}\n\n/**\n * @internal — Set the cleanup collector for the current effect execution.\n */\nexport function setCleanupCollector(collector: CleanupCollector): CleanupCollector {\n const prev = currentCleanupCollector;\n currentCleanupCollector = collector;\n return prev;\n}\n","/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by bundler (tsup define). Defaults to true for unbundled usage.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\nexport const __DEV__: boolean = typeof process !== 'undefined'\n ? (process!.env?.NODE_ENV !== 'production')\n : true;\n\n// ---------------------------------------------------------------------------\n// Global error handler\n// ---------------------------------------------------------------------------\n\n/** Callback signature for the global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nlet _errorHandler: ErrorHandler | null = null;\n\n/**\n * Install a global error handler for FormaJS reactive errors.\n * Called when effects, computeds, or event handlers throw.\n *\n * ```ts\n * onError((err, info) => {\n * console.error(`[${info?.source}]`, err);\n * Sentry.captureException(err);\n * });\n * ```\n */\nexport function onError(handler: ErrorHandler): void {\n _errorHandler = handler;\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n if (_errorHandler) {\n try { _errorHandler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (__DEV__) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Effect\n *\n * Side-effectful reactive computation that auto-tracks signal dependencies.\n * Backed by alien-signals for automatic dependency tracking.\n *\n * TC39 Signals equivalent: Signal.subtle.Watcher (effect is a userland concept)\n */\n\nimport { effect as rawEffect } from 'alien-signals';\nimport { hasActiveRoot, registerDisposer } from './root.js';\nimport { setCleanupCollector } from './cleanup.js';\nimport { reportError } from './dev.js';\n\n// ---------------------------------------------------------------------------\n// Cleanup array pool — avoids allocating a new array every effect re-run\n// ---------------------------------------------------------------------------\n\nconst POOL_SIZE = 32;\nconst MAX_REENTRANT_RUNS = 100;\nconst pool: (() => void)[][] = [];\nfor (let i = 0; i < POOL_SIZE; i++) pool.push([]);\nlet poolIdx = POOL_SIZE;\n\nfunction acquireArray(): (() => void)[] {\n if (poolIdx > 0) {\n const arr = pool[--poolIdx]!;\n arr.length = 0;\n return arr;\n }\n return [];\n}\n\nfunction releaseArray(arr: (() => void)[]): void {\n arr.length = 0;\n if (poolIdx < POOL_SIZE) {\n pool[poolIdx++] = arr;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified cleanup runner — single function for both re-run and dispose paths\n// ---------------------------------------------------------------------------\n\nfunction runCleanup(fn: (() => void) | undefined): void {\n if (fn === undefined) return;\n try {\n fn();\n } catch (e) {\n reportError(e, 'effect cleanup');\n }\n}\n\nfunction runCleanups(bag: (() => void)[] | undefined): void {\n if (bag === undefined) return;\n for (let i = 0; i < bag.length; i++) {\n try { bag[i]!(); } catch (e) { reportError(e, 'effect cleanup'); }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive effect that auto-tracks signal dependencies.\n *\n * The provided function runs immediately and re-runs whenever any signal it\n * reads changes. If the function returns a cleanup function, that cleanup is\n * called before each re-run and on disposal.\n *\n * Additionally, `onCleanup()` can be called inside the effect to register\n * cleanup functions composably.\n *\n * Returns a dispose function that stops the effect.\n */\n/**\n * @internal — Lightweight effect for Forma's internal DOM bindings.\n *\n * Bypasses createEffect's cleanup infrastructure (pool, collector, error\n * reporting, engine compression) since internal effects never use\n * onCleanup() or return cleanup functions. This saves ~4 function calls\n * per effect creation and per re-run.\n *\n * ONLY use for effects that:\n * 1. Never call onCleanup()\n * 2. Never return a cleanup function\n * 3. Contain simple \"read signal → write DOM\" logic\n */\nexport function internalEffect(fn: () => void): () => void {\n const dispose = rawEffect(fn);\n if (hasActiveRoot()) {\n registerDisposer(dispose);\n }\n return dispose;\n}\n\nexport function createEffect(fn: () => void | (() => void)): () => void {\n const shouldRegister = hasActiveRoot();\n\n // Most effects have zero or one cleanup. Track the single-cleanup case\n // without array allocation; only promote to pooled array when needed.\n let cleanup: (() => void) | undefined;\n let cleanupBag: (() => void)[] | undefined;\n let nextCleanup: (() => void) | undefined;\n let nextCleanupBag: (() => void)[] | undefined;\n\n const addCleanup = (cb: () => void) => {\n if (nextCleanupBag !== undefined) {\n nextCleanupBag.push(cb);\n return;\n }\n if (nextCleanup !== undefined) {\n const bag = acquireArray();\n bag.push(nextCleanup, cb);\n nextCleanup = undefined;\n nextCleanupBag = bag;\n return;\n }\n nextCleanup = cb;\n };\n\n // \"Engine Compression\" exploit: most effects never use cleanup (onCleanup()\n // or return value). After the first clean run, we know this effect is\n // \"cleanup-free\" and can skip the entire cleanup infrastructure on re-runs.\n // This saves 4 function calls per re-run: acquireArray, setCleanupCollector,\n // releaseArray, and bag inspection. Like the engine compression ratio exploit —\n // the \"rules\" say we should always prepare for cleanup, but we measure at\n // \"room temperature\" (first run) and exploit the gap.\n let skipCleanupInfra = false;\n let firstRun = true;\n let running = false;\n let rerunRequested = false;\n\n const runOnce = () => {\n // Run and clear previous cleanups (only if any exist).\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n\n // Ultra-fast path: this effect has proven it doesn't use cleanup.\n // Skip acquireArray + setCleanupCollector + bag checks + releaseArray.\n if (skipCleanupInfra) {\n try { fn(); } catch (e) { reportError(e, 'effect'); }\n return;\n }\n\n nextCleanup = undefined;\n nextCleanupBag = undefined;\n\n // Full path: install collector for onCleanup() calls.\n const prevCollector = setCleanupCollector(addCleanup);\n\n try {\n const result = fn();\n\n if (typeof result === 'function') {\n addCleanup(result as () => void);\n }\n\n // Hot path: no cleanups registered or returned — enable ultra-fast path.\n if (nextCleanup === undefined && nextCleanupBag === undefined) {\n // First clean run → enable ultra-fast path for all subsequent runs\n if (firstRun) skipCleanupInfra = true;\n return;\n }\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } catch (e) {\n reportError(e, 'effect');\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } finally {\n setCleanupCollector(prevCollector);\n firstRun = false;\n }\n };\n\n const safeFn = () => {\n if (running) {\n rerunRequested = true;\n return;\n }\n\n running = true;\n try {\n let reentrantRuns = 0;\n do {\n rerunRequested = false;\n runOnce();\n if (rerunRequested) {\n reentrantRuns++;\n if (reentrantRuns >= MAX_REENTRANT_RUNS) {\n reportError(\n new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`),\n 'effect',\n );\n rerunRequested = false;\n }\n }\n } while (rerunRequested);\n } finally {\n running = false;\n }\n };\n\n const dispose = rawEffect(safeFn);\n\n // Wrap dispose to also run final cleanups\n let disposed = false;\n const wrappedDispose = () => {\n if (disposed) return;\n disposed = true;\n dispose();\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n };\n\n // Register in current root scope (if any)\n if (shouldRegister) {\n registerDisposer(wrappedDispose);\n }\n\n return wrappedDispose;\n}\n","/**\n * Forma Reactive - Memo\n *\n * Alias for createComputed with SolidJS/React-familiar naming.\n * A memoized derived value that only recomputes when its dependencies change.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { createComputed } from './computed.js';\n\n/**\n * Create a memoized computed value.\n * Identical to `createComputed` — provided for React/SolidJS familiarity.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), both createComputed and createMemo in FormaJS are lazy\n * cached derivations — equivalent to SolidJS's createMemo. They are\n * identical.\n *\n * The computation runs lazily and caches the result. It only recomputes\n * when a signal it reads during computation changes.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createMemo(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n */\nexport const createMemo: typeof createComputed = createComputed;\n","/**\n * Forma Reactive - Batch\n *\n * Groups multiple signal updates and defers effect execution until the\n * outermost batch completes. Prevents intermediate re-renders.\n * Backed by alien-signals.\n *\n * TC39 Signals: no built-in batch — this is a userland optimization.\n */\n\nimport { startBatch, endBatch } from 'alien-signals';\n\n/**\n * Group multiple signal updates so that effects only run once after all\n * updates have been applied.\n *\n * ```ts\n * batch(() => {\n * setA(1);\n * setB(2);\n * // effects that depend on A and B won't run yet\n * });\n * // effects run here, once\n * ```\n *\n * Batches are nestable — only the outermost batch triggers the flush.\n */\nexport function batch(fn: () => void): void {\n startBatch();\n try {\n fn();\n } finally {\n endBatch();\n }\n}\n","/**\n * Forma Reactive - Untrack\n *\n * Read signals without subscribing to them in the reactive graph.\n * Essential for reading values inside effects without creating dependencies.\n *\n * TC39 Signals equivalent: Signal.subtle.untrack()\n */\n\nimport { setActiveSub } from 'alien-signals';\n\n/**\n * Execute a function without tracking signal reads.\n * Any signals read inside `fn` will NOT become dependencies of the\n * surrounding effect or computed.\n *\n * ```ts\n * createEffect(() => {\n * const a = count(); // tracked — effect re-runs when count changes\n * const b = untrack(() => other()); // NOT tracked — effect ignores other changes\n * });\n * ```\n */\nexport function untrack<T>(fn: () => T): T {\n const prev = setActiveSub(undefined);\n try {\n return fn();\n } finally {\n setActiveSub(prev);\n }\n}\n","/**\n * Forma Reactive - On\n *\n * Explicit dependency tracking for effects.\n * Only re-runs when the specified signals change, ignoring all other reads.\n *\n * SolidJS equivalent: on()\n * Vue equivalent: watch() with explicit deps\n * React equivalent: useEffect dependency array\n */\n\nimport { untrack } from './untrack.js';\n\n/**\n * Create a tracked effect body that only fires when specific dependencies change.\n *\n * Wraps a function so that only the `deps` signals are tracked.\n * All signal reads inside `fn` are untracked (won't cause re-runs).\n *\n * Use with `createEffect`:\n *\n * ```ts\n * const [a, setA] = createSignal(1);\n * const [b, setB] = createSignal(2);\n *\n * // Only re-runs when `a` changes, NOT when `b` changes:\n * createEffect(on(a, (value, prev) => {\n * console.log(`a changed: ${prev} → ${value}, b is ${b()}`);\n * }));\n *\n * setA(10); // fires: \"a changed: 1 → 10, b is 2\"\n * setB(20); // does NOT fire\n * ```\n *\n * Multiple dependencies:\n * ```ts\n * createEffect(on(\n * () => [a(), b()] as const,\n * ([aVal, bVal], prev) => { ... }\n * ));\n * ```\n */\nexport function on<T, U>(\n deps: () => T,\n fn: (value: T, prev: T | undefined) => U,\n options?: { defer?: boolean },\n): () => U | undefined {\n let prev: T | undefined;\n let isFirst = true;\n\n return () => {\n // Track only the deps\n const value = deps();\n\n if (options?.defer && isFirst) {\n isFirst = false;\n prev = value;\n return undefined;\n }\n\n // Run the body untracked so it doesn't add extra dependencies\n const result = untrack(() => fn(value, prev));\n prev = value;\n return result;\n };\n}\n","/**\n * Forma Reactive - Ref\n *\n * Mutable container that does NOT trigger reactivity.\n * Use for DOM references, previous values, instance variables —\n * anything that needs to persist across effect re-runs without\n * causing re-execution.\n *\n * React equivalent: useRef\n * SolidJS equivalent: (none — uses plain variables in setup)\n */\n\n/** A mutable container that does NOT trigger reactivity when written to. */\nexport interface Ref<T> {\n /** The stored value. Mutating this does not notify effects. */\n current: T;\n}\n\n/**\n * Create a mutable ref container.\n *\n * Unlike signals, writing to `.current` does NOT trigger effects.\n * Use when you need a stable reference across reactive scopes.\n *\n * ```ts\n * const timerRef = createRef<number | null>(null);\n *\n * createEffect(() => {\n * timerRef.current = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timerRef.current!));\n * });\n *\n * // DOM ref pattern:\n * const elRef = createRef<HTMLElement | null>(null);\n * h('div', { ref: (el) => { elRef.current = el; } });\n * ```\n */\nexport function createRef<T>(initialValue: T): Ref<T> {\n return { current: initialValue };\n}\n","/**\n * Forma Reactive - Reducer\n *\n * State machine pattern — dispatch actions to a pure reducer function.\n * Fine-grained: only the resulting state signal is reactive.\n *\n * React equivalent: useReducer\n * SolidJS equivalent: (none — uses createSignal + helpers)\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\n\n/** A function that dispatches an action to a reducer. */\nexport type Dispatch<A> = (action: A) => void;\n\n/**\n * Create a reducer — predictable state updates via dispatched actions.\n *\n * The reducer function must be pure: `(state, action) => newState`.\n * Returns a [state, dispatch] tuple.\n *\n * ```ts\n * type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };\n *\n * const [count, dispatch] = createReducer(\n * (state: number, action: Action) => {\n * switch (action.type) {\n * case 'increment': return state + 1;\n * case 'decrement': return state - 1;\n * case 'reset': return 0;\n * }\n * },\n * 0,\n * );\n *\n * dispatch({ type: 'increment' }); // count() === 1\n * dispatch({ type: 'increment' }); // count() === 2\n * dispatch({ type: 'reset' }); // count() === 0\n * ```\n */\nexport function createReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initialState: S,\n): [state: SignalGetter<S>, dispatch: Dispatch<A>] {\n const [state, setState] = createSignal(initialState);\n\n const dispatch: Dispatch<A> = (action) => {\n setState((prev) => reducer(prev, action));\n };\n\n return [state, dispatch];\n}\n","/**\n * Forma Reactive - Suspense Context\n *\n * Shared context stack for Suspense boundaries. Lives in the reactive layer\n * (not DOM) so that createResource can import it without circular dependencies.\n *\n * The pattern mirrors the lifecycle context stack in component/define.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Context object for a Suspense boundary, tracking pending async resources. */\nexport interface SuspenseContext {\n /** Increment when a resource starts loading inside this boundary. */\n increment(): void;\n /** Decrement when a resource resolves/rejects inside this boundary. */\n decrement(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Context stack\n// ---------------------------------------------------------------------------\n\nlet currentSuspenseContext: SuspenseContext | null = null;\nconst suspenseStack: (SuspenseContext | null)[] = [];\n\nexport function pushSuspenseContext(ctx: SuspenseContext): void {\n suspenseStack.push(currentSuspenseContext);\n currentSuspenseContext = ctx;\n}\n\nexport function popSuspenseContext(): void {\n currentSuspenseContext = suspenseStack.pop() ?? null;\n}\n\n/** Get the current Suspense context (if any). Called by createResource. */\nexport function getSuspenseContext(): SuspenseContext | null {\n return currentSuspenseContext;\n}\n","/**\n * Forma Reactive - Resource\n *\n * Async data fetching primitive with reactive loading/error state.\n * Tracks a source signal and refetches when it changes.\n *\n * SolidJS equivalent: createResource\n * React equivalent: use() + Suspense (React 19), or useSWR/react-query\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\nimport { internalEffect } from './effect.js';\nimport { untrack } from './untrack.js';\nimport { getSuspenseContext } from './suspense-context.js';\n\n/** An async data resource with reactive loading and error state. */\nexport interface Resource<T> {\n /** The resolved data (or undefined while loading). */\n (): T | undefined;\n /** True while the fetcher is running. */\n loading: SignalGetter<boolean>;\n /** The error if the fetcher rejected (or undefined). */\n error: SignalGetter<unknown>;\n /** Manually refetch with the current source value. */\n refetch: () => void;\n /** Manually set the data (overrides fetcher result). */\n mutate: (value: T | undefined) => void;\n}\n\n/** Options for {@link createResource}. */\nexport interface ResourceOptions<T> {\n /** Initial value before first fetch resolves. */\n initialValue?: T;\n}\n\n/**\n * Create an async resource that fetches data reactively.\n *\n * When `source` changes, the fetcher re-runs automatically.\n * Provides reactive `loading` and `error` signals.\n *\n * ```ts\n * const [userId, setUserId] = createSignal(1);\n *\n * const user = createResource(\n * userId, // source signal\n * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher\n * );\n *\n * internalEffect(() => {\n * if (user.loading()) console.log('Loading...');\n * else if (user.error()) console.log('Error:', user.error());\n * else console.log('User:', user());\n * });\n *\n * setUserId(2); // automatically refetches\n * ```\n *\n * Without a source signal (static fetch):\n * ```ts\n * const posts = createResource(\n * () => true, // constant source — fetches once\n * () => fetch('/api/posts').then(r => r.json()),\n * );\n * ```\n */\nexport function createResource<T, S = true>(\n source: SignalGetter<S>,\n fetcher: (source: S) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> {\n const [data, setData] = createSignal<T | undefined>(options?.initialValue);\n const [loading, setLoading] = createSignal(false);\n const [error, setError] = createSignal<unknown>(undefined);\n\n // Capture the Suspense context at creation time (not at fetch time).\n // This is critical because the Suspense boundary pushes/pops its context\n // synchronously during children() execution.\n const suspenseCtx = getSuspenseContext();\n\n let abortController: AbortController | null = null;\n let fetchVersion = 0;\n\n const doFetch = () => {\n // Read source outside tracking to get current value\n const sourceValue = untrack(source);\n\n // Abort previous in-flight request\n if (abortController) {\n abortController.abort();\n }\n const controller = new AbortController();\n abortController = controller;\n\n const version = ++fetchVersion;\n const isLatest = () => version === fetchVersion;\n let suspensePending = false;\n\n // Notify Suspense boundary that a fetch has started\n if (suspenseCtx) {\n suspenseCtx.increment();\n suspensePending = true;\n }\n\n setLoading(true);\n setError(undefined);\n\n Promise.resolve(fetcher(sourceValue))\n .then((result) => {\n // Only apply if this is still the latest fetch and wasn't aborted.\n if (isLatest() && !controller.signal.aborted) {\n setData(() => result);\n }\n })\n .catch((err) => {\n if (isLatest() && !controller.signal.aborted) {\n // Ignore abort errors\n if (err?.name !== 'AbortError') {\n setError(err);\n }\n }\n })\n .finally(() => {\n if (suspensePending) suspenseCtx?.decrement();\n if (isLatest()) {\n setLoading(false);\n if (abortController === controller) {\n abortController = null; // Release controller for GC\n }\n }\n });\n };\n\n // Auto-fetch when source changes\n internalEffect(() => {\n source(); // track the source signal\n doFetch();\n });\n\n // Build the resource object\n const resource = (() => data()) as Resource<T>;\n resource.loading = loading;\n resource.error = error;\n resource.refetch = doFetch;\n resource.mutate = (value) => setData(() => value);\n\n return resource;\n}\n"]} |
| import { S as SignalGetter } from './signal-C9v4akyJ.js'; | ||
| /** | ||
| * Forma Reactive - Resource | ||
| * | ||
| * Async data fetching primitive with reactive loading/error state. | ||
| * Tracks a source signal and refetches when it changes. | ||
| * | ||
| * SolidJS equivalent: createResource | ||
| * React equivalent: use() + Suspense (React 19), or useSWR/react-query | ||
| */ | ||
| /** An async data resource with reactive loading and error state. */ | ||
| interface Resource<T> { | ||
| /** The resolved data (or undefined while loading). */ | ||
| (): T | undefined; | ||
| /** True while the fetcher is running. */ | ||
| loading: SignalGetter<boolean>; | ||
| /** The error if the fetcher rejected (or undefined). */ | ||
| error: SignalGetter<unknown>; | ||
| /** Manually refetch with the current source value. */ | ||
| refetch: () => void; | ||
| /** Manually set the data (overrides fetcher result). */ | ||
| mutate: (value: T | undefined) => void; | ||
| } | ||
| /** Options for {@link createResource}. */ | ||
| interface ResourceOptions<T> { | ||
| /** Initial value before first fetch resolves. */ | ||
| initialValue?: T; | ||
| } | ||
| /** | ||
| * Create an async resource that fetches data reactively. | ||
| * | ||
| * When `source` changes, the fetcher re-runs automatically. | ||
| * Provides reactive `loading` and `error` signals. | ||
| * | ||
| * ```ts | ||
| * const [userId, setUserId] = createSignal(1); | ||
| * | ||
| * const user = createResource( | ||
| * userId, // source signal | ||
| * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher | ||
| * ); | ||
| * | ||
| * internalEffect(() => { | ||
| * if (user.loading()) console.log('Loading...'); | ||
| * else if (user.error()) console.log('Error:', user.error()); | ||
| * else console.log('User:', user()); | ||
| * }); | ||
| * | ||
| * setUserId(2); // automatically refetches | ||
| * ``` | ||
| * | ||
| * Without a source signal (static fetch): | ||
| * ```ts | ||
| * const posts = createResource( | ||
| * () => true, // constant source — fetches once | ||
| * () => fetch('/api/posts').then(r => r.json()), | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S) => Promise<T>, options?: ResourceOptions<T>): Resource<T>; | ||
| export { type Resource as R, type ResourceOptions as a, createResource as c }; |
| import { S as SignalGetter } from './signal-C9v4akyJ.cjs'; | ||
| /** | ||
| * Forma Reactive - Resource | ||
| * | ||
| * Async data fetching primitive with reactive loading/error state. | ||
| * Tracks a source signal and refetches when it changes. | ||
| * | ||
| * SolidJS equivalent: createResource | ||
| * React equivalent: use() + Suspense (React 19), or useSWR/react-query | ||
| */ | ||
| /** An async data resource with reactive loading and error state. */ | ||
| interface Resource<T> { | ||
| /** The resolved data (or undefined while loading). */ | ||
| (): T | undefined; | ||
| /** True while the fetcher is running. */ | ||
| loading: SignalGetter<boolean>; | ||
| /** The error if the fetcher rejected (or undefined). */ | ||
| error: SignalGetter<unknown>; | ||
| /** Manually refetch with the current source value. */ | ||
| refetch: () => void; | ||
| /** Manually set the data (overrides fetcher result). */ | ||
| mutate: (value: T | undefined) => void; | ||
| } | ||
| /** Options for {@link createResource}. */ | ||
| interface ResourceOptions<T> { | ||
| /** Initial value before first fetch resolves. */ | ||
| initialValue?: T; | ||
| } | ||
| /** | ||
| * Create an async resource that fetches data reactively. | ||
| * | ||
| * When `source` changes, the fetcher re-runs automatically. | ||
| * Provides reactive `loading` and `error` signals. | ||
| * | ||
| * ```ts | ||
| * const [userId, setUserId] = createSignal(1); | ||
| * | ||
| * const user = createResource( | ||
| * userId, // source signal | ||
| * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher | ||
| * ); | ||
| * | ||
| * internalEffect(() => { | ||
| * if (user.loading()) console.log('Loading...'); | ||
| * else if (user.error()) console.log('Error:', user.error()); | ||
| * else console.log('User:', user()); | ||
| * }); | ||
| * | ||
| * setUserId(2); // automatically refetches | ||
| * ``` | ||
| * | ||
| * Without a source signal (static fetch): | ||
| * ```ts | ||
| * const posts = createResource( | ||
| * () => true, // constant source — fetches once | ||
| * () => fetch('/api/posts').then(r => r.json()), | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S) => Promise<T>, options?: ResourceOptions<T>): Resource<T>; | ||
| export { type Resource as R, type ResourceOptions as a, createResource as c }; |
| /** | ||
| * Forma Reactive - Signal | ||
| * | ||
| * Fine-grained reactive primitive backed by alien-signals. | ||
| * API: createSignal returns [getter, setter] tuple following SolidJS conventions. | ||
| * | ||
| * TC39 Signals equivalent: Signal.State | ||
| */ | ||
| /** A function that reads the current value of a signal. */ | ||
| type SignalGetter<T> = () => T; | ||
| /** A function that updates a signal — accepts a value or an updater function. */ | ||
| type SignalSetter<T> = (v: T | ((prev: T) => T)) => void; | ||
| interface SignalOptions<T> { | ||
| /** Debug name — attached to getter in dev mode for devtools inspection. */ | ||
| name?: string; | ||
| /** | ||
| * Custom equality check. When provided, the setter will read the current | ||
| * value and only update the signal if `equals(prev, next)` returns `false`. | ||
| * | ||
| * Default: none (alien-signals uses strict inequality `!==` internally). | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * | ||
| * setPos({ x: 0, y: 0 }); // skipped — equals returns true | ||
| * setPos({ x: 1, y: 0 }); // applied — equals returns false | ||
| * ``` | ||
| */ | ||
| equals?: (prev: T, next: T) => boolean; | ||
| } | ||
| /** | ||
| * Create a reactive signal. | ||
| * | ||
| * ```ts | ||
| * const [count, setCount] = createSignal(0); | ||
| * console.log(count()); // 0 | ||
| * setCount(1); | ||
| * setCount(prev => prev + 1); | ||
| * ``` | ||
| * | ||
| * With custom equality: | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>]; | ||
| export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c }; |
| /** | ||
| * Forma Reactive - Signal | ||
| * | ||
| * Fine-grained reactive primitive backed by alien-signals. | ||
| * API: createSignal returns [getter, setter] tuple following SolidJS conventions. | ||
| * | ||
| * TC39 Signals equivalent: Signal.State | ||
| */ | ||
| /** A function that reads the current value of a signal. */ | ||
| type SignalGetter<T> = () => T; | ||
| /** A function that updates a signal — accepts a value or an updater function. */ | ||
| type SignalSetter<T> = (v: T | ((prev: T) => T)) => void; | ||
| interface SignalOptions<T> { | ||
| /** Debug name — attached to getter in dev mode for devtools inspection. */ | ||
| name?: string; | ||
| /** | ||
| * Custom equality check. When provided, the setter will read the current | ||
| * value and only update the signal if `equals(prev, next)` returns `false`. | ||
| * | ||
| * Default: none (alien-signals uses strict inequality `!==` internally). | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * | ||
| * setPos({ x: 0, y: 0 }); // skipped — equals returns true | ||
| * setPos({ x: 1, y: 0 }); // applied — equals returns false | ||
| * ``` | ||
| */ | ||
| equals?: (prev: T, next: T) => boolean; | ||
| } | ||
| /** | ||
| * Create a reactive signal. | ||
| * | ||
| * ```ts | ||
| * const [count, setCount] = createSignal(0); | ||
| * console.log(count()); // 0 | ||
| * setCount(1); | ||
| * setCount(prev => prev + 1); | ||
| * ``` | ||
| * | ||
| * With custom equality: | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>]; | ||
| export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c }; |
+11
-11
| 'use strict'; | ||
| var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs'); | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| var chunkBARF67I6_cjs = require('./chunk-BARF67I6.cjs'); | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
| // src/http/fetch.ts | ||
| function createFetch(url, options) { | ||
| const [data, setData] = chunk3U57L2TY_cjs.createSignal(null); | ||
| const [error, setError] = chunk3U57L2TY_cjs.createSignal(null); | ||
| const [loading, setLoading] = chunk3U57L2TY_cjs.createSignal(false); | ||
| const [data, setData] = chunkQLPCVK7C_cjs.createSignal(null); | ||
| const [error, setError] = chunkQLPCVK7C_cjs.createSignal(null); | ||
| const [loading, setLoading] = chunkQLPCVK7C_cjs.createSignal(false); | ||
| let currentController = null; | ||
@@ -60,3 +60,3 @@ function resolveURL() { | ||
| if (typeof url === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| url(); | ||
@@ -90,5 +90,5 @@ void execute(); | ||
| function createSSE(url, options) { | ||
| const [data, setData] = chunk3U57L2TY_cjs.createSignal(null); | ||
| const [error, setError] = chunk3U57L2TY_cjs.createSignal(null); | ||
| const [connected, setConnected] = chunk3U57L2TY_cjs.createSignal(false); | ||
| const [data, setData] = chunkQLPCVK7C_cjs.createSignal(null); | ||
| const [error, setError] = chunkQLPCVK7C_cjs.createSignal(null); | ||
| const [connected, setConnected] = chunkQLPCVK7C_cjs.createSignal(false); | ||
| const source = new EventSource(url, { | ||
@@ -147,4 +147,4 @@ withCredentials: options?.withCredentials ?? false | ||
| const maxReconnects = options?.maxReconnects ?? 5; | ||
| const [data, setData] = chunk3U57L2TY_cjs.createSignal(null); | ||
| const [status, setStatus] = chunk3U57L2TY_cjs.createSignal("connecting"); | ||
| const [data, setData] = chunkQLPCVK7C_cjs.createSignal(null); | ||
| const [status, setStatus] = chunkQLPCVK7C_cjs.createSignal("connecting"); | ||
| const handlers = /* @__PURE__ */ new Set(); | ||
@@ -151,0 +151,0 @@ let socket = null; |
+2
-2
@@ -1,3 +0,3 @@ | ||
| import { internalEffect } from './chunk-OUVOAYIO.js'; | ||
| import { createSignal } from './chunk-OZCHIVAZ.js'; | ||
| import { internalEffect } from './chunk-TBWZZ3SI.js'; | ||
| import { createSignal } from './chunk-HLM5BZZQ.js'; | ||
@@ -4,0 +4,0 @@ // src/http/fetch.ts |
+61
-61
| 'use strict'; | ||
| var chunkSZSKW57A_cjs = require('./chunk-SZSKW57A.cjs'); | ||
| var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs'); | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| var chunk7L3KHGEA_cjs = require('./chunk-7L3KHGEA.cjs'); | ||
| var chunkBARF67I6_cjs = require('./chunk-BARF67I6.cjs'); | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
@@ -11,3 +11,3 @@ // src/dom/text.ts | ||
| const node = new Text(""); | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| node.data = value(); | ||
@@ -28,8 +28,8 @@ }); | ||
| if (target.hasAttribute("data-forma-ssr")) { | ||
| chunkDCTOXHPF_cjs.createRoot((dispose) => { | ||
| chunkBARF67I6_cjs.createRoot((dispose) => { | ||
| disposeRoot = dispose; | ||
| chunkSZSKW57A_cjs.hydrateIsland(component, target); | ||
| chunk7L3KHGEA_cjs.hydrateIsland(component, target); | ||
| }); | ||
| } else { | ||
| const dom = chunkDCTOXHPF_cjs.createRoot((dispose) => { | ||
| const dom = chunkBARF67I6_cjs.createRoot((dispose) => { | ||
| disposeRoot = dispose; | ||
@@ -60,3 +60,3 @@ return component(); | ||
| let currentMatch = UNSET; | ||
| const switchDispose = chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const switchDispose = chunkBARF67I6_cjs.internalEffect(() => { | ||
| const val = value(); | ||
@@ -96,5 +96,5 @@ if (val === currentMatch) return; | ||
| let branchDispose; | ||
| const node = chunkDCTOXHPF_cjs.createRoot((dispose) => { | ||
| const node = chunkBARF67I6_cjs.createRoot((dispose) => { | ||
| branchDispose = dispose; | ||
| return chunkDCTOXHPF_cjs.untrack(() => matchedCase.render()); | ||
| return chunkBARF67I6_cjs.untrack(() => matchedCase.render()); | ||
| }); | ||
@@ -142,3 +142,3 @@ entry = { node, dispose: branchDispose }; | ||
| }; | ||
| chunkDCTOXHPF_cjs.createEffect(() => { | ||
| chunkBARF67I6_cjs.createEffect(() => { | ||
| const node = children2(); | ||
@@ -162,5 +162,5 @@ removeMountedNode(); | ||
| fragment2.appendChild(endMarker); | ||
| const [retryCount, setRetryCount] = chunk3U57L2TY_cjs.createSignal(0); | ||
| const [retryCount, setRetryCount] = chunkQLPCVK7C_cjs.createSignal(0); | ||
| let currentNode = null; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| retryCount(); | ||
@@ -193,3 +193,3 @@ const parent2 = startMarker.parentNode; | ||
| fragment2.appendChild(endMarker); | ||
| const [pending, setPending] = chunk3U57L2TY_cjs.createSignal(0); | ||
| const [pending, setPending] = chunkQLPCVK7C_cjs.createSignal(0); | ||
| let currentNode = null; | ||
@@ -206,9 +206,9 @@ let resolvedNode = null; | ||
| }; | ||
| chunkDCTOXHPF_cjs.pushSuspenseContext(ctx); | ||
| chunkBARF67I6_cjs.pushSuspenseContext(ctx); | ||
| try { | ||
| resolvedNode = children2(); | ||
| } finally { | ||
| chunkDCTOXHPF_cjs.popSuspenseContext(); | ||
| chunkBARF67I6_cjs.popSuspenseContext(); | ||
| } | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const parent2 = startMarker.parentNode; | ||
@@ -280,3 +280,3 @@ if (!parent2) return; | ||
| if (!hydrateFn) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[forma] No hydrate function for island "${componentName}" (id=${id})`); | ||
| if (chunkBARF67I6_cjs.__DEV__) console.warn(`[forma] No hydrate function for island "${componentName}" (id=${id})`); | ||
| root.setAttribute("data-forma-status", "error"); | ||
@@ -337,4 +337,4 @@ continue; | ||
| let activeRoot = root; | ||
| chunkDCTOXHPF_cjs.createRoot((dispose) => { | ||
| activeRoot = chunkSZSKW57A_cjs.hydrateIsland(() => hydrateFn(root, props), root); | ||
| chunkBARF67I6_cjs.createRoot((dispose) => { | ||
| activeRoot = chunk7L3KHGEA_cjs.hydrateIsland(() => hydrateFn(root, props), root); | ||
| activeRoot.__formaDispose = dispose; | ||
@@ -344,3 +344,3 @@ }); | ||
| } catch (err) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.error(`[forma] Island "${componentName}" (id=${id}) failed:`, err); | ||
| if (chunkBARF67I6_cjs.__DEV__) console.error(`[forma] Island "${componentName}" (id=${id}) failed:`, err); | ||
| root.setAttribute("data-forma-status", "error"); | ||
@@ -394,3 +394,3 @@ } | ||
| } catch (e) { | ||
| chunkDCTOXHPF_cjs.reportError(e, "onUnmount"); | ||
| chunkBARF67I6_cjs.reportError(e, "onUnmount"); | ||
| } | ||
@@ -402,3 +402,3 @@ } | ||
| } catch (e) { | ||
| chunkDCTOXHPF_cjs.reportError(e, "component disposer"); | ||
| chunkBARF67I6_cjs.reportError(e, "component disposer"); | ||
| } | ||
@@ -418,3 +418,3 @@ } | ||
| } catch (e) { | ||
| chunkDCTOXHPF_cjs.reportError(e, "onMount"); | ||
| chunkBARF67I6_cjs.reportError(e, "onMount"); | ||
| } | ||
@@ -522,3 +522,3 @@ } | ||
| if (!pair) { | ||
| pair = chunk3U57L2TY_cjs.createSignal(initialValue); | ||
| pair = chunkQLPCVK7C_cjs.createSignal(initialValue); | ||
| signals.set(path, pair); | ||
@@ -563,3 +563,3 @@ registerChild(path); | ||
| let result; | ||
| chunkDCTOXHPF_cjs.batch(() => { | ||
| chunkBARF67I6_cjs.batch(() => { | ||
| const rawArgs = args.map( | ||
@@ -678,7 +678,7 @@ (a) => a != null && typeof a === "object" && a[RAW] ? a[RAW] : a | ||
| function getCurrentSnapshot() { | ||
| return chunkDCTOXHPF_cjs.untrack(() => deepClone(initial)); | ||
| return chunkBARF67I6_cjs.untrack(() => deepClone(initial)); | ||
| } | ||
| const setter = (partial) => { | ||
| const updates = typeof partial === "function" ? partial(getCurrentSnapshot()) : partial; | ||
| chunkDCTOXHPF_cjs.batch(() => { | ||
| chunkBARF67I6_cjs.batch(() => { | ||
| for (const key of Object.keys(updates)) { | ||
@@ -698,7 +698,7 @@ rootProxy[key] = updates[key]; | ||
| let _cursor = 0; | ||
| const [stackSignal, setStackSignal] = chunk3U57L2TY_cjs.createSignal([..._stack]); | ||
| const [cursorSignal, setCursorSignal] = chunk3U57L2TY_cjs.createSignal(_cursor); | ||
| const [stackLenSignal, setStackLenSignal] = chunk3U57L2TY_cjs.createSignal(_stack.length); | ||
| const [stackSignal, setStackSignal] = chunkQLPCVK7C_cjs.createSignal([..._stack]); | ||
| const [cursorSignal, setCursorSignal] = chunkQLPCVK7C_cjs.createSignal(_cursor); | ||
| const [stackLenSignal, setStackLenSignal] = chunkQLPCVK7C_cjs.createSignal(_stack.length); | ||
| function syncSignals() { | ||
| chunkDCTOXHPF_cjs.batch(() => { | ||
| chunkBARF67I6_cjs.batch(() => { | ||
| setStackSignal([..._stack]); | ||
@@ -711,3 +711,3 @@ setCursorSignal(_cursor); | ||
| let isFirstRun = true; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const value = sourceGet(); | ||
@@ -780,3 +780,3 @@ if (isFirstRun) { | ||
| } | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| chunkBARF67I6_cjs.internalEffect(() => { | ||
| const value = sourceGet(); | ||
@@ -1039,107 +1039,107 @@ try { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.Fragment; } | ||
| get: function () { return chunk7L3KHGEA_cjs.Fragment; } | ||
| }); | ||
| Object.defineProperty(exports, "cleanup", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.cleanup; } | ||
| get: function () { return chunk7L3KHGEA_cjs.cleanup; } | ||
| }); | ||
| Object.defineProperty(exports, "createList", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.createList; } | ||
| get: function () { return chunk7L3KHGEA_cjs.createList; } | ||
| }); | ||
| Object.defineProperty(exports, "createShow", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.createShow; } | ||
| get: function () { return chunk7L3KHGEA_cjs.createShow; } | ||
| }); | ||
| Object.defineProperty(exports, "fragment", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.fragment; } | ||
| get: function () { return chunk7L3KHGEA_cjs.fragment; } | ||
| }); | ||
| Object.defineProperty(exports, "h", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.h; } | ||
| get: function () { return chunk7L3KHGEA_cjs.h; } | ||
| }); | ||
| Object.defineProperty(exports, "hydrateIsland", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.hydrateIsland; } | ||
| get: function () { return chunk7L3KHGEA_cjs.hydrateIsland; } | ||
| }); | ||
| Object.defineProperty(exports, "reconcileList", { | ||
| enumerable: true, | ||
| get: function () { return chunkSZSKW57A_cjs.reconcileList; } | ||
| get: function () { return chunk7L3KHGEA_cjs.reconcileList; } | ||
| }); | ||
| Object.defineProperty(exports, "batch", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.batch; } | ||
| get: function () { return chunkBARF67I6_cjs.batch; } | ||
| }); | ||
| Object.defineProperty(exports, "createEffect", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createEffect; } | ||
| get: function () { return chunkBARF67I6_cjs.createEffect; } | ||
| }); | ||
| Object.defineProperty(exports, "createMemo", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createMemo; } | ||
| get: function () { return chunkBARF67I6_cjs.createMemo; } | ||
| }); | ||
| Object.defineProperty(exports, "createReducer", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createReducer; } | ||
| get: function () { return chunkBARF67I6_cjs.createReducer; } | ||
| }); | ||
| Object.defineProperty(exports, "createRef", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createRef; } | ||
| get: function () { return chunkBARF67I6_cjs.createRef; } | ||
| }); | ||
| Object.defineProperty(exports, "createResource", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createResource; } | ||
| get: function () { return chunkBARF67I6_cjs.createResource; } | ||
| }); | ||
| Object.defineProperty(exports, "createRoot", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.createRoot; } | ||
| get: function () { return chunkBARF67I6_cjs.createRoot; } | ||
| }); | ||
| Object.defineProperty(exports, "getBatchDepth", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.getBatchDepth; } | ||
| get: function () { return chunkBARF67I6_cjs.getBatchDepth; } | ||
| }); | ||
| Object.defineProperty(exports, "isComputed", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.isComputed; } | ||
| get: function () { return chunkBARF67I6_cjs.isComputed; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffect", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.isEffect; } | ||
| get: function () { return chunkBARF67I6_cjs.isEffect; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffectScope", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.isEffectScope; } | ||
| get: function () { return chunkBARF67I6_cjs.isEffectScope; } | ||
| }); | ||
| Object.defineProperty(exports, "isSignal", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.isSignal; } | ||
| get: function () { return chunkBARF67I6_cjs.isSignal; } | ||
| }); | ||
| Object.defineProperty(exports, "on", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.on; } | ||
| get: function () { return chunkBARF67I6_cjs.on; } | ||
| }); | ||
| Object.defineProperty(exports, "onCleanup", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.onCleanup; } | ||
| get: function () { return chunkBARF67I6_cjs.onCleanup; } | ||
| }); | ||
| Object.defineProperty(exports, "onError", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.onError; } | ||
| get: function () { return chunkBARF67I6_cjs.onError; } | ||
| }); | ||
| Object.defineProperty(exports, "trigger", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.trigger; } | ||
| get: function () { return chunkBARF67I6_cjs.trigger; } | ||
| }); | ||
| Object.defineProperty(exports, "untrack", { | ||
| enumerable: true, | ||
| get: function () { return chunkDCTOXHPF_cjs.untrack; } | ||
| get: function () { return chunkBARF67I6_cjs.untrack; } | ||
| }); | ||
| Object.defineProperty(exports, "createComputed", { | ||
| enumerable: true, | ||
| get: function () { return chunk3U57L2TY_cjs.createComputed; } | ||
| get: function () { return chunkQLPCVK7C_cjs.createComputed; } | ||
| }); | ||
| Object.defineProperty(exports, "createSignal", { | ||
| enumerable: true, | ||
| get: function () { return chunk3U57L2TY_cjs.createSignal; } | ||
| get: function () { return chunkQLPCVK7C_cjs.createSignal; } | ||
| }); | ||
@@ -1146,0 +1146,0 @@ exports.$ = $; |
+141
-10
| /// <reference path="./jsx.d.ts" /> | ||
| import { S as SignalGetter } from './signal-YlS1kgfh.cjs'; | ||
| export { a as SignalOptions, b as SignalSetter, c as createSignal } from './signal-YlS1kgfh.cjs'; | ||
| export { R as Resource, a as ResourceOptions, c as createResource } from './resource-DK98lW5e.cjs'; | ||
| import { S as SignalGetter } from './signal-C9v4akyJ.cjs'; | ||
| export { a as SignalOptions, b as SignalSetter, c as createSignal } from './signal-C9v4akyJ.cjs'; | ||
| export { R as Resource, a as ResourceOptions, c as createResource } from './resource-DeEzxUz6.cjs'; | ||
| export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals'; | ||
@@ -226,3 +226,5 @@ | ||
| */ | ||
| /** A mutable container that does NOT trigger reactivity when written to. */ | ||
| interface Ref<T> { | ||
| /** The stored value. Mutating this does not notify effects. */ | ||
| current: T; | ||
@@ -261,2 +263,3 @@ } | ||
| /** A function that dispatches an action to a reducer. */ | ||
| type Dispatch<A> = (action: A) => void; | ||
@@ -290,2 +293,3 @@ /** | ||
| /** Callback signature for the global {@link onError} handler. */ | ||
| type ErrorHandler = (error: unknown, info?: { | ||
@@ -434,8 +438,14 @@ source?: string; | ||
| */ | ||
| /** Return value from {@link reconcileList} — the new nodes and items arrays. */ | ||
| interface ReconcileResult<T> { | ||
| /** The DOM nodes currently in the list, in order. */ | ||
| nodes: Node[]; | ||
| /** The items array corresponding to the current nodes. */ | ||
| items: T[]; | ||
| } | ||
| /** Animation hooks for list transitions. */ | ||
| interface ListTransitionHooks { | ||
| /** Called after a new node is inserted into the DOM. */ | ||
| onInsert?: (node: Node) => void; | ||
| /** Called before a node is removed. Call `done()` when the exit animation finishes. */ | ||
| onBeforeRemove?: (node: Node, done: () => void) => void; | ||
@@ -537,3 +547,3 @@ } | ||
| */ | ||
| declare function createShow(when: () => unknown, thenFn: () => Node, elseFn?: () => Node): DocumentFragment; | ||
| declare function createShow(when: () => unknown, thenFn: () => Node, elseFn?: () => Node | null): DocumentFragment; | ||
@@ -554,4 +564,7 @@ /** | ||
| */ | ||
| /** A single branch for {@link createSwitch}. */ | ||
| interface SwitchCase<T> { | ||
| /** The value that activates this branch. */ | ||
| match: T; | ||
| /** Factory function that renders this branch's DOM. */ | ||
| render: () => Node; | ||
@@ -756,6 +769,11 @@ } | ||
| */ | ||
| /** A teardown function that disposes effects and cleans up resources. */ | ||
| type CleanupFn = () => void; | ||
| /** A function that runs once to build the component's DOM tree. */ | ||
| type SetupFn = () => HTMLElement | DocumentFragment; | ||
| /** Definition object passed to {@link defineComponent}. */ | ||
| interface ComponentDef { | ||
| /** The setup function that builds the component's DOM and reactive bindings. */ | ||
| setup: SetupFn; | ||
| /** Optional debug name for devtools inspection. */ | ||
| name?: string; | ||
@@ -803,2 +821,3 @@ } | ||
| */ | ||
| /** A typed dependency injection context created by {@link createContext}. */ | ||
| interface Context<T> { | ||
@@ -861,2 +880,3 @@ /** Unique identifier for this context. */ | ||
| */ | ||
| /** Setter for a reactive store — accepts a partial update object or updater function. */ | ||
| type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partial<T>)) => void; | ||
@@ -909,2 +929,3 @@ /** | ||
| */ | ||
| /** Undo/redo controls returned by {@link createHistory}. */ | ||
| interface HistoryControls<T> { | ||
@@ -951,2 +972,3 @@ /** Undo the last change, restoring the previous value. */ | ||
| */ | ||
| /** Options for {@link persist} — storage backend, serialization, and validation. */ | ||
| interface PersistOptions<T> { | ||
@@ -977,28 +999,121 @@ /** Storage backend. Defaults to localStorage. */ | ||
| /** | ||
| * A typed publish/subscribe event bus. | ||
| * | ||
| * The type parameter `T` maps event names to their payload types, | ||
| * ensuring type-safe event emission and subscription. | ||
| */ | ||
| interface EventBus<T extends Record<string, unknown>> { | ||
| /** Subscribe to an event. Returns an unsubscribe function. */ | ||
| on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void; | ||
| /** Subscribe to an event, automatically unsubscribing after the first firing. */ | ||
| once<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void; | ||
| /** Emit an event to all current subscribers. */ | ||
| emit<K extends keyof T>(event: K, payload: T[K]): void; | ||
| /** Remove a specific handler from an event. */ | ||
| off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void; | ||
| /** Remove all handlers for all events. */ | ||
| clear(): void; | ||
| } | ||
| /** | ||
| * Create a typed event bus for publish/subscribe messaging. | ||
| * | ||
| * ```ts | ||
| * type Events = { save: { id: number }; delete: { id: number } }; | ||
| * const bus = createBus<Events>(); | ||
| * | ||
| * const unsub = bus.on('save', (payload) => console.log(payload.id)); | ||
| * bus.emit('save', { id: 42 }); | ||
| * unsub(); | ||
| * ``` | ||
| */ | ||
| declare function createBus<T extends Record<string, unknown> = Record<string, unknown>>(): EventBus<T>; | ||
| /** | ||
| * Attach a single event listener on a parent that fires for children matching | ||
| * a CSS selector. Efficient for large/dynamic lists — one listener instead of | ||
| * one per child. | ||
| * | ||
| * ```ts | ||
| * const unsub = delegate(ul, 'li', 'click', (e, li) => { | ||
| * console.log('Clicked:', li.textContent); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @param container The parent element or document to listen on. | ||
| * @param selector CSS selector to match against delegated targets. | ||
| * @param event DOM event name (e.g. `'click'`, `'input'`). | ||
| * @param handler Called with the event and the matched child element. | ||
| * @param options Standard `addEventListener` options (capture, passive, etc.). | ||
| * @returns An unsubscribe function that removes the listener. | ||
| */ | ||
| declare function delegate<K extends keyof HTMLElementEventMap>(container: HTMLElement | Document, selector: string, event: K, handler: (e: HTMLElementEventMap[K], matchedEl: HTMLElement) => void, options?: AddEventListenerOptions): () => void; | ||
| type KeyCombo = string; | ||
| /** Options for the {@link onKey} keyboard shortcut handler. */ | ||
| interface KeyOptions { | ||
| /** Element or document to listen on. Defaults to `document`. */ | ||
| target?: EventTarget; | ||
| /** Whether to call `e.preventDefault()`. Defaults to `true`. */ | ||
| preventDefault?: boolean; | ||
| } | ||
| /** | ||
| * Listen for a keyboard shortcut and invoke a handler when it fires. | ||
| * | ||
| * Supports modifier keys: `ctrl`, `shift`, `alt`, `meta`/`cmd`. | ||
| * | ||
| * ```ts | ||
| * const unsub = onKey('ctrl+s', (e) => save()); | ||
| * const unsub2 = onKey('escape', () => close(), { target: modal }); | ||
| * ``` | ||
| * | ||
| * @param combo Key combination string (e.g. `'ctrl+s'`, `'shift+enter'`). | ||
| * @param handler Called when the combo is pressed. | ||
| * @param options Optional target element and preventDefault behavior. | ||
| * @returns An unsubscribe function that removes the listener. | ||
| */ | ||
| declare function onKey(combo: KeyCombo, handler: (e: KeyboardEvent) => void, options?: KeyOptions): () => void; | ||
| /** | ||
| * Select a single element by CSS selector. Returns `null` if not found. | ||
| * | ||
| * ```ts | ||
| * const btn = $<HTMLButtonElement>('.submit-btn'); | ||
| * const inner = $('span', container); | ||
| * ``` | ||
| */ | ||
| declare function $<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T | null; | ||
| /** | ||
| * Select all elements matching a CSS selector, returned as a plain array. | ||
| * | ||
| * ```ts | ||
| * const items = $$<HTMLLIElement>('ul > li'); | ||
| * items.forEach((li) => li.classList.add('active')); | ||
| * ``` | ||
| */ | ||
| declare function $$<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T[]; | ||
| /** Add one or more CSS classes to an element. */ | ||
| declare function addClass(el: HTMLElement, ...classes: string[]): void; | ||
| /** Remove one or more CSS classes from an element. */ | ||
| declare function removeClass(el: HTMLElement, ...classes: string[]): void; | ||
| /** Toggle a CSS class on an element. Returns the resulting state. */ | ||
| declare function toggleClass(el: HTMLElement, className: string, force?: boolean): boolean; | ||
| /** | ||
| * Apply multiple inline styles to an element at once. | ||
| * | ||
| * ```ts | ||
| * setStyle(el, { opacity: '0', transform: 'translateY(-10px)' }); | ||
| * ``` | ||
| */ | ||
| declare function setStyle(el: HTMLElement, styles: Partial<CSSStyleDeclaration>): void; | ||
| /** | ||
| * Set or remove multiple attributes on an element. | ||
| * | ||
| * - `false` / `null` removes the attribute. | ||
| * - `true` sets it as a boolean attribute (empty string). | ||
| * - A string sets the attribute value. | ||
| */ | ||
| declare function setAttr(el: HTMLElement, attrs: Record<string, string | boolean | null>): void; | ||
| /** Set an element's text content. Safe for user-controlled strings. */ | ||
| declare function setText(el: HTMLElement, text: string): void; | ||
@@ -1010,22 +1125,38 @@ /** | ||
| * trust the HTML source (e.g., server-rendered markup you control). | ||
| * | ||
| /** | ||
| * Set raw HTML on an element. **No sanitization is performed.** | ||
| * | ||
| * Prefer `setText()` for user-controlled content. Only use this when you | ||
| * trust the HTML source (e.g., server-rendered markup you control). | ||
| */ | ||
| declare function setHTMLUnsafe(el: HTMLElement, html: string): void; | ||
| /** Find the closest ancestor matching a CSS selector. */ | ||
| declare function closest<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector: string): T | null; | ||
| /** Get direct child elements, optionally filtered by a CSS selector. */ | ||
| declare function children<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[]; | ||
| /** Get sibling elements (excluding the element itself), optionally filtered. */ | ||
| declare function siblings<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[]; | ||
| /** Get the parent element, or `null` if detached. */ | ||
| declare function parent<T extends HTMLElement = HTMLElement>(el: HTMLElement): T | null; | ||
| /** Find the next sibling element, optionally matching a CSS selector. */ | ||
| declare function nextSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null; | ||
| /** Find the previous sibling element, optionally matching a CSS selector. */ | ||
| declare function prevSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null; | ||
| /** | ||
| * Observe element size changes via `ResizeObserver`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onResize(el: HTMLElement, handler: (entry: ResizeObserverEntry) => void): () => void; | ||
| /** | ||
| * Observe element visibility changes via `IntersectionObserver`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onIntersect(el: HTMLElement, handler: (entry: IntersectionObserverEntry) => void, options?: IntersectionObserverInit): () => void; | ||
| /** | ||
| * Observe DOM mutations (child additions/removals, attribute changes) via | ||
| * `MutationObserver`. Defaults to `{ childList: true, subtree: true }`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onMutation(el: HTMLElement, handler: (mutations: MutationRecord[]) => void, options?: MutationObserverInit): () => void; | ||
| export { $, $$, type CleanupFn, type ComponentDef, type Context, type CreateListOptions, type Dispatch, type ErrorHandler, type EventBus, Fragment, type HistoryControls, type IslandHydrateFn, type KeyOptions, type ListTransitionHooks, type PersistOptions, type ReconcileResult, type Ref, type SetupFn, SignalGetter, type StoreSetter, type SwitchCase, activateIslands, addClass, batch, children, cleanup, closest, createBus, createComputed, createContext, createEffect, createErrorBoundary, createHistory, createList, createMemo, createPortal, createReducer, createRef, createRoot, createShow, createStore, createSuspense, createSwitch, createText, deactivateAllIslands, deactivateIsland, defineComponent, delegate, disposeComponent, fragment, h, hydrateIsland, inject, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, removeClass, setAttr, setHTMLUnsafe, setStyle, setText, siblings, template, templateMany, toggleClass, trackDisposer, unprovide, untrack }; |
+141
-10
| /// <reference path="./jsx.d.ts" /> | ||
| import { S as SignalGetter } from './signal-YlS1kgfh.js'; | ||
| export { a as SignalOptions, b as SignalSetter, c as createSignal } from './signal-YlS1kgfh.js'; | ||
| export { R as Resource, a as ResourceOptions, c as createResource } from './resource-Cd0cGOxS.js'; | ||
| import { S as SignalGetter } from './signal-C9v4akyJ.js'; | ||
| export { a as SignalOptions, b as SignalSetter, c as createSignal } from './signal-C9v4akyJ.js'; | ||
| export { R as Resource, a as ResourceOptions, c as createResource } from './resource-BHsgURy0.js'; | ||
| export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals'; | ||
@@ -226,3 +226,5 @@ | ||
| */ | ||
| /** A mutable container that does NOT trigger reactivity when written to. */ | ||
| interface Ref<T> { | ||
| /** The stored value. Mutating this does not notify effects. */ | ||
| current: T; | ||
@@ -261,2 +263,3 @@ } | ||
| /** A function that dispatches an action to a reducer. */ | ||
| type Dispatch<A> = (action: A) => void; | ||
@@ -290,2 +293,3 @@ /** | ||
| /** Callback signature for the global {@link onError} handler. */ | ||
| type ErrorHandler = (error: unknown, info?: { | ||
@@ -434,8 +438,14 @@ source?: string; | ||
| */ | ||
| /** Return value from {@link reconcileList} — the new nodes and items arrays. */ | ||
| interface ReconcileResult<T> { | ||
| /** The DOM nodes currently in the list, in order. */ | ||
| nodes: Node[]; | ||
| /** The items array corresponding to the current nodes. */ | ||
| items: T[]; | ||
| } | ||
| /** Animation hooks for list transitions. */ | ||
| interface ListTransitionHooks { | ||
| /** Called after a new node is inserted into the DOM. */ | ||
| onInsert?: (node: Node) => void; | ||
| /** Called before a node is removed. Call `done()` when the exit animation finishes. */ | ||
| onBeforeRemove?: (node: Node, done: () => void) => void; | ||
@@ -537,3 +547,3 @@ } | ||
| */ | ||
| declare function createShow(when: () => unknown, thenFn: () => Node, elseFn?: () => Node): DocumentFragment; | ||
| declare function createShow(when: () => unknown, thenFn: () => Node, elseFn?: () => Node | null): DocumentFragment; | ||
@@ -554,4 +564,7 @@ /** | ||
| */ | ||
| /** A single branch for {@link createSwitch}. */ | ||
| interface SwitchCase<T> { | ||
| /** The value that activates this branch. */ | ||
| match: T; | ||
| /** Factory function that renders this branch's DOM. */ | ||
| render: () => Node; | ||
@@ -756,6 +769,11 @@ } | ||
| */ | ||
| /** A teardown function that disposes effects and cleans up resources. */ | ||
| type CleanupFn = () => void; | ||
| /** A function that runs once to build the component's DOM tree. */ | ||
| type SetupFn = () => HTMLElement | DocumentFragment; | ||
| /** Definition object passed to {@link defineComponent}. */ | ||
| interface ComponentDef { | ||
| /** The setup function that builds the component's DOM and reactive bindings. */ | ||
| setup: SetupFn; | ||
| /** Optional debug name for devtools inspection. */ | ||
| name?: string; | ||
@@ -803,2 +821,3 @@ } | ||
| */ | ||
| /** A typed dependency injection context created by {@link createContext}. */ | ||
| interface Context<T> { | ||
@@ -861,2 +880,3 @@ /** Unique identifier for this context. */ | ||
| */ | ||
| /** Setter for a reactive store — accepts a partial update object or updater function. */ | ||
| type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partial<T>)) => void; | ||
@@ -909,2 +929,3 @@ /** | ||
| */ | ||
| /** Undo/redo controls returned by {@link createHistory}. */ | ||
| interface HistoryControls<T> { | ||
@@ -951,2 +972,3 @@ /** Undo the last change, restoring the previous value. */ | ||
| */ | ||
| /** Options for {@link persist} — storage backend, serialization, and validation. */ | ||
| interface PersistOptions<T> { | ||
@@ -977,28 +999,121 @@ /** Storage backend. Defaults to localStorage. */ | ||
| /** | ||
| * A typed publish/subscribe event bus. | ||
| * | ||
| * The type parameter `T` maps event names to their payload types, | ||
| * ensuring type-safe event emission and subscription. | ||
| */ | ||
| interface EventBus<T extends Record<string, unknown>> { | ||
| /** Subscribe to an event. Returns an unsubscribe function. */ | ||
| on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void; | ||
| /** Subscribe to an event, automatically unsubscribing after the first firing. */ | ||
| once<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void; | ||
| /** Emit an event to all current subscribers. */ | ||
| emit<K extends keyof T>(event: K, payload: T[K]): void; | ||
| /** Remove a specific handler from an event. */ | ||
| off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void; | ||
| /** Remove all handlers for all events. */ | ||
| clear(): void; | ||
| } | ||
| /** | ||
| * Create a typed event bus for publish/subscribe messaging. | ||
| * | ||
| * ```ts | ||
| * type Events = { save: { id: number }; delete: { id: number } }; | ||
| * const bus = createBus<Events>(); | ||
| * | ||
| * const unsub = bus.on('save', (payload) => console.log(payload.id)); | ||
| * bus.emit('save', { id: 42 }); | ||
| * unsub(); | ||
| * ``` | ||
| */ | ||
| declare function createBus<T extends Record<string, unknown> = Record<string, unknown>>(): EventBus<T>; | ||
| /** | ||
| * Attach a single event listener on a parent that fires for children matching | ||
| * a CSS selector. Efficient for large/dynamic lists — one listener instead of | ||
| * one per child. | ||
| * | ||
| * ```ts | ||
| * const unsub = delegate(ul, 'li', 'click', (e, li) => { | ||
| * console.log('Clicked:', li.textContent); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @param container The parent element or document to listen on. | ||
| * @param selector CSS selector to match against delegated targets. | ||
| * @param event DOM event name (e.g. `'click'`, `'input'`). | ||
| * @param handler Called with the event and the matched child element. | ||
| * @param options Standard `addEventListener` options (capture, passive, etc.). | ||
| * @returns An unsubscribe function that removes the listener. | ||
| */ | ||
| declare function delegate<K extends keyof HTMLElementEventMap>(container: HTMLElement | Document, selector: string, event: K, handler: (e: HTMLElementEventMap[K], matchedEl: HTMLElement) => void, options?: AddEventListenerOptions): () => void; | ||
| type KeyCombo = string; | ||
| /** Options for the {@link onKey} keyboard shortcut handler. */ | ||
| interface KeyOptions { | ||
| /** Element or document to listen on. Defaults to `document`. */ | ||
| target?: EventTarget; | ||
| /** Whether to call `e.preventDefault()`. Defaults to `true`. */ | ||
| preventDefault?: boolean; | ||
| } | ||
| /** | ||
| * Listen for a keyboard shortcut and invoke a handler when it fires. | ||
| * | ||
| * Supports modifier keys: `ctrl`, `shift`, `alt`, `meta`/`cmd`. | ||
| * | ||
| * ```ts | ||
| * const unsub = onKey('ctrl+s', (e) => save()); | ||
| * const unsub2 = onKey('escape', () => close(), { target: modal }); | ||
| * ``` | ||
| * | ||
| * @param combo Key combination string (e.g. `'ctrl+s'`, `'shift+enter'`). | ||
| * @param handler Called when the combo is pressed. | ||
| * @param options Optional target element and preventDefault behavior. | ||
| * @returns An unsubscribe function that removes the listener. | ||
| */ | ||
| declare function onKey(combo: KeyCombo, handler: (e: KeyboardEvent) => void, options?: KeyOptions): () => void; | ||
| /** | ||
| * Select a single element by CSS selector. Returns `null` if not found. | ||
| * | ||
| * ```ts | ||
| * const btn = $<HTMLButtonElement>('.submit-btn'); | ||
| * const inner = $('span', container); | ||
| * ``` | ||
| */ | ||
| declare function $<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T | null; | ||
| /** | ||
| * Select all elements matching a CSS selector, returned as a plain array. | ||
| * | ||
| * ```ts | ||
| * const items = $$<HTMLLIElement>('ul > li'); | ||
| * items.forEach((li) => li.classList.add('active')); | ||
| * ``` | ||
| */ | ||
| declare function $$<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T[]; | ||
| /** Add one or more CSS classes to an element. */ | ||
| declare function addClass(el: HTMLElement, ...classes: string[]): void; | ||
| /** Remove one or more CSS classes from an element. */ | ||
| declare function removeClass(el: HTMLElement, ...classes: string[]): void; | ||
| /** Toggle a CSS class on an element. Returns the resulting state. */ | ||
| declare function toggleClass(el: HTMLElement, className: string, force?: boolean): boolean; | ||
| /** | ||
| * Apply multiple inline styles to an element at once. | ||
| * | ||
| * ```ts | ||
| * setStyle(el, { opacity: '0', transform: 'translateY(-10px)' }); | ||
| * ``` | ||
| */ | ||
| declare function setStyle(el: HTMLElement, styles: Partial<CSSStyleDeclaration>): void; | ||
| /** | ||
| * Set or remove multiple attributes on an element. | ||
| * | ||
| * - `false` / `null` removes the attribute. | ||
| * - `true` sets it as a boolean attribute (empty string). | ||
| * - A string sets the attribute value. | ||
| */ | ||
| declare function setAttr(el: HTMLElement, attrs: Record<string, string | boolean | null>): void; | ||
| /** Set an element's text content. Safe for user-controlled strings. */ | ||
| declare function setText(el: HTMLElement, text: string): void; | ||
@@ -1010,22 +1125,38 @@ /** | ||
| * trust the HTML source (e.g., server-rendered markup you control). | ||
| * | ||
| /** | ||
| * Set raw HTML on an element. **No sanitization is performed.** | ||
| * | ||
| * Prefer `setText()` for user-controlled content. Only use this when you | ||
| * trust the HTML source (e.g., server-rendered markup you control). | ||
| */ | ||
| declare function setHTMLUnsafe(el: HTMLElement, html: string): void; | ||
| /** Find the closest ancestor matching a CSS selector. */ | ||
| declare function closest<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector: string): T | null; | ||
| /** Get direct child elements, optionally filtered by a CSS selector. */ | ||
| declare function children<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[]; | ||
| /** Get sibling elements (excluding the element itself), optionally filtered. */ | ||
| declare function siblings<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[]; | ||
| /** Get the parent element, or `null` if detached. */ | ||
| declare function parent<T extends HTMLElement = HTMLElement>(el: HTMLElement): T | null; | ||
| /** Find the next sibling element, optionally matching a CSS selector. */ | ||
| declare function nextSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null; | ||
| /** Find the previous sibling element, optionally matching a CSS selector. */ | ||
| declare function prevSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null; | ||
| /** | ||
| * Observe element size changes via `ResizeObserver`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onResize(el: HTMLElement, handler: (entry: ResizeObserverEntry) => void): () => void; | ||
| /** | ||
| * Observe element visibility changes via `IntersectionObserver`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onIntersect(el: HTMLElement, handler: (entry: IntersectionObserverEntry) => void, options?: IntersectionObserverInit): () => void; | ||
| /** | ||
| * Observe DOM mutations (child additions/removals, attribute changes) via | ||
| * `MutationObserver`. Defaults to `{ childList: true, subtree: true }`. | ||
| * | ||
| * @returns A disconnect function that stops observing. | ||
| */ | ||
| declare function onMutation(el: HTMLElement, handler: (mutations: MutationRecord[]) => void, options?: MutationObserverInit): () => void; | ||
| export { $, $$, type CleanupFn, type ComponentDef, type Context, type CreateListOptions, type Dispatch, type ErrorHandler, type EventBus, Fragment, type HistoryControls, type IslandHydrateFn, type KeyOptions, type ListTransitionHooks, type PersistOptions, type ReconcileResult, type Ref, type SetupFn, SignalGetter, type StoreSetter, type SwitchCase, activateIslands, addClass, batch, children, cleanup, closest, createBus, createComputed, createContext, createEffect, createErrorBoundary, createHistory, createList, createMemo, createPortal, createReducer, createRef, createRoot, createShow, createStore, createSuspense, createSwitch, createText, deactivateAllIslands, deactivateIsland, defineComponent, delegate, disposeComponent, fragment, h, hydrateIsland, inject, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, removeClass, setAttr, setHTMLUnsafe, setStyle, setText, siblings, template, templateMany, toggleClass, trackDisposer, unprovide, untrack }; |
+6
-6
@@ -1,7 +0,7 @@ | ||
| import { hydrateIsland } from './chunk-FJGTMWKY.js'; | ||
| export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-FJGTMWKY.js'; | ||
| import { internalEffect, createRoot, untrack, createEffect, pushSuspenseContext, popSuspenseContext, __DEV__, reportError, batch } from './chunk-OUVOAYIO.js'; | ||
| export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, onError, trigger, untrack } from './chunk-OUVOAYIO.js'; | ||
| import { createSignal } from './chunk-OZCHIVAZ.js'; | ||
| export { createComputed, createSignal } from './chunk-OZCHIVAZ.js'; | ||
| import { hydrateIsland } from './chunk-7Q7LIV23.js'; | ||
| export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-7Q7LIV23.js'; | ||
| import { internalEffect, createRoot, untrack, createEffect, pushSuspenseContext, popSuspenseContext, __DEV__, reportError, batch } from './chunk-TBWZZ3SI.js'; | ||
| export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, onError, trigger, untrack } from './chunk-TBWZZ3SI.js'; | ||
| import { createSignal } from './chunk-HLM5BZZQ.js'; | ||
| export { createComputed, createSignal } from './chunk-HLM5BZZQ.js'; | ||
@@ -8,0 +8,0 @@ // src/dom/text.ts |
+7
-7
| 'use strict'; | ||
| var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs'); | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| var chunkBARF67I6_cjs = require('./chunk-BARF67I6.cjs'); | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
| // src/server/action.ts | ||
| function createAction(serverFn, options) { | ||
| const [pending, setPending] = chunk3U57L2TY_cjs.createSignal(false); | ||
| const [error, setError] = chunk3U57L2TY_cjs.createSignal(void 0); | ||
| const [pending, setPending] = chunkQLPCVK7C_cjs.createSignal(false); | ||
| const [error, setError] = chunkQLPCVK7C_cjs.createSignal(void 0); | ||
| const action = async (...args) => { | ||
@@ -15,3 +15,3 @@ setPending(true); | ||
| try { | ||
| chunkDCTOXHPF_cjs.batch(() => options.optimistic(...args)); | ||
| chunkBARF67I6_cjs.batch(() => options.optimistic(...args)); | ||
| } catch { | ||
@@ -23,3 +23,3 @@ } | ||
| if (options?.onSuccess) { | ||
| chunkDCTOXHPF_cjs.batch(() => options.onSuccess(result, ...args)); | ||
| chunkBARF67I6_cjs.batch(() => options.onSuccess(result, ...args)); | ||
| } | ||
@@ -36,3 +36,3 @@ if (options?.invalidates) { | ||
| try { | ||
| chunkDCTOXHPF_cjs.batch(() => options.onError(err, ...args)); | ||
| chunkBARF67I6_cjs.batch(() => options.onError(err, ...args)); | ||
| } catch { | ||
@@ -39,0 +39,0 @@ } |
@@ -1,3 +0,3 @@ | ||
| import { R as Resource } from './resource-DK98lW5e.cjs'; | ||
| import './signal-YlS1kgfh.cjs'; | ||
| import { R as Resource } from './resource-DeEzxUz6.cjs'; | ||
| import './signal-C9v4akyJ.cjs'; | ||
@@ -4,0 +4,0 @@ /** |
+2
-2
@@ -1,3 +0,3 @@ | ||
| import { R as Resource } from './resource-Cd0cGOxS.js'; | ||
| import './signal-YlS1kgfh.js'; | ||
| import { R as Resource } from './resource-BHsgURy0.js'; | ||
| import './signal-C9v4akyJ.js'; | ||
@@ -4,0 +4,0 @@ /** |
+2
-2
@@ -1,3 +0,3 @@ | ||
| import { batch } from './chunk-OUVOAYIO.js'; | ||
| import { createSignal } from './chunk-OZCHIVAZ.js'; | ||
| import { batch } from './chunk-TBWZZ3SI.js'; | ||
| import { createSignal } from './chunk-HLM5BZZQ.js'; | ||
@@ -4,0 +4,0 @@ // src/server/action.ts |
| 'use strict'; | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs'); | ||
@@ -10,3 +10,3 @@ // src/reactive/tc39-compat.ts | ||
| constructor(initialValue, options) { | ||
| const [getter, setter] = chunk3U57L2TY_cjs.createSignal(initialValue, options); | ||
| const [getter, setter] = chunkQLPCVK7C_cjs.createSignal(initialValue, options); | ||
| this._get = getter; | ||
@@ -28,3 +28,3 @@ this._set = setter; | ||
| constructor(fn) { | ||
| this._get = chunk3U57L2TY_cjs.createComputed(fn); | ||
| this._get = chunkQLPCVK7C_cjs.createComputed(fn); | ||
| } | ||
@@ -31,0 +31,0 @@ get() { |
@@ -1,2 +0,2 @@ | ||
| import { a as SignalOptions } from './signal-YlS1kgfh.cjs'; | ||
| import { a as SignalOptions } from './signal-C9v4akyJ.cjs'; | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,2 +0,2 @@ | ||
| import { a as SignalOptions } from './signal-YlS1kgfh.js'; | ||
| import { a as SignalOptions } from './signal-C9v4akyJ.js'; | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,2 +0,2 @@ | ||
| import { createSignal, createComputed } from './chunk-OZCHIVAZ.js'; | ||
| import { createSignal, createComputed } from './chunk-HLM5BZZQ.js'; | ||
@@ -3,0 +3,0 @@ // src/reactive/tc39-compat.ts |
+1
-1
| { | ||
| "name": "@getforma/core", | ||
| "author": "Forma <victor@getforma.dev>", | ||
| "version": "1.0.7", | ||
| "version": "1.0.8", | ||
| "description": "Real DOM reactive library — fine-grained signals, islands architecture, SSR hydration. No virtual DOM, no diffing. ~15KB gzipped.", | ||
@@ -6,0 +6,0 @@ "type": "module", |
+4
-4
@@ -46,3 +46,3 @@ # FormaJS | ||
| > **Production:** Pin the version (e.g., `@getforma/core@1.0.1`) instead of `@latest`. | ||
| > **Production:** Pin the version (e.g., `@getforma/core@1.0.7`) instead of `@latest`. | ||
@@ -705,6 +705,6 @@ --- | ||
| <!-- jsDelivr (recommended) --> | ||
| <script src="https://cdn.jsdelivr.net/npm/@getforma/core@1.0.1/dist/formajs-runtime.global.js"></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/@getforma/core@1.0.7/dist/formajs-runtime.global.js"></script> | ||
| <!-- unpkg --> | ||
| <script src="https://unpkg.com/@getforma/core@1.0.1/dist/formajs-runtime.global.js"></script> | ||
| <script src="https://unpkg.com/@getforma/core@1.0.7/dist/formajs-runtime.global.js"></script> | ||
| ``` | ||
@@ -716,3 +716,3 @@ | ||
| <script type="module"> | ||
| import { createSignal, h, mount } from "https://cdn.jsdelivr.net/npm/@getforma/core@1.0.1/dist/index.js"; | ||
| import { createSignal, h, mount } from "https://cdn.jsdelivr.net/npm/@getforma/core@1.0.7/dist/index.js"; | ||
@@ -719,0 +719,0 @@ const [count, setCount] = createSignal(0); |
| 'use strict'; | ||
| var alienSignals = require('alien-signals'); | ||
| // src/reactive/signal.ts | ||
| function applySignalSet(s, v, equals) { | ||
| if (typeof v !== "function") { | ||
| if (equals) { | ||
| const prevSub2 = alienSignals.setActiveSub(void 0); | ||
| const prev2 = s(); | ||
| alienSignals.setActiveSub(prevSub2); | ||
| if (equals(prev2, v)) return; | ||
| } | ||
| s(v); | ||
| return; | ||
| } | ||
| const prevSub = alienSignals.setActiveSub(void 0); | ||
| const prev = s(); | ||
| alienSignals.setActiveSub(prevSub); | ||
| const next = v(prev); | ||
| if (equals && equals(prev, next)) return; | ||
| s(next); | ||
| } | ||
| function createSignal(initialValue, options) { | ||
| const s = alienSignals.signal(initialValue); | ||
| const getter = s; | ||
| const eq = options?.equals; | ||
| const setter = (v) => applySignalSet(s, v, eq); | ||
| return [getter, setter]; | ||
| } | ||
| function createComputed(fn) { | ||
| return alienSignals.computed(fn); | ||
| } | ||
| exports.createComputed = createComputed; | ||
| exports.createSignal = createSignal; | ||
| //# sourceMappingURL=chunk-3U57L2TY.cjs.map | ||
| //# sourceMappingURL=chunk-3U57L2TY.cjs.map |
| {"version":3,"sources":["../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","setActiveSub","prev","createRawSignal","rawComputed"],"mappings":";;;;;AAkEA,SAAS,cAAA,CACP,CAAA,EACA,CAAA,EACA,MAAA,EACM;AACN,EAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC3B,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,MAAMA,QAAAA,GAAUC,0BAAa,MAAS,CAAA;AACtC,MAAA,MAAMC,QAAO,CAAA,EAAE;AACf,MAAAD,yBAAA,CAAaD,QAAO,CAAA;AACpB,MAAA,IAAI,MAAA,CAAOE,KAAAA,EAAM,CAAC,CAAA,EAAG;AAAA,IACvB;AACA,IAAA,CAAA,CAAE,CAAC,CAAA;AACH,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,OAAA,GAAUD,0BAAa,MAAS,CAAA;AACtC,EAAA,MAAM,OAAO,CAAA,EAAE;AACf,EAAAA,yBAAA,CAAa,OAAO,CAAA;AACpB,EAAA,MAAM,IAAA,GAAQ,EAAqB,IAAI,CAAA;AACvC,EAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAM,IAAI,CAAA,EAAG;AAClC,EAAA,CAAA,CAAE,IAAI,CAAA;AACR;AAqBO,SAAS,YAAA,CAAgB,cAAiB,OAAA,EAA0E;AACzH,EAAA,MAAM,CAAA,GAAIE,oBAAmB,YAAY,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,CAAA;AACf,EAAA,MAAM,KAAK,OAAA,EAAS,MAAA;AACpB,EAAA,MAAM,SAA0B,CAAC,CAAA,KAA4B,cAAA,CAAe,CAAA,EAAG,GAAG,EAAE,CAAA;AAEpF,EAAA,OAAO,CAAC,QAAQ,MAAM,CAAA;AACxB;AC7EO,SAAS,eAAkB,EAAA,EAAuC;AACvE,EAAA,OAAOC,sBAAY,EAAE,CAAA;AACvB","file":"chunk-3U57L2TY.cjs","sourcesContent":["/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport type SignalGetter<T> = () => T;\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n if (typeof v !== 'function') {\n if (equals) {\n // Read current value without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // skip — values are equal\n }\n s(v);\n return;\n }\n\n // Functional update: read prev without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // skip — values are equal\n s(next);\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n return rawComputed(fn);\n}\n"]} |
| 'use strict'; | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| var alienSignals = require('alien-signals'); | ||
| var currentRoot = null; | ||
| var rootStack = []; | ||
| function createRoot(fn) { | ||
| const scope = { disposers: [], scopeDispose: null }; | ||
| rootStack.push(currentRoot); | ||
| currentRoot = scope; | ||
| const dispose = () => { | ||
| if (scope.scopeDispose) { | ||
| try { | ||
| scope.scopeDispose(); | ||
| } catch { | ||
| } | ||
| scope.scopeDispose = null; | ||
| } | ||
| for (const d of scope.disposers) { | ||
| try { | ||
| d(); | ||
| } catch { | ||
| } | ||
| } | ||
| scope.disposers.length = 0; | ||
| }; | ||
| let result; | ||
| try { | ||
| scope.scopeDispose = alienSignals.effectScope(() => { | ||
| result = fn(dispose); | ||
| }); | ||
| } finally { | ||
| currentRoot = rootStack.pop() ?? null; | ||
| } | ||
| return result; | ||
| } | ||
| function registerDisposer(dispose) { | ||
| if (currentRoot) { | ||
| currentRoot.disposers.push(dispose); | ||
| } | ||
| } | ||
| function hasActiveRoot() { | ||
| return currentRoot !== null; | ||
| } | ||
| // src/reactive/cleanup.ts | ||
| var currentCleanupCollector = null; | ||
| function onCleanup(fn) { | ||
| currentCleanupCollector?.(fn); | ||
| } | ||
| function setCleanupCollector(collector) { | ||
| const prev = currentCleanupCollector; | ||
| currentCleanupCollector = collector; | ||
| return prev; | ||
| } | ||
| // src/reactive/dev.ts | ||
| var __DEV__ = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true; | ||
| var _errorHandler = null; | ||
| function onError(handler) { | ||
| _errorHandler = handler; | ||
| } | ||
| function reportError(error, source) { | ||
| if (_errorHandler) { | ||
| try { | ||
| _errorHandler(error, source ? { source } : {}); | ||
| } catch { | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| console.error(`[forma] ${source ?? "Unknown"} error:`, error); | ||
| } | ||
| } | ||
| var POOL_SIZE = 32; | ||
| var MAX_REENTRANT_RUNS = 100; | ||
| var pool = []; | ||
| for (let i = 0; i < POOL_SIZE; i++) pool.push([]); | ||
| var poolIdx = POOL_SIZE; | ||
| function acquireArray() { | ||
| if (poolIdx > 0) { | ||
| const arr = pool[--poolIdx]; | ||
| arr.length = 0; | ||
| return arr; | ||
| } | ||
| return []; | ||
| } | ||
| function releaseArray(arr) { | ||
| arr.length = 0; | ||
| if (poolIdx < POOL_SIZE) { | ||
| pool[poolIdx++] = arr; | ||
| } | ||
| } | ||
| function runCleanup(fn) { | ||
| if (fn === void 0) return; | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| function runCleanups(bag) { | ||
| if (bag === void 0) return; | ||
| for (let i = 0; i < bag.length; i++) { | ||
| try { | ||
| bag[i](); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| } | ||
| function internalEffect(fn) { | ||
| const dispose = alienSignals.effect(fn); | ||
| if (hasActiveRoot()) { | ||
| registerDisposer(dispose); | ||
| } | ||
| return dispose; | ||
| } | ||
| function createEffect(fn) { | ||
| const shouldRegister = hasActiveRoot(); | ||
| let cleanup; | ||
| let cleanupBag; | ||
| let nextCleanup; | ||
| let nextCleanupBag; | ||
| const addCleanup = (cb) => { | ||
| if (nextCleanupBag !== void 0) { | ||
| nextCleanupBag.push(cb); | ||
| return; | ||
| } | ||
| if (nextCleanup !== void 0) { | ||
| const bag = acquireArray(); | ||
| bag.push(nextCleanup, cb); | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = bag; | ||
| return; | ||
| } | ||
| nextCleanup = cb; | ||
| }; | ||
| let skipCleanupInfra = false; | ||
| let firstRun = true; | ||
| let running = false; | ||
| let rerunRequested = false; | ||
| const runOnce = () => { | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| if (skipCleanupInfra) { | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| } | ||
| return; | ||
| } | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = void 0; | ||
| const prevCollector = setCleanupCollector(addCleanup); | ||
| try { | ||
| const result = fn(); | ||
| if (typeof result === "function") { | ||
| addCleanup(result); | ||
| } | ||
| if (nextCleanup === void 0 && nextCleanupBag === void 0) { | ||
| if (firstRun) skipCleanupInfra = true; | ||
| return; | ||
| } | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } finally { | ||
| setCleanupCollector(prevCollector); | ||
| firstRun = false; | ||
| } | ||
| }; | ||
| const safeFn = () => { | ||
| if (running) { | ||
| rerunRequested = true; | ||
| return; | ||
| } | ||
| running = true; | ||
| try { | ||
| let reentrantRuns = 0; | ||
| do { | ||
| rerunRequested = false; | ||
| runOnce(); | ||
| if (rerunRequested) { | ||
| reentrantRuns++; | ||
| if (reentrantRuns >= MAX_REENTRANT_RUNS) { | ||
| reportError( | ||
| new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`), | ||
| "effect" | ||
| ); | ||
| rerunRequested = false; | ||
| } | ||
| } | ||
| } while (rerunRequested); | ||
| } finally { | ||
| running = false; | ||
| } | ||
| }; | ||
| const dispose = alienSignals.effect(safeFn); | ||
| let disposed = false; | ||
| const wrappedDispose = () => { | ||
| if (disposed) return; | ||
| disposed = true; | ||
| dispose(); | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| }; | ||
| if (shouldRegister) { | ||
| registerDisposer(wrappedDispose); | ||
| } | ||
| return wrappedDispose; | ||
| } | ||
| // src/reactive/memo.ts | ||
| var createMemo = chunk3U57L2TY_cjs.createComputed; | ||
| function batch(fn) { | ||
| alienSignals.startBatch(); | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| alienSignals.endBatch(); | ||
| } | ||
| } | ||
| function untrack(fn) { | ||
| const prev = alienSignals.setActiveSub(void 0); | ||
| try { | ||
| return fn(); | ||
| } finally { | ||
| alienSignals.setActiveSub(prev); | ||
| } | ||
| } | ||
| // src/reactive/on.ts | ||
| function on(deps, fn, options) { | ||
| let prev; | ||
| let isFirst = true; | ||
| return () => { | ||
| const value2 = deps(); | ||
| if (options?.defer && isFirst) { | ||
| isFirst = false; | ||
| prev = value2; | ||
| return void 0; | ||
| } | ||
| const result = untrack(() => fn(value2, prev)); | ||
| prev = value2; | ||
| return result; | ||
| }; | ||
| } | ||
| // src/reactive/ref.ts | ||
| function createRef(initialValue) { | ||
| return { current: initialValue }; | ||
| } | ||
| // src/reactive/reducer.ts | ||
| function createReducer(reducer, initialState) { | ||
| const [state, setState] = chunk3U57L2TY_cjs.createSignal(initialState); | ||
| const dispatch = (action) => { | ||
| setState((prev) => reducer(prev, action)); | ||
| }; | ||
| return [state, dispatch]; | ||
| } | ||
| // src/reactive/suspense-context.ts | ||
| var currentSuspenseContext = null; | ||
| var suspenseStack = []; | ||
| function pushSuspenseContext(ctx) { | ||
| suspenseStack.push(currentSuspenseContext); | ||
| currentSuspenseContext = ctx; | ||
| } | ||
| function popSuspenseContext() { | ||
| currentSuspenseContext = suspenseStack.pop() ?? null; | ||
| } | ||
| function getSuspenseContext() { | ||
| return currentSuspenseContext; | ||
| } | ||
| // src/reactive/resource.ts | ||
| function createResource(source, fetcher, options) { | ||
| const [data, setData] = chunk3U57L2TY_cjs.createSignal(options?.initialValue); | ||
| const [loading, setLoading] = chunk3U57L2TY_cjs.createSignal(false); | ||
| const [error, setError] = chunk3U57L2TY_cjs.createSignal(void 0); | ||
| const suspenseCtx = getSuspenseContext(); | ||
| let abortController = null; | ||
| let fetchVersion = 0; | ||
| const doFetch = () => { | ||
| const sourceValue = untrack(source); | ||
| if (abortController) { | ||
| abortController.abort(); | ||
| } | ||
| const controller = new AbortController(); | ||
| abortController = controller; | ||
| const version = ++fetchVersion; | ||
| const isLatest = () => version === fetchVersion; | ||
| let suspensePending = false; | ||
| if (suspenseCtx) { | ||
| suspenseCtx.increment(); | ||
| suspensePending = true; | ||
| } | ||
| setLoading(true); | ||
| setError(void 0); | ||
| Promise.resolve(fetcher(sourceValue)).then((result) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| setData(() => result); | ||
| } | ||
| }).catch((err) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| if (err?.name !== "AbortError") { | ||
| setError(err); | ||
| } | ||
| } | ||
| }).finally(() => { | ||
| if (suspensePending) suspenseCtx?.decrement(); | ||
| if (isLatest()) { | ||
| setLoading(false); | ||
| if (abortController === controller) { | ||
| abortController = null; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| internalEffect(() => { | ||
| source(); | ||
| doFetch(); | ||
| }); | ||
| const resource = (() => data()); | ||
| resource.loading = loading; | ||
| resource.error = error; | ||
| resource.refetch = doFetch; | ||
| resource.mutate = (value2) => setData(() => value2); | ||
| return resource; | ||
| } | ||
| Object.defineProperty(exports, "getBatchDepth", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.getBatchDepth; } | ||
| }); | ||
| Object.defineProperty(exports, "isComputed", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isComputed; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffect", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isEffect; } | ||
| }); | ||
| Object.defineProperty(exports, "isEffectScope", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isEffectScope; } | ||
| }); | ||
| Object.defineProperty(exports, "isSignal", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.isSignal; } | ||
| }); | ||
| Object.defineProperty(exports, "trigger", { | ||
| enumerable: true, | ||
| get: function () { return alienSignals.trigger; } | ||
| }); | ||
| exports.__DEV__ = __DEV__; | ||
| exports.batch = batch; | ||
| exports.createEffect = createEffect; | ||
| exports.createMemo = createMemo; | ||
| exports.createReducer = createReducer; | ||
| exports.createRef = createRef; | ||
| exports.createResource = createResource; | ||
| exports.createRoot = createRoot; | ||
| exports.internalEffect = internalEffect; | ||
| exports.on = on; | ||
| exports.onCleanup = onCleanup; | ||
| exports.onError = onError; | ||
| exports.popSuspenseContext = popSuspenseContext; | ||
| exports.pushSuspenseContext = pushSuspenseContext; | ||
| exports.reportError = reportError; | ||
| exports.untrack = untrack; | ||
| //# sourceMappingURL=chunk-DCTOXHPF.cjs.map | ||
| //# sourceMappingURL=chunk-DCTOXHPF.cjs.map |
| {"version":3,"sources":["../src/reactive/root.ts","../src/reactive/cleanup.ts","../src/reactive/dev.ts","../src/reactive/effect.ts","../src/reactive/memo.ts","../src/reactive/batch.ts","../src/reactive/untrack.ts","../src/reactive/on.ts","../src/reactive/ref.ts","../src/reactive/reducer.ts","../src/reactive/suspense-context.ts","../src/reactive/resource.ts"],"names":["rawEffectScope","rawEffect","createComputed","startBatch","endBatch","setActiveSub","value","createSignal"],"mappings":";;;;;AAiBA,IAAI,WAAA,GAAgC,IAAA;AACpC,IAAM,YAAkC,EAAC;AAyBlC,SAAS,WAAc,EAAA,EAAmC;AAC/D,EAAA,MAAM,QAAmB,EAAE,SAAA,EAAW,EAAC,EAAG,cAAc,IAAA,EAAK;AAE7D,EAAA,SAAA,CAAU,KAAK,WAAW,CAAA;AAC1B,EAAA,WAAA,GAAc,KAAA;AAEd,EAAA,MAAM,UAAU,MAAM;AAEpB,IAAA,IAAI,MAAM,YAAA,EAAc;AACtB,MAAA,IAAI;AAAE,QAAA,KAAA,CAAM,YAAA,EAAa;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAA4C;AAChF,MAAA,KAAA,CAAM,YAAA,GAAe,IAAA;AAAA,IACvB;AAEA,IAAA,KAAA,MAAW,CAAA,IAAK,MAAM,SAAA,EAAW;AAC/B,MAAA,IAAI;AAAE,QAAA,CAAA,EAAE;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAiC;AAAA,IACtD;AACA,IAAA,KAAA,CAAM,UAAU,MAAA,GAAS,CAAA;AAAA,EAC3B,CAAA;AAEA,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AAEF,IAAA,KAAA,CAAM,YAAA,GAAeA,yBAAe,MAAM;AACxC,MAAA,MAAA,GAAS,GAAG,OAAO,CAAA;AAAA,IACrB,CAAC,CAAA;AAAA,EACH,CAAA,SAAE;AACA,IAAA,WAAA,GAAc,SAAA,CAAU,KAAI,IAAK,IAAA;AAAA,EACnC;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,iBAAiB,OAAA,EAA2B;AAC1D,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,WAAA,CAAY,SAAA,CAAU,KAAK,OAAO,CAAA;AAAA,EACpC;AACF;AAKO,SAAS,aAAA,GAAyB;AACvC,EAAA,OAAO,WAAA,KAAgB,IAAA;AACzB;;;AC5EA,IAAI,uBAAA,GAA4C,IAAA;AAgBzC,SAAS,UAAU,EAAA,EAAsB;AAC9C,EAAA,uBAAA,GAA0B,EAAE,CAAA;AAC9B;AAKO,SAAS,oBAAoB,SAAA,EAA+C;AACjF,EAAA,MAAM,IAAA,GAAO,uBAAA;AACb,EAAA,uBAAA,GAA0B,SAAA;AAC1B,EAAA,OAAO,IAAA;AACT;;;AC/BO,IAAM,UAAmB,OAAO,OAAA,KAAY,cAC9C,OAAA,CAAS,GAAA,EAAK,aAAa,YAAA,GAC5B;AAQJ,IAAI,aAAA,GAAqC,IAAA;AAalC,SAAS,QAAQ,OAAA,EAA6B;AACnD,EAAA,aAAA,GAAgB,OAAA;AAClB;AAGO,SAAS,WAAA,CAAY,OAAgB,MAAA,EAAuB;AACjE,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,IAAI;AAAE,MAAA,aAAA,CAAc,OAAO,MAAA,GAAS,EAAE,MAAA,EAAO,GAAI,EAAE,CAAA;AAAA,IAAG,CAAA,CAAA,MAAQ;AAAA,IAA8B;AAAA,EAC9F;AACA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,IAAU,SAAS,WAAW,KAAK,CAAA;AAAA,EAC9D;AACF;AC1BA,IAAM,SAAA,GAAY,EAAA;AAClB,IAAM,kBAAA,GAAqB,GAAA;AAC3B,IAAM,OAAyB,EAAC;AAChC,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,GAAI,SAAA,EAAW,KAAK,IAAA,CAAK,IAAA,CAAK,EAAE,CAAA;AAChD,IAAI,OAAA,GAAU,SAAA;AAEd,SAAS,YAAA,GAA+B;AACtC,EAAA,IAAI,UAAU,CAAA,EAAG;AACf,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,EAAE,OAAO,CAAA;AAC1B,IAAA,GAAA,CAAI,MAAA,GAAS,CAAA;AACb,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,EAAC;AACV;AAEA,SAAS,aAAa,GAAA,EAA2B;AAC/C,EAAA,GAAA,CAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI,UAAU,SAAA,EAAW;AACvB,IAAA,IAAA,CAAK,SAAS,CAAA,GAAI,GAAA;AAAA,EACpB;AACF;AAMA,SAAS,WAAW,EAAA,EAAoC;AACtD,EAAA,IAAI,OAAO,MAAA,EAAW;AACtB,EAAA,IAAI;AACF,IAAA,EAAA,EAAG;AAAA,EACL,SAAS,CAAA,EAAG;AACV,IAAA,WAAA,CAAY,GAAG,gBAAgB,CAAA;AAAA,EACjC;AACF;AAEA,SAAS,YAAY,GAAA,EAAuC;AAC1D,EAAA,IAAI,QAAQ,MAAA,EAAW;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,IAAI;AAAE,MAAA,GAAA,CAAI,CAAC,CAAA,EAAG;AAAA,IAAG,SAAS,CAAA,EAAG;AAAE,MAAA,WAAA,CAAY,GAAG,gBAAgB,CAAA;AAAA,IAAG;AAAA,EACnE;AACF;AA+BO,SAAS,eAAe,EAAA,EAA4B;AACzD,EAAA,MAAM,OAAA,GAAUC,oBAAU,EAAE,CAAA;AAC5B,EAAA,IAAI,eAAc,EAAG;AACnB,IAAA,gBAAA,CAAiB,OAAO,CAAA;AAAA,EAC1B;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,aAAa,EAAA,EAA2C;AACtE,EAAA,MAAM,iBAAiB,aAAA,EAAc;AAIrC,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,cAAA;AAEJ,EAAA,MAAM,UAAA,GAAa,CAAC,EAAA,KAAmB;AACrC,IAAA,IAAI,mBAAmB,MAAA,EAAW;AAChC,MAAA,cAAA,CAAe,KAAK,EAAE,CAAA;AACtB,MAAA;AAAA,IACF;AACA,IAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,MAAA,MAAM,MAAM,YAAA,EAAa;AACzB,MAAA,GAAA,CAAI,IAAA,CAAK,aAAa,EAAE,CAAA;AACxB,MAAA,WAAA,GAAc,MAAA;AACd,MAAA,cAAA,GAAiB,GAAA;AACjB,MAAA;AAAA,IACF;AACA,IAAA,WAAA,GAAc,EAAA;AAAA,EAChB,CAAA;AASA,EAAA,IAAI,gBAAA,GAAmB,KAAA;AACvB,EAAA,IAAI,QAAA,GAAW,IAAA;AACf,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,cAAA,GAAiB,KAAA;AAErB,EAAA,MAAM,UAAU,MAAM;AAEpB,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,UAAA,CAAW,OAAO,CAAA;AAClB,MAAA,OAAA,GAAU,MAAA;AAAA,IACZ;AACA,IAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,MAAA,WAAA,CAAY,UAAU,CAAA;AACtB,MAAA,YAAA,CAAa,UAAU,CAAA;AACvB,MAAA,UAAA,GAAa,MAAA;AAAA,IACf;AAIA,IAAA,IAAI,gBAAA,EAAkB;AACpB,MAAA,IAAI;AAAE,QAAA,EAAA,EAAG;AAAA,MAAG,SAAS,CAAA,EAAG;AAAE,QAAA,WAAA,CAAY,GAAG,QAAQ,CAAA;AAAA,MAAG;AACpD,MAAA;AAAA,IACF;AAEA,IAAA,WAAA,GAAc,MAAA;AACd,IAAA,cAAA,GAAiB,MAAA;AAGjB,IAAA,MAAM,aAAA,GAAgB,oBAAoB,UAAU,CAAA;AAEpD,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,EAAA,EAAG;AAElB,MAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,QAAA,UAAA,CAAW,MAAoB,CAAA;AAAA,MACjC;AAGA,MAAA,IAAI,WAAA,KAAgB,KAAA,CAAA,IAAa,cAAA,KAAmB,KAAA,CAAA,EAAW;AAE7D,QAAA,IAAI,UAAU,gBAAA,GAAmB,IAAA;AACjC,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,mBAAmB,KAAA,CAAA,EAAW;AAChC,QAAA,UAAA,GAAa,cAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,OAAA,GAAU,WAAA;AAAA,MACZ;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,WAAA,CAAY,GAAG,QAAQ,CAAA;AAEvB,MAAA,IAAI,mBAAmB,MAAA,EAAW;AAChC,QAAA,UAAA,GAAa,cAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,OAAA,GAAU,WAAA;AAAA,MACZ;AAAA,IACF,CAAA,SAAE;AACA,MAAA,mBAAA,CAAoB,aAAa,CAAA;AACjC,MAAA,QAAA,GAAW,KAAA;AAAA,IACb;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAS,MAAM;AACnB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,cAAA,GAAiB,IAAA;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,IAAI;AACF,MAAA,IAAI,aAAA,GAAgB,CAAA;AACpB,MAAA,GAAG;AACD,QAAA,cAAA,GAAiB,KAAA;AACjB,QAAA,OAAA,EAAQ;AACR,QAAA,IAAI,cAAA,EAAgB;AAClB,UAAA,aAAA,EAAA;AACA,UAAA,IAAI,iBAAiB,kBAAA,EAAoB;AACvC,YAAA,WAAA;AAAA,cACE,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,kBAAkB,CAAA,gBAAA,CAAkB,CAAA;AAAA,cACvE;AAAA,aACF;AACA,YAAA,cAAA,GAAiB,KAAA;AAAA,UACnB;AAAA,QACF;AAAA,MACF,CAAA,QAAS,cAAA;AAAA,IACX,CAAA,SAAE;AACA,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,OAAA,GAAUA,oBAAU,MAAM,CAAA;AAGhC,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,MAAM,iBAAiB,MAAM;AAC3B,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,OAAA,EAAQ;AACR,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,UAAA,CAAW,OAAO,CAAA;AAClB,MAAA,OAAA,GAAU,MAAA;AAAA,IACZ;AACA,IAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,MAAA,WAAA,CAAY,UAAU,CAAA;AACtB,MAAA,YAAA,CAAa,UAAU,CAAA;AACvB,MAAA,UAAA,GAAa,MAAA;AAAA,IACf;AAAA,EACF,CAAA;AAGA,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,gBAAA,CAAiB,cAAc,CAAA;AAAA,EACjC;AAEA,EAAA,OAAO,cAAA;AACT;;;ACtNO,IAAM,UAAA,GAAoCC;ACJ1C,SAAS,MAAM,EAAA,EAAsB;AAC1C,EAAAC,uBAAA,EAAW;AACX,EAAA,IAAI;AACF,IAAA,EAAA,EAAG;AAAA,EACL,CAAA,SAAE;AACA,IAAAC,qBAAA,EAAS;AAAA,EACX;AACF;ACXO,SAAS,QAAW,EAAA,EAAgB;AACzC,EAAA,MAAM,IAAA,GAAOC,0BAAa,MAAS,CAAA;AACnC,EAAA,IAAI;AACF,IAAA,OAAO,EAAA,EAAG;AAAA,EACZ,CAAA,SAAE;AACA,IAAAA,yBAAA,CAAa,IAAI,CAAA;AAAA,EACnB;AACF;;;ACYO,SAAS,EAAA,CACd,IAAA,EACA,EAAA,EACA,OAAA,EACqB;AACrB,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,OAAA,GAAU,IAAA;AAEd,EAAA,OAAO,MAAM;AAEX,IAAA,MAAMC,SAAQ,IAAA,EAAK;AAEnB,IAAA,IAAI,OAAA,EAAS,SAAS,OAAA,EAAS;AAC7B,MAAA,OAAA,GAAU,KAAA;AACV,MAAA,IAAA,GAAOA,MAAAA;AACP,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,SAAS,OAAA,CAAQ,MAAM,EAAA,CAAGA,MAAAA,EAAO,IAAI,CAAC,CAAA;AAC5C,IAAA,IAAA,GAAOA,MAAAA;AACP,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AACF;;;AC9BO,SAAS,UAAa,YAAA,EAAyB;AACpD,EAAA,OAAO,EAAE,SAAS,YAAA,EAAa;AACjC;;;ACEO,SAAS,aAAA,CACd,SACA,YAAA,EACiD;AACjD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,+BAAa,YAAY,CAAA;AAEnD,EAAA,MAAM,QAAA,GAAwB,CAAC,MAAA,KAAW;AACxC,IAAA,QAAA,CAAS,CAAC,IAAA,KAAS,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAC,CAAA;AAAA,EAC1C,CAAA;AAEA,EAAA,OAAO,CAAC,OAAO,QAAQ,CAAA;AACzB;;;AC1BA,IAAI,sBAAA,GAAiD,IAAA;AACrD,IAAM,gBAA4C,EAAC;AAE5C,SAAS,oBAAoB,GAAA,EAA4B;AAC9D,EAAA,aAAA,CAAc,KAAK,sBAAsB,CAAA;AACzC,EAAA,sBAAA,GAAyB,GAAA;AAC3B;AAEO,SAAS,kBAAA,GAA2B;AACzC,EAAA,sBAAA,GAAyB,aAAA,CAAc,KAAI,IAAK,IAAA;AAClD;AAGO,SAAS,kBAAA,GAA6C;AAC3D,EAAA,OAAO,sBAAA;AACT;;;ACyBO,SAAS,cAAA,CACd,MAAA,EACA,OAAA,EACA,OAAA,EACa;AACb,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,8BAAA,CAA4B,SAAS,YAAY,CAAA;AACzE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,+BAAa,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,+BAAsB,MAAS,CAAA;AAKzD,EAAA,MAAM,cAAc,kBAAA,EAAmB;AAEvC,EAAA,IAAI,eAAA,GAA0C,IAAA;AAC9C,EAAA,IAAI,YAAA,GAAe,CAAA;AAEnB,EAAA,MAAM,UAAU,MAAM;AAEpB,IAAA,MAAM,WAAA,GAAc,QAAQ,MAAM,CAAA;AAGlC,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,eAAA,CAAgB,KAAA,EAAM;AAAA,IACxB;AACA,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,eAAA,GAAkB,UAAA;AAElB,IAAA,MAAM,UAAU,EAAE,YAAA;AAClB,IAAA,MAAM,QAAA,GAAW,MAAM,OAAA,KAAY,YAAA;AACnC,IAAA,IAAI,eAAA,GAAkB,KAAA;AAGtB,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,WAAA,CAAY,SAAA,EAAU;AACtB,MAAA,eAAA,GAAkB,IAAA;AAAA,IACpB;AAEA,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,MAAS,CAAA;AAElB,IAAA,OAAA,CAAQ,QAAQ,OAAA,CAAQ,WAAW,CAAC,CAAA,CACjC,IAAA,CAAK,CAAC,MAAA,KAAW;AAEhB,MAAA,IAAI,QAAA,EAAS,IAAK,CAAC,UAAA,CAAW,OAAO,OAAA,EAAS;AAC5C,QAAA,OAAA,CAAQ,MAAM,MAAM,CAAA;AAAA,MACtB;AAAA,IACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAQ;AACd,MAAA,IAAI,QAAA,EAAS,IAAK,CAAC,UAAA,CAAW,OAAO,OAAA,EAAS;AAE5C,QAAA,IAAI,GAAA,EAAK,SAAS,YAAA,EAAc;AAC9B,UAAA,QAAA,CAAS,GAAG,CAAA;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,eAAA,eAA8B,SAAA,EAAU;AAC5C,MAAA,IAAI,UAAS,EAAG;AACd,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,IAAI,oBAAoB,UAAA,EAAY;AAClC,UAAA,eAAA,GAAkB,IAAA;AAAA,QACpB;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AAAA,EACL,CAAA;AAGA,EAAA,cAAA,CAAe,MAAM;AACnB,IAAA,MAAA,EAAO;AACP,IAAA,OAAA,EAAQ;AAAA,EACV,CAAC,CAAA;AAGD,EAAA,MAAM,QAAA,IAAY,MAAM,IAAA,EAAK,CAAA;AAC7B,EAAA,QAAA,CAAS,OAAA,GAAU,OAAA;AACnB,EAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AACjB,EAAA,QAAA,CAAS,OAAA,GAAU,OAAA;AACnB,EAAA,QAAA,CAAS,MAAA,GAAS,CAACD,MAAAA,KAAU,OAAA,CAAQ,MAAMA,MAAK,CAAA;AAEhD,EAAA,OAAO,QAAA;AACT","file":"chunk-DCTOXHPF.cjs","sourcesContent":["/**\n * Forma Reactive - Root\n *\n * Explicit reactive ownership scope. All effects created inside a root\n * are automatically disposed when the root is torn down.\n *\n * Uses alien-signals' `effectScope` under the hood for native graph-level\n * effect tracking, with a userland disposer list for non-effect cleanup\n * (e.g., event listeners, DOM references, timers).\n */\n\nimport { effectScope as rawEffectScope } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Root scope tracking\n// ---------------------------------------------------------------------------\n\nlet currentRoot: RootScope | null = null;\nconst rootStack: (RootScope | null)[] = [];\n\ninterface RootScope {\n /** Userland disposers (event listeners, DOM refs, timers, etc.) */\n disposers: (() => void)[];\n /** alien-signals effect scope dispose — tears down all reactive effects */\n scopeDispose: (() => void) | null;\n}\n\n/**\n * Create a reactive root scope.\n *\n * All effects created (via `createEffect`) inside the callback are tracked\n * at both the reactive graph level (via alien-signals effectScope) and the\n * userland level (via registerDisposer). The returned `dispose` function\n * tears down everything.\n *\n * ```ts\n * const dispose = createRoot(() => {\n * createEffect(() => console.log(count()));\n * createEffect(() => console.log(name()));\n * });\n * // later: dispose() stops both effects\n * ```\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const scope: RootScope = { disposers: [], scopeDispose: null };\n\n rootStack.push(currentRoot);\n currentRoot = scope;\n\n const dispose = () => {\n // Dispose alien-signals effect scope first (reactive graph cleanup)\n if (scope.scopeDispose) {\n try { scope.scopeDispose(); } catch { /* ensure userland disposers still run */ }\n scope.scopeDispose = null;\n }\n // Then run userland disposers\n for (const d of scope.disposers) {\n try { d(); } catch { /* ensure all disposers run */ }\n }\n scope.disposers.length = 0;\n };\n\n let result: T;\n try {\n // Wrap in alien-signals effectScope for native effect tracking\n scope.scopeDispose = rawEffectScope(() => {\n result = fn(dispose);\n });\n } finally {\n currentRoot = rootStack.pop() ?? null;\n }\n\n return result!;\n}\n\n/**\n * @internal — called by createEffect to register disposers in the current root.\n */\nexport function registerDisposer(dispose: () => void): void {\n if (currentRoot) {\n currentRoot.disposers.push(dispose);\n }\n}\n\n/**\n * @internal — check if we're inside a root scope.\n */\nexport function hasActiveRoot(): boolean {\n return currentRoot !== null;\n}\n","/**\n * Forma Reactive - Cleanup\n *\n * Register cleanup functions within reactive scopes.\n * Inspired by SolidJS onCleanup().\n */\n\n// ---------------------------------------------------------------------------\n// Cleanup context tracking\n// ---------------------------------------------------------------------------\n\ntype CleanupCollector = ((fn: () => void) => void) | null;\n\nlet currentCleanupCollector: CleanupCollector = null;\n\n/**\n * Register a cleanup function in the current reactive scope.\n * The cleanup runs before the effect re-executes and on disposal.\n *\n * More composable than returning a cleanup from the effect function,\n * since it can be called from helper functions.\n *\n * ```ts\n * createEffect(() => {\n * const timer = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timer));\n * });\n * ```\n */\nexport function onCleanup(fn: () => void): void {\n currentCleanupCollector?.(fn);\n}\n\n/**\n * @internal — Set the cleanup collector for the current effect execution.\n */\nexport function setCleanupCollector(collector: CleanupCollector): CleanupCollector {\n const prev = currentCleanupCollector;\n currentCleanupCollector = collector;\n return prev;\n}\n","/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by bundler (tsup define). Defaults to true for unbundled usage.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\nexport const __DEV__: boolean = typeof process !== 'undefined'\n ? (process!.env?.NODE_ENV !== 'production')\n : true;\n\n// ---------------------------------------------------------------------------\n// Global error handler\n// ---------------------------------------------------------------------------\n\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nlet _errorHandler: ErrorHandler | null = null;\n\n/**\n * Install a global error handler for FormaJS reactive errors.\n * Called when effects, computeds, or event handlers throw.\n *\n * ```ts\n * onError((err, info) => {\n * console.error(`[${info?.source}]`, err);\n * Sentry.captureException(err);\n * });\n * ```\n */\nexport function onError(handler: ErrorHandler): void {\n _errorHandler = handler;\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n if (_errorHandler) {\n try { _errorHandler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (__DEV__) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Effect\n *\n * Side-effectful reactive computation that auto-tracks signal dependencies.\n * Backed by alien-signals for automatic dependency tracking.\n *\n * TC39 Signals equivalent: Signal.subtle.Watcher (effect is a userland concept)\n */\n\nimport { effect as rawEffect } from 'alien-signals';\nimport { hasActiveRoot, registerDisposer } from './root.js';\nimport { setCleanupCollector } from './cleanup.js';\nimport { reportError } from './dev.js';\n\n// ---------------------------------------------------------------------------\n// Cleanup array pool — avoids allocating a new array every effect re-run\n// ---------------------------------------------------------------------------\n\nconst POOL_SIZE = 32;\nconst MAX_REENTRANT_RUNS = 100;\nconst pool: (() => void)[][] = [];\nfor (let i = 0; i < POOL_SIZE; i++) pool.push([]);\nlet poolIdx = POOL_SIZE;\n\nfunction acquireArray(): (() => void)[] {\n if (poolIdx > 0) {\n const arr = pool[--poolIdx]!;\n arr.length = 0;\n return arr;\n }\n return [];\n}\n\nfunction releaseArray(arr: (() => void)[]): void {\n arr.length = 0;\n if (poolIdx < POOL_SIZE) {\n pool[poolIdx++] = arr;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified cleanup runner — single function for both re-run and dispose paths\n// ---------------------------------------------------------------------------\n\nfunction runCleanup(fn: (() => void) | undefined): void {\n if (fn === undefined) return;\n try {\n fn();\n } catch (e) {\n reportError(e, 'effect cleanup');\n }\n}\n\nfunction runCleanups(bag: (() => void)[] | undefined): void {\n if (bag === undefined) return;\n for (let i = 0; i < bag.length; i++) {\n try { bag[i]!(); } catch (e) { reportError(e, 'effect cleanup'); }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive effect that auto-tracks signal dependencies.\n *\n * The provided function runs immediately and re-runs whenever any signal it\n * reads changes. If the function returns a cleanup function, that cleanup is\n * called before each re-run and on disposal.\n *\n * Additionally, `onCleanup()` can be called inside the effect to register\n * cleanup functions composably.\n *\n * Returns a dispose function that stops the effect.\n */\n/**\n * @internal — Lightweight effect for Forma's internal DOM bindings.\n *\n * Bypasses createEffect's cleanup infrastructure (pool, collector, error\n * reporting, engine compression) since internal effects never use\n * onCleanup() or return cleanup functions. This saves ~4 function calls\n * per effect creation and per re-run.\n *\n * ONLY use for effects that:\n * 1. Never call onCleanup()\n * 2. Never return a cleanup function\n * 3. Contain simple \"read signal → write DOM\" logic\n */\nexport function internalEffect(fn: () => void): () => void {\n const dispose = rawEffect(fn);\n if (hasActiveRoot()) {\n registerDisposer(dispose);\n }\n return dispose;\n}\n\nexport function createEffect(fn: () => void | (() => void)): () => void {\n const shouldRegister = hasActiveRoot();\n\n // Most effects have zero or one cleanup. Track the single-cleanup case\n // without array allocation; only promote to pooled array when needed.\n let cleanup: (() => void) | undefined;\n let cleanupBag: (() => void)[] | undefined;\n let nextCleanup: (() => void) | undefined;\n let nextCleanupBag: (() => void)[] | undefined;\n\n const addCleanup = (cb: () => void) => {\n if (nextCleanupBag !== undefined) {\n nextCleanupBag.push(cb);\n return;\n }\n if (nextCleanup !== undefined) {\n const bag = acquireArray();\n bag.push(nextCleanup, cb);\n nextCleanup = undefined;\n nextCleanupBag = bag;\n return;\n }\n nextCleanup = cb;\n };\n\n // \"Engine Compression\" exploit: most effects never use cleanup (onCleanup()\n // or return value). After the first clean run, we know this effect is\n // \"cleanup-free\" and can skip the entire cleanup infrastructure on re-runs.\n // This saves 4 function calls per re-run: acquireArray, setCleanupCollector,\n // releaseArray, and bag inspection. Like the engine compression ratio exploit —\n // the \"rules\" say we should always prepare for cleanup, but we measure at\n // \"room temperature\" (first run) and exploit the gap.\n let skipCleanupInfra = false;\n let firstRun = true;\n let running = false;\n let rerunRequested = false;\n\n const runOnce = () => {\n // Run and clear previous cleanups (only if any exist).\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n\n // Ultra-fast path: this effect has proven it doesn't use cleanup.\n // Skip acquireArray + setCleanupCollector + bag checks + releaseArray.\n if (skipCleanupInfra) {\n try { fn(); } catch (e) { reportError(e, 'effect'); }\n return;\n }\n\n nextCleanup = undefined;\n nextCleanupBag = undefined;\n\n // Full path: install collector for onCleanup() calls.\n const prevCollector = setCleanupCollector(addCleanup);\n\n try {\n const result = fn();\n\n if (typeof result === 'function') {\n addCleanup(result as () => void);\n }\n\n // Hot path: no cleanups registered or returned — enable ultra-fast path.\n if (nextCleanup === undefined && nextCleanupBag === undefined) {\n // First clean run → enable ultra-fast path for all subsequent runs\n if (firstRun) skipCleanupInfra = true;\n return;\n }\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } catch (e) {\n reportError(e, 'effect');\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } finally {\n setCleanupCollector(prevCollector);\n firstRun = false;\n }\n };\n\n const safeFn = () => {\n if (running) {\n rerunRequested = true;\n return;\n }\n\n running = true;\n try {\n let reentrantRuns = 0;\n do {\n rerunRequested = false;\n runOnce();\n if (rerunRequested) {\n reentrantRuns++;\n if (reentrantRuns >= MAX_REENTRANT_RUNS) {\n reportError(\n new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`),\n 'effect',\n );\n rerunRequested = false;\n }\n }\n } while (rerunRequested);\n } finally {\n running = false;\n }\n };\n\n const dispose = rawEffect(safeFn);\n\n // Wrap dispose to also run final cleanups\n let disposed = false;\n const wrappedDispose = () => {\n if (disposed) return;\n disposed = true;\n dispose();\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n };\n\n // Register in current root scope (if any)\n if (shouldRegister) {\n registerDisposer(wrappedDispose);\n }\n\n return wrappedDispose;\n}\n","/**\n * Forma Reactive - Memo\n *\n * Alias for createComputed with SolidJS/React-familiar naming.\n * A memoized derived value that only recomputes when its dependencies change.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { createComputed } from './computed.js';\n\n/**\n * Create a memoized computed value.\n * Identical to `createComputed` — provided for React/SolidJS familiarity.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), both createComputed and createMemo in FormaJS are lazy\n * cached derivations — equivalent to SolidJS's createMemo. They are\n * identical.\n *\n * The computation runs lazily and caches the result. It only recomputes\n * when a signal it reads during computation changes.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createMemo(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n */\nexport const createMemo: typeof createComputed = createComputed;\n","/**\n * Forma Reactive - Batch\n *\n * Groups multiple signal updates and defers effect execution until the\n * outermost batch completes. Prevents intermediate re-renders.\n * Backed by alien-signals.\n *\n * TC39 Signals: no built-in batch — this is a userland optimization.\n */\n\nimport { startBatch, endBatch } from 'alien-signals';\n\n/**\n * Group multiple signal updates so that effects only run once after all\n * updates have been applied.\n *\n * ```ts\n * batch(() => {\n * setA(1);\n * setB(2);\n * // effects that depend on A and B won't run yet\n * });\n * // effects run here, once\n * ```\n *\n * Batches are nestable — only the outermost batch triggers the flush.\n */\nexport function batch(fn: () => void): void {\n startBatch();\n try {\n fn();\n } finally {\n endBatch();\n }\n}\n","/**\n * Forma Reactive - Untrack\n *\n * Read signals without subscribing to them in the reactive graph.\n * Essential for reading values inside effects without creating dependencies.\n *\n * TC39 Signals equivalent: Signal.subtle.untrack()\n */\n\nimport { setActiveSub } from 'alien-signals';\n\n/**\n * Execute a function without tracking signal reads.\n * Any signals read inside `fn` will NOT become dependencies of the\n * surrounding effect or computed.\n *\n * ```ts\n * createEffect(() => {\n * const a = count(); // tracked — effect re-runs when count changes\n * const b = untrack(() => other()); // NOT tracked — effect ignores other changes\n * });\n * ```\n */\nexport function untrack<T>(fn: () => T): T {\n const prev = setActiveSub(undefined);\n try {\n return fn();\n } finally {\n setActiveSub(prev);\n }\n}\n","/**\n * Forma Reactive - On\n *\n * Explicit dependency tracking for effects.\n * Only re-runs when the specified signals change, ignoring all other reads.\n *\n * SolidJS equivalent: on()\n * Vue equivalent: watch() with explicit deps\n * React equivalent: useEffect dependency array\n */\n\nimport { untrack } from './untrack.js';\n\n/**\n * Create a tracked effect body that only fires when specific dependencies change.\n *\n * Wraps a function so that only the `deps` signals are tracked.\n * All signal reads inside `fn` are untracked (won't cause re-runs).\n *\n * Use with `createEffect`:\n *\n * ```ts\n * const [a, setA] = createSignal(1);\n * const [b, setB] = createSignal(2);\n *\n * // Only re-runs when `a` changes, NOT when `b` changes:\n * createEffect(on(a, (value, prev) => {\n * console.log(`a changed: ${prev} → ${value}, b is ${b()}`);\n * }));\n *\n * setA(10); // fires: \"a changed: 1 → 10, b is 2\"\n * setB(20); // does NOT fire\n * ```\n *\n * Multiple dependencies:\n * ```ts\n * createEffect(on(\n * () => [a(), b()] as const,\n * ([aVal, bVal], prev) => { ... }\n * ));\n * ```\n */\nexport function on<T, U>(\n deps: () => T,\n fn: (value: T, prev: T | undefined) => U,\n options?: { defer?: boolean },\n): () => U | undefined {\n let prev: T | undefined;\n let isFirst = true;\n\n return () => {\n // Track only the deps\n const value = deps();\n\n if (options?.defer && isFirst) {\n isFirst = false;\n prev = value;\n return undefined;\n }\n\n // Run the body untracked so it doesn't add extra dependencies\n const result = untrack(() => fn(value, prev));\n prev = value;\n return result;\n };\n}\n","/**\n * Forma Reactive - Ref\n *\n * Mutable container that does NOT trigger reactivity.\n * Use for DOM references, previous values, instance variables —\n * anything that needs to persist across effect re-runs without\n * causing re-execution.\n *\n * React equivalent: useRef\n * SolidJS equivalent: (none — uses plain variables in setup)\n */\n\nexport interface Ref<T> {\n current: T;\n}\n\n/**\n * Create a mutable ref container.\n *\n * Unlike signals, writing to `.current` does NOT trigger effects.\n * Use when you need a stable reference across reactive scopes.\n *\n * ```ts\n * const timerRef = createRef<number | null>(null);\n *\n * createEffect(() => {\n * timerRef.current = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timerRef.current!));\n * });\n *\n * // DOM ref pattern:\n * const elRef = createRef<HTMLElement | null>(null);\n * h('div', { ref: (el) => { elRef.current = el; } });\n * ```\n */\nexport function createRef<T>(initialValue: T): Ref<T> {\n return { current: initialValue };\n}\n","/**\n * Forma Reactive - Reducer\n *\n * State machine pattern — dispatch actions to a pure reducer function.\n * Fine-grained: only the resulting state signal is reactive.\n *\n * React equivalent: useReducer\n * SolidJS equivalent: (none — uses createSignal + helpers)\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\n\nexport type Dispatch<A> = (action: A) => void;\n\n/**\n * Create a reducer — predictable state updates via dispatched actions.\n *\n * The reducer function must be pure: `(state, action) => newState`.\n * Returns a [state, dispatch] tuple.\n *\n * ```ts\n * type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };\n *\n * const [count, dispatch] = createReducer(\n * (state: number, action: Action) => {\n * switch (action.type) {\n * case 'increment': return state + 1;\n * case 'decrement': return state - 1;\n * case 'reset': return 0;\n * }\n * },\n * 0,\n * );\n *\n * dispatch({ type: 'increment' }); // count() === 1\n * dispatch({ type: 'increment' }); // count() === 2\n * dispatch({ type: 'reset' }); // count() === 0\n * ```\n */\nexport function createReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initialState: S,\n): [state: SignalGetter<S>, dispatch: Dispatch<A>] {\n const [state, setState] = createSignal(initialState);\n\n const dispatch: Dispatch<A> = (action) => {\n setState((prev) => reducer(prev, action));\n };\n\n return [state, dispatch];\n}\n","/**\n * Forma Reactive - Suspense Context\n *\n * Shared context stack for Suspense boundaries. Lives in the reactive layer\n * (not DOM) so that createResource can import it without circular dependencies.\n *\n * The pattern mirrors the lifecycle context stack in component/define.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SuspenseContext {\n /** Increment when a resource starts loading inside this boundary. */\n increment(): void;\n /** Decrement when a resource resolves/rejects inside this boundary. */\n decrement(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Context stack\n// ---------------------------------------------------------------------------\n\nlet currentSuspenseContext: SuspenseContext | null = null;\nconst suspenseStack: (SuspenseContext | null)[] = [];\n\nexport function pushSuspenseContext(ctx: SuspenseContext): void {\n suspenseStack.push(currentSuspenseContext);\n currentSuspenseContext = ctx;\n}\n\nexport function popSuspenseContext(): void {\n currentSuspenseContext = suspenseStack.pop() ?? null;\n}\n\n/** Get the current Suspense context (if any). Called by createResource. */\nexport function getSuspenseContext(): SuspenseContext | null {\n return currentSuspenseContext;\n}\n","/**\n * Forma Reactive - Resource\n *\n * Async data fetching primitive with reactive loading/error state.\n * Tracks a source signal and refetches when it changes.\n *\n * SolidJS equivalent: createResource\n * React equivalent: use() + Suspense (React 19), or useSWR/react-query\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\nimport { internalEffect } from './effect.js';\nimport { untrack } from './untrack.js';\nimport { getSuspenseContext } from './suspense-context.js';\n\nexport interface Resource<T> {\n /** The resolved data (or undefined while loading). */\n (): T | undefined;\n /** True while the fetcher is running. */\n loading: SignalGetter<boolean>;\n /** The error if the fetcher rejected (or undefined). */\n error: SignalGetter<unknown>;\n /** Manually refetch with the current source value. */\n refetch: () => void;\n /** Manually set the data (overrides fetcher result). */\n mutate: (value: T | undefined) => void;\n}\n\nexport interface ResourceOptions<T> {\n /** Initial value before first fetch resolves. */\n initialValue?: T;\n}\n\n/**\n * Create an async resource that fetches data reactively.\n *\n * When `source` changes, the fetcher re-runs automatically.\n * Provides reactive `loading` and `error` signals.\n *\n * ```ts\n * const [userId, setUserId] = createSignal(1);\n *\n * const user = createResource(\n * userId, // source signal\n * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher\n * );\n *\n * internalEffect(() => {\n * if (user.loading()) console.log('Loading...');\n * else if (user.error()) console.log('Error:', user.error());\n * else console.log('User:', user());\n * });\n *\n * setUserId(2); // automatically refetches\n * ```\n *\n * Without a source signal (static fetch):\n * ```ts\n * const posts = createResource(\n * () => true, // constant source — fetches once\n * () => fetch('/api/posts').then(r => r.json()),\n * );\n * ```\n */\nexport function createResource<T, S = true>(\n source: SignalGetter<S>,\n fetcher: (source: S) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> {\n const [data, setData] = createSignal<T | undefined>(options?.initialValue);\n const [loading, setLoading] = createSignal(false);\n const [error, setError] = createSignal<unknown>(undefined);\n\n // Capture the Suspense context at creation time (not at fetch time).\n // This is critical because the Suspense boundary pushes/pops its context\n // synchronously during children() execution.\n const suspenseCtx = getSuspenseContext();\n\n let abortController: AbortController | null = null;\n let fetchVersion = 0;\n\n const doFetch = () => {\n // Read source outside tracking to get current value\n const sourceValue = untrack(source);\n\n // Abort previous in-flight request\n if (abortController) {\n abortController.abort();\n }\n const controller = new AbortController();\n abortController = controller;\n\n const version = ++fetchVersion;\n const isLatest = () => version === fetchVersion;\n let suspensePending = false;\n\n // Notify Suspense boundary that a fetch has started\n if (suspenseCtx) {\n suspenseCtx.increment();\n suspensePending = true;\n }\n\n setLoading(true);\n setError(undefined);\n\n Promise.resolve(fetcher(sourceValue))\n .then((result) => {\n // Only apply if this is still the latest fetch and wasn't aborted.\n if (isLatest() && !controller.signal.aborted) {\n setData(() => result);\n }\n })\n .catch((err) => {\n if (isLatest() && !controller.signal.aborted) {\n // Ignore abort errors\n if (err?.name !== 'AbortError') {\n setError(err);\n }\n }\n })\n .finally(() => {\n if (suspensePending) suspenseCtx?.decrement();\n if (isLatest()) {\n setLoading(false);\n if (abortController === controller) {\n abortController = null; // Release controller for GC\n }\n }\n });\n };\n\n // Auto-fetch when source changes\n internalEffect(() => {\n source(); // track the source signal\n doFetch();\n });\n\n // Build the resource object\n const resource = (() => data()) as Resource<T>;\n resource.loading = loading;\n resource.error = error;\n resource.refetch = doFetch;\n resource.mutate = (value) => setData(() => value);\n\n return resource;\n}\n"]} |
| import { internalEffect, createRoot, untrack, __DEV__ } from './chunk-OUVOAYIO.js'; | ||
| import { createSignal } from './chunk-OZCHIVAZ.js'; | ||
| // src/dom/element.ts | ||
| var Fragment = /* @__PURE__ */ Symbol.for("forma.fragment"); | ||
| var SVG_NS = "http://www.w3.org/2000/svg"; | ||
| var XLINK_NS = "http://www.w3.org/1999/xlink"; | ||
| var SVG_TAGS = /* @__PURE__ */ new Set([ | ||
| "svg", | ||
| "path", | ||
| "circle", | ||
| "rect", | ||
| "line", | ||
| "polyline", | ||
| "polygon", | ||
| "ellipse", | ||
| "g", | ||
| "text", | ||
| "tspan", | ||
| "textPath", | ||
| "defs", | ||
| "use", | ||
| "symbol", | ||
| "clipPath", | ||
| "mask", | ||
| "pattern", | ||
| "marker", | ||
| "linearGradient", | ||
| "radialGradient", | ||
| "stop", | ||
| "filter", | ||
| "feGaussianBlur", | ||
| "feColorMatrix", | ||
| "feOffset", | ||
| "feBlend", | ||
| "feMerge", | ||
| "feMergeNode", | ||
| "feComposite", | ||
| "feFlood", | ||
| "feMorphology", | ||
| "feTurbulence", | ||
| "feDisplacementMap", | ||
| "feImage", | ||
| "foreignObject", | ||
| "animate", | ||
| "animateTransform", | ||
| "animateMotion", | ||
| "set", | ||
| "image", | ||
| "switch", | ||
| "desc", | ||
| "title", | ||
| "metadata" | ||
| ]); | ||
| var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([ | ||
| "disabled", | ||
| "checked", | ||
| "readonly", | ||
| "required", | ||
| "autofocus", | ||
| "autoplay", | ||
| "controls", | ||
| "default", | ||
| "defer", | ||
| "formnovalidate", | ||
| "hidden", | ||
| "ismap", | ||
| "loop", | ||
| "multiple", | ||
| "muted", | ||
| "nomodule", | ||
| "novalidate", | ||
| "open", | ||
| "playsinline", | ||
| "reversed", | ||
| "selected", | ||
| "async" | ||
| ]); | ||
| var ELEMENT_PROTOS = null; | ||
| function getProto(tag) { | ||
| if (!ELEMENT_PROTOS) { | ||
| ELEMENT_PROTOS = /* @__PURE__ */ Object.create(null); | ||
| for (const t of [ | ||
| "div", | ||
| "span", | ||
| "p", | ||
| "a", | ||
| "li", | ||
| "ul", | ||
| "ol", | ||
| "button", | ||
| "input", | ||
| "label", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "section", | ||
| "header", | ||
| "footer", | ||
| "main", | ||
| "nav", | ||
| "table", | ||
| "tr", | ||
| "td", | ||
| "th", | ||
| "tbody", | ||
| "img", | ||
| "form", | ||
| "select", | ||
| "option", | ||
| "textarea", | ||
| "i", | ||
| "b", | ||
| "strong", | ||
| "em", | ||
| "small", | ||
| "article", | ||
| "aside", | ||
| "details", | ||
| "summary" | ||
| ]) { | ||
| ELEMENT_PROTOS[t] = document.createElement(t); | ||
| } | ||
| } | ||
| return ELEMENT_PROTOS[tag] ?? (ELEMENT_PROTOS[tag] = document.createElement(tag)); | ||
| } | ||
| var EVENT_NAMES = /* @__PURE__ */ Object.create(null); | ||
| function eventName(key) { | ||
| return EVENT_NAMES[key] ?? (EVENT_NAMES[key] = key.slice(2).toLowerCase()); | ||
| } | ||
| var ABORT_SYM = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| function getAbortController(el) { | ||
| let controller = el[ABORT_SYM]; | ||
| if (!controller) { | ||
| controller = new AbortController(); | ||
| el[ABORT_SYM] = controller; | ||
| } | ||
| return controller; | ||
| } | ||
| function cleanup(el) { | ||
| const controller = el[ABORT_SYM]; | ||
| if (controller) { | ||
| controller.abort(); | ||
| delete el[ABORT_SYM]; | ||
| } | ||
| } | ||
| var CACHE_SYM = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function getCache(el) { | ||
| return el[CACHE_SYM] ?? (el[CACHE_SYM] = /* @__PURE__ */ Object.create(null)); | ||
| } | ||
| function handleClass(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache["class"] === v) return; | ||
| cache["class"] = v; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = v; | ||
| } else { | ||
| el.setAttribute("class", v); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache["class"] === value) return; | ||
| cache["class"] = value; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| } | ||
| } | ||
| function handleStyle(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| let prevKeys = []; | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (typeof v === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === v) return; | ||
| cache["style"] = v; | ||
| prevKeys = []; | ||
| el.style.cssText = v; | ||
| } else if (v && typeof v === "object") { | ||
| const style = el.style; | ||
| const nextKeys = Object.keys(v); | ||
| for (const k of prevKeys) { | ||
| if (!(k in v)) { | ||
| style.removeProperty(k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())); | ||
| } | ||
| } | ||
| Object.assign(style, v); | ||
| prevKeys = nextKeys; | ||
| } | ||
| }); | ||
| } else if (typeof value === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === value) return; | ||
| cache["style"] = value; | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| } | ||
| function handleEvent(el, key, value) { | ||
| const controller = getAbortController(el); | ||
| el.addEventListener( | ||
| eventName(key), | ||
| value, | ||
| { signal: controller.signal } | ||
| ); | ||
| } | ||
| function handleInnerHTML(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const resolved = value(); | ||
| if (resolved == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof resolved !== "object" || !("__html" in resolved)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof resolved | ||
| ); | ||
| } | ||
| const html = resolved.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| const cache = getCache(el); | ||
| if (cache["innerHTML"] === html) return; | ||
| cache["innerHTML"] = html; | ||
| el.innerHTML = html; | ||
| }); | ||
| } else { | ||
| if (value == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| } | ||
| } | ||
| function handleXLink(el, key, value) { | ||
| const localName = key.slice(6); | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(v)); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| } | ||
| } | ||
| } | ||
| function handleBooleanAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache[key] === v) return; | ||
| cache[key] = v; | ||
| if (v) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache[key] === value) return; | ||
| cache[key] = value; | ||
| if (value) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| } | ||
| } | ||
| function handleGenericAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(v); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(value); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| } | ||
| } | ||
| var PROP_HANDLERS = /* @__PURE__ */ new Map(); | ||
| PROP_HANDLERS.set("class", handleClass); | ||
| PROP_HANDLERS.set("className", handleClass); | ||
| PROP_HANDLERS.set("style", handleStyle); | ||
| PROP_HANDLERS.set("ref", () => { | ||
| }); | ||
| PROP_HANDLERS.set("dangerouslySetInnerHTML", handleInnerHTML); | ||
| for (const attr of BOOLEAN_ATTRS) { | ||
| PROP_HANDLERS.set(attr, handleBooleanAttr); | ||
| } | ||
| function applyProp(el, key, value) { | ||
| if (key === "class") { | ||
| handleClass(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| return; | ||
| } | ||
| const handler = PROP_HANDLERS.get(key); | ||
| if (handler) { | ||
| handler(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| handleXLink(el, key, value); | ||
| return; | ||
| } | ||
| handleGenericAttr(el, key, value); | ||
| } | ||
| function applyStaticProp(el, key, value) { | ||
| if (value == null || value === false) return; | ||
| if (key === "class" || key === "className") { | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "style") { | ||
| if (typeof value === "string") { | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "dangerouslySetInnerHTML") { | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| return; | ||
| } | ||
| if (BOOLEAN_ATTRS.has(key)) { | ||
| if (value) el.setAttribute(key, ""); | ||
| return; | ||
| } | ||
| if (value === true) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.setAttribute(key, String(value)); | ||
| } | ||
| } | ||
| function appendChild(parent, child) { | ||
| if (child instanceof Node) { | ||
| parent.appendChild(child); | ||
| return; | ||
| } | ||
| if (typeof child === "string") { | ||
| parent.appendChild(new Text(child)); | ||
| return; | ||
| } | ||
| if (child == null || child === false || child === true) { | ||
| return; | ||
| } | ||
| if (typeof child === "number") { | ||
| parent.appendChild(new Text(String(child))); | ||
| return; | ||
| } | ||
| if (typeof child === "function") { | ||
| if (parent instanceof Element) { | ||
| parent[DYNAMIC_CHILD_SYM] = true; | ||
| } | ||
| let currentNode = null; | ||
| let currentFragChildren = null; | ||
| let warnedArray = false; | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const clearCurrent = () => { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| if (currentNode && currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } | ||
| currentNode = null; | ||
| }; | ||
| internalEffect(() => { | ||
| const v = child(); | ||
| let resolved = v; | ||
| if (Array.isArray(v)) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const item of v) { | ||
| if (item instanceof Node) frag.appendChild(item); | ||
| else if (Array.isArray(item)) { | ||
| if (DEBUG) console.warn("[forma] Nested arrays in function children are not supported. Flatten the array or use createList()."); | ||
| } else if (item != null && item !== false && item !== true) { | ||
| frag.appendChild(new Text(String(item))); | ||
| } | ||
| } | ||
| resolved = frag.childNodes.length > 0 ? frag : null; | ||
| if (DEBUG && !warnedArray) { | ||
| warnedArray = true; | ||
| console.warn("[forma] Function child returned an array \u2014 auto-wrapped in DocumentFragment. Consider using createList() or wrapping in a container element for better performance."); | ||
| } | ||
| } | ||
| if (resolved instanceof Node) { | ||
| clearCurrent(); | ||
| const isNewFrag = resolved instanceof DocumentFragment; | ||
| if (isNewFrag) { | ||
| currentFragChildren = Array.from(resolved.childNodes); | ||
| } | ||
| parent.appendChild(resolved); | ||
| currentNode = isNewFrag ? null : resolved; | ||
| } else if (resolved == null || resolved === false || resolved === true) { | ||
| clearCurrent(); | ||
| } else { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| const text = typeof resolved === "symbol" ? String(resolved) : String(resolved ?? ""); | ||
| if (!currentNode) { | ||
| currentNode = new Text(text); | ||
| parent.appendChild(currentNode); | ||
| } else if (currentNode.nodeType === 3) { | ||
| currentNode.data = text; | ||
| } else { | ||
| const tn = new Text(text); | ||
| parent.replaceChild(tn, currentNode); | ||
| currentNode = tn; | ||
| } | ||
| } | ||
| }); | ||
| return; | ||
| } | ||
| if (Array.isArray(child)) { | ||
| for (const item of child) { | ||
| appendChild(parent, item); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| function h(tag, props, ...children) { | ||
| if (typeof tag === "function" && tag !== Fragment) { | ||
| const mergedProps = { ...props ?? {}, children }; | ||
| return tag(mergedProps); | ||
| } | ||
| if (tag === Fragment) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| const tagName = tag; | ||
| if (hydrating) { | ||
| return { type: "element", tag: tagName, props: props ?? null, children }; | ||
| } | ||
| let el; | ||
| if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) { | ||
| el = ELEMENT_PROTOS[tagName].cloneNode(false); | ||
| } else if (SVG_TAGS.has(tagName)) { | ||
| el = document.createElementNS(SVG_NS, tagName); | ||
| } else { | ||
| el = getProto(tagName).cloneNode(false); | ||
| } | ||
| if (props) { | ||
| let hasDynamic = false; | ||
| for (const key in props) { | ||
| if (key === "ref") continue; | ||
| const value = props[key]; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| continue; | ||
| } | ||
| if (typeof value === "function") { | ||
| if (!hasDynamic) { | ||
| el[CACHE_SYM] = /* @__PURE__ */ Object.create(null); | ||
| hasDynamic = true; | ||
| } | ||
| applyProp(el, key, value); | ||
| continue; | ||
| } | ||
| applyStaticProp(el, key, value); | ||
| } | ||
| } | ||
| const childLen = children.length; | ||
| if (childLen === 1) { | ||
| const only = children[0]; | ||
| if (typeof only === "string") { | ||
| el.textContent = only; | ||
| } else if (typeof only === "number") { | ||
| el.textContent = String(only); | ||
| } else { | ||
| appendChild(el, only); | ||
| } | ||
| } else if (childLen > 1) { | ||
| for (const child of children) { | ||
| appendChild(el, child); | ||
| } | ||
| } | ||
| if (props && typeof props["ref"] === "function") { | ||
| props["ref"](el); | ||
| } | ||
| return el; | ||
| } | ||
| function fragment(...children) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| // src/dom/show.ts | ||
| function createShow(when, thenFn, elseFn) { | ||
| if (hydrating) { | ||
| const branch = when() ? thenFn() : elseFn?.() ?? null; | ||
| return { | ||
| type: "show", | ||
| condition: when, | ||
| whenTrue: thenFn, | ||
| whenFalse: elseFn, | ||
| initialBranch: branch | ||
| }; | ||
| } | ||
| const startMarker = document.createComment("forma-show"); | ||
| const endMarker = document.createComment("/forma-show"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let currentNode = null; | ||
| let lastTruthy = null; | ||
| let currentDispose = null; | ||
| const showDispose = internalEffect(() => { | ||
| const truthy = !!when(); | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const DEBUG_LABEL = DEBUG ? thenFn.toString().slice(0, 60) : ""; | ||
| if (truthy === lastTruthy) { | ||
| if (DEBUG) console.log("[forma:show] skip (same)", truthy, DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show]", lastTruthy, "\u2192", truthy, DEBUG_LABEL); | ||
| lastTruthy = truthy; | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| if (DEBUG) console.warn("[forma:show] parentNode is null! skipping.", DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show] parent:", parent.nodeName, "inDoc:", document.contains(parent)); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| if (currentNode) { | ||
| if (currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } else { | ||
| while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) { | ||
| parent.removeChild(startMarker.nextSibling); | ||
| } | ||
| } | ||
| } | ||
| const branchFn = truthy ? thenFn : elseFn; | ||
| if (branchFn) { | ||
| let branchDispose; | ||
| currentNode = createRoot((dispose) => { | ||
| branchDispose = dispose; | ||
| return untrack(() => branchFn()); | ||
| }); | ||
| currentDispose = branchDispose; | ||
| } else { | ||
| currentNode = null; | ||
| } | ||
| if (currentNode) { | ||
| parent.insertBefore(currentNode, endMarker); | ||
| } | ||
| }); | ||
| fragment2.__showDispose = () => { | ||
| showDispose(); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| }; | ||
| return fragment2; | ||
| } | ||
| // src/dom/hydrate.ts | ||
| var ABORT_SYM2 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var hydrating = false; | ||
| function setHydrating(value) { | ||
| hydrating = value; | ||
| } | ||
| function isDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "element"; | ||
| } | ||
| function isShowDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "show"; | ||
| } | ||
| function isListDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "list"; | ||
| } | ||
| function applyDynamicProps(el, props) { | ||
| if (!props) return; | ||
| for (const key in props) { | ||
| const value = props[key]; | ||
| if (typeof value !== "function") continue; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| let ac = el[ABORT_SYM2]; | ||
| if (!ac) { | ||
| ac = new AbortController(); | ||
| el[ABORT_SYM2] = ac; | ||
| } | ||
| el.addEventListener(key.slice(2).toLowerCase(), value, { signal: ac.signal }); | ||
| continue; | ||
| } | ||
| const fn = value; | ||
| const attrKey = key; | ||
| internalEffect(() => { | ||
| const v = fn(); | ||
| if (v === false || v == null) { | ||
| el.removeAttribute(attrKey); | ||
| } else if (v === true) { | ||
| el.setAttribute(attrKey, ""); | ||
| } else { | ||
| el.setAttribute(attrKey, String(v)); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function ensureNode(value) { | ||
| if (value instanceof Node) return value; | ||
| if (value == null || value === false || value === true) return null; | ||
| if (typeof value === "string") return new Text(value); | ||
| if (typeof value === "number") return new Text(String(value)); | ||
| if (isDescriptor(value)) return descriptorToElement(value); | ||
| if (isShowDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createShow( | ||
| value.condition, | ||
| () => ensureNode(value.whenTrue()) ?? document.createComment("empty"), | ||
| value.whenFalse ? () => ensureNode(value.whenFalse()) ?? document.createComment("empty") : void 0 | ||
| ); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| if (isListDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createList(value.items, value.keyFn, value.renderFn, value.options); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function descriptorToElement(desc) { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const children = desc.children.map((child) => { | ||
| if (isDescriptor(child)) return descriptorToElement(child); | ||
| if (isShowDescriptor(child)) return ensureNode(child); | ||
| if (isListDescriptor(child)) return ensureNode(child); | ||
| return child; | ||
| }); | ||
| return h(desc.tag, desc.props, ...children); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| function isIslandStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 105; | ||
| } | ||
| function isShowStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 115; | ||
| } | ||
| function isTextStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 116; | ||
| } | ||
| function isListStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 108; | ||
| } | ||
| function findClosingMarker(start) { | ||
| const closing = "/" + start.data; | ||
| let node = start.nextSibling; | ||
| while (node) { | ||
| if (node.nodeType === 8 && node.data === closing) { | ||
| return node; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function findTextBetween(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 3) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function nextElementBetweenMarkers(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return void 0; | ||
| } | ||
| function extractContentBetweenMarkers(start, end) { | ||
| const frag = document.createDocumentFragment(); | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| const next = node.nextSibling; | ||
| frag.appendChild(node); | ||
| node = next; | ||
| } | ||
| return frag; | ||
| } | ||
| function setupShowEffect(desc, marker) { | ||
| let currentCondition = !!desc.condition(); | ||
| let thenFragment = null; | ||
| let elseFragment = null; | ||
| const hasSSRContent = marker.start.nextSibling !== marker.end; | ||
| if (!hasSSRContent && currentCondition) { | ||
| if (__DEV__) console.warn("[forma] Hydration: show condition mismatch \u2014 SSR empty but client condition is true"); | ||
| const trueBranch = desc.whenTrue(); | ||
| if (trueBranch instanceof Node) { | ||
| marker.start.parentNode.insertBefore(trueBranch, marker.end); | ||
| } | ||
| } | ||
| internalEffect(() => { | ||
| const next = !!desc.condition(); | ||
| if (next === currentCondition) return; | ||
| currentCondition = next; | ||
| const parent = marker.start.parentNode; | ||
| if (!parent) return; | ||
| const current = extractContentBetweenMarkers(marker.start, marker.end); | ||
| if (!next) { | ||
| thenFragment = current; | ||
| } else { | ||
| elseFragment = current; | ||
| } | ||
| let branch = next ? thenFragment ?? desc.whenTrue() : desc.whenFalse ? elseFragment ?? desc.whenFalse() : null; | ||
| if (next && thenFragment) thenFragment = null; | ||
| if (!next && elseFragment) elseFragment = null; | ||
| if (branch != null && !(branch instanceof Node)) { | ||
| branch = ensureNode(branch); | ||
| } | ||
| if (branch instanceof Node) { | ||
| parent.insertBefore(branch, marker.end); | ||
| } | ||
| }); | ||
| } | ||
| function adoptBranchContent(desc, regionStart, regionEnd) { | ||
| if (isDescriptor(desc)) { | ||
| const el = nextElementBetweenMarkers(regionStart, regionEnd); | ||
| if (el) adoptNode(desc, el); | ||
| } else if (isShowDescriptor(desc)) { | ||
| let node = regionStart.nextSibling; | ||
| while (node && node !== regionEnd) { | ||
| if (node.nodeType === 8 && isShowStart(node.data)) { | ||
| const innerStart = node; | ||
| const innerEnd = findClosingMarker(innerStart); | ||
| if (innerEnd) { | ||
| if (desc.initialBranch) { | ||
| adoptBranchContent(desc.initialBranch, innerStart, innerEnd); | ||
| } | ||
| setupShowEffect(desc, { start: innerStart, end: innerEnd}); | ||
| } | ||
| break; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| function adoptNode(desc, ssrEl) { | ||
| if (!ssrEl || ssrEl.tagName !== desc.tag.toUpperCase()) { | ||
| if (__DEV__) console.warn(`Hydration mismatch: expected <${desc.tag}>, got <${ssrEl?.tagName?.toLowerCase() ?? "nothing"}>`); | ||
| const fresh = descriptorToElement(desc); | ||
| if (ssrEl) ssrEl.replaceWith(fresh); | ||
| return; | ||
| } | ||
| applyDynamicProps(ssrEl, desc.props); | ||
| let cursor = ssrEl.firstChild; | ||
| for (const child of desc.children) { | ||
| if (child === false || child == null) continue; | ||
| if (isDescriptor(child)) { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| while (cursor && cursor.nodeType === 1 && cursor.hasAttribute("data-forma-island")) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (!cursor) { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| continue; | ||
| } | ||
| if (cursor.nodeType === 1) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(child, el); | ||
| } else if (cursor.nodeType === 8 && isIslandStart(cursor.data)) { | ||
| const end = findClosingMarker(cursor); | ||
| const fresh = descriptorToElement(child); | ||
| if (end) { | ||
| end.parentNode.insertBefore(fresh, end); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| ssrEl.appendChild(fresh); | ||
| cursor = null; | ||
| } | ||
| } else { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| } | ||
| } else if (isShowDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isShowStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| if (child.initialBranch) { | ||
| adoptBranchContent(child.initialBranch, start, end); | ||
| } | ||
| setupShowEffect(child, { start, end}); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (isListDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isListStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| const ssrKeyMap = /* @__PURE__ */ new Map(); | ||
| const ssrElements = []; | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) { | ||
| const el = node; | ||
| ssrElements.push(el); | ||
| const key = el.getAttribute("data-forma-key"); | ||
| if (key != null) { | ||
| ssrKeyMap.set(key, el); | ||
| } | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| const currentItems = untrack(() => child.items()); | ||
| const listKeyFn = child.keyFn; | ||
| const listRenderFn = child.renderFn; | ||
| const useIndexFallback = ssrKeyMap.size === 0 && ssrElements.length > 0; | ||
| const adoptedNodes = []; | ||
| const adoptedItems = []; | ||
| const usedIndices = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < currentItems.length; i++) { | ||
| const item = currentItems[i]; | ||
| const key = listKeyFn(item); | ||
| let ssrNode; | ||
| if (useIndexFallback) { | ||
| if (i < ssrElements.length) { | ||
| ssrNode = ssrElements[i]; | ||
| usedIndices.add(i); | ||
| } | ||
| } else { | ||
| ssrNode = ssrKeyMap.get(String(key)); | ||
| if (ssrNode) ssrKeyMap.delete(String(key)); | ||
| } | ||
| if (ssrNode) { | ||
| adoptedNodes.push(ssrNode); | ||
| adoptedItems.push(item); | ||
| } else { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: list item key "${key}" not found in SSR \u2014 rendering fresh`); | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const [getIndex] = createSignal(i); | ||
| const fresh = listRenderFn(item, getIndex); | ||
| end.parentNode.insertBefore(fresh, end); | ||
| adoptedNodes.push(fresh); | ||
| adoptedItems.push(item); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| } | ||
| if (useIndexFallback) { | ||
| for (let i = 0; i < ssrElements.length; i++) { | ||
| if (!usedIndices.has(i) && ssrElements[i].parentNode) { | ||
| ssrElements[i].parentNode.removeChild(ssrElements[i]); | ||
| } | ||
| } | ||
| } else { | ||
| for (const [unusedKey, unusedNode] of ssrKeyMap) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: removing extra SSR list item with key "${unusedKey}"`); | ||
| if (unusedNode.parentNode) { | ||
| unusedNode.parentNode.removeChild(unusedNode); | ||
| } | ||
| } | ||
| } | ||
| const parent = start.parentNode; | ||
| for (const adoptedNode of adoptedNodes) { | ||
| parent.insertBefore(adoptedNode, end); | ||
| } | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < adoptedItems.length; i++) { | ||
| const item = adoptedItems[i]; | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = createSignal(i); | ||
| cache.set(key, { | ||
| element: adoptedNodes[i], | ||
| item, | ||
| getIndex, | ||
| setIndex | ||
| }); | ||
| } | ||
| let reconcileNodes = adoptedNodes.slice(); | ||
| let reconcileItems = adoptedItems.slice(); | ||
| internalEffect(() => { | ||
| const newItems = child.items(); | ||
| const parent2 = start.parentNode; | ||
| if (!parent2) return; | ||
| const result = reconcileList( | ||
| parent2, | ||
| reconcileItems, | ||
| newItems, | ||
| reconcileNodes, | ||
| listKeyFn, | ||
| (item) => { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = createSignal(0); | ||
| const element = untrack(() => listRenderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| }, | ||
| (_node, item) => { | ||
| const key = listKeyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }, | ||
| end | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| const key = listKeyFn(newItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| reconcileNodes = result.nodes; | ||
| reconcileItems = result.items; | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (typeof child === "function") { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor && cursor.nodeType === 1) { | ||
| const initial = child(); | ||
| if (isDescriptor(initial)) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(initial, el); | ||
| continue; | ||
| } | ||
| } | ||
| if (cursor && cursor.nodeType === 8) { | ||
| const data = cursor.data; | ||
| if (isTextStart(data)) { | ||
| const endMarker = findClosingMarker(cursor); | ||
| let textNode = cursor.nextSibling; | ||
| if (!textNode || textNode.nodeType !== 3) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node for marker ${data} \u2014 SSR walker should emit content between markers`); | ||
| const created = document.createTextNode(""); | ||
| cursor.parentNode.insertBefore(created, endMarker || cursor.nextSibling); | ||
| textNode = created; | ||
| } | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = endMarker ? endMarker.nextSibling : textNode.nextSibling; | ||
| } else if (isShowStart(data)) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| let textNode = findTextBetween(start, end); | ||
| if (!textNode) { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node for show marker ${start.data} \u2014 SSR walker should emit content between markers`); | ||
| textNode = document.createTextNode(""); | ||
| start.parentNode.insertBefore(textNode, end); | ||
| } | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else if (cursor && cursor.nodeType === 3) { | ||
| const textNode = cursor; | ||
| cursor = cursor.nextSibling; | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } else { | ||
| if (__DEV__) console.warn(`[FormaJS] Hydration: created text node in empty <${ssrEl.tagName.toLowerCase()}> \u2014 IR may not cover this component`); | ||
| const textNode = document.createTextNode(""); | ||
| ssrEl.appendChild(textNode); | ||
| internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } | ||
| } else if (typeof child === "string" || typeof child === "number") { | ||
| if (cursor && cursor.nodeType === 3) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function hydrateIsland(component, target) { | ||
| const hasSSRContent = target.childElementCount > 0 || target.childNodes.length > 0 && Array.from(target.childNodes).some((n) => n.nodeType === 1 || n.nodeType === 3 && n.data.trim()); | ||
| if (!hasSSRContent) { | ||
| if (__DEV__) { | ||
| const name = target.getAttribute("data-forma-component") || "unknown"; | ||
| console.warn( | ||
| `[forma] Island "${name}" has no SSR content \u2014 falling back to CSR. This means the IR walker did not render content between ISLAND_START and ISLAND_END.` | ||
| ); | ||
| } | ||
| const result = component(); | ||
| if (result instanceof Element) { | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (attr.name.startsWith("data-forma-")) { | ||
| result.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| target.replaceWith(result); | ||
| return result; | ||
| } else if (result instanceof Node) { | ||
| target.appendChild(result); | ||
| } | ||
| return target; | ||
| } | ||
| setHydrating(true); | ||
| let descriptor; | ||
| try { | ||
| descriptor = component(); | ||
| } finally { | ||
| setHydrating(false); | ||
| } | ||
| if (!descriptor || !isDescriptor(descriptor)) { | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| if (target.hasAttribute("data-forma-island")) { | ||
| adoptNode(descriptor, target); | ||
| } else { | ||
| adoptNode(descriptor, target.children[0]); | ||
| } | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| // src/dom/list.ts | ||
| function longestIncreasingSubsequence(arr) { | ||
| const n = arr.length; | ||
| if (n === 0) return []; | ||
| const tails = new Int32Array(n); | ||
| const tailIndices = new Int32Array(n); | ||
| const predecessor = new Int32Array(n).fill(-1); | ||
| let tailsLen = 0; | ||
| for (let i = 0; i < n; i++) { | ||
| const val = arr[i]; | ||
| let lo = 0, hi = tailsLen; | ||
| while (lo < hi) { | ||
| const mid = lo + hi >> 1; | ||
| if (tails[mid] < val) lo = mid + 1; | ||
| else hi = mid; | ||
| } | ||
| tails[lo] = val; | ||
| tailIndices[lo] = i; | ||
| if (lo > 0) predecessor[i] = tailIndices[lo - 1]; | ||
| if (lo >= tailsLen) tailsLen++; | ||
| } | ||
| const result = new Array(tailsLen); | ||
| let idx = tailIndices[tailsLen - 1]; | ||
| for (let i = tailsLen - 1; i >= 0; i--) { | ||
| result[i] = idx; | ||
| idx = predecessor[idx]; | ||
| } | ||
| return result; | ||
| } | ||
| var SMALL_LIST_THRESHOLD = 32; | ||
| var ABORT_SYM3 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var CACHE_SYM2 = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM2 = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function canPatchStaticElement(target, source) { | ||
| return target instanceof HTMLElement && source instanceof HTMLElement && target.tagName === source.tagName && !target[ABORT_SYM3] && !target[CACHE_SYM2] && !target[DYNAMIC_CHILD_SYM2] && !source[ABORT_SYM3] && !source[CACHE_SYM2] && !source[DYNAMIC_CHILD_SYM2]; | ||
| } | ||
| function patchStaticElement(target, source) { | ||
| const sourceAttrNames = /* @__PURE__ */ new Set(); | ||
| for (const attr of Array.from(source.attributes)) { | ||
| sourceAttrNames.add(attr.name); | ||
| if (target.getAttribute(attr.name) !== attr.value) { | ||
| target.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (!sourceAttrNames.has(attr.name)) { | ||
| target.removeAttribute(attr.name); | ||
| } | ||
| } | ||
| target.replaceChildren(...Array.from(source.childNodes)); | ||
| } | ||
| function reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| const oldKeys = new Array(oldLen); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeys[i] = keyFn(oldItems[i]); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| let found = -1; | ||
| for (let j = 0; j < oldLen; j++) { | ||
| if (!oldUsed[j] && oldKeys[j] === key) { | ||
| found = j; | ||
| oldUsed[j] = 1; | ||
| break; | ||
| } | ||
| } | ||
| oldIndices[i] = found; | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function reconcileList(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| if (newLen === 0) { | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| return { nodes: [], items: [] }; | ||
| } | ||
| if (oldLen === 0) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = createFn(newItems[i]); | ||
| if (beforeNode) { | ||
| parent.insertBefore(node, beforeNode); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| hooks?.onInsert?.(node); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| if (oldLen < SMALL_LIST_THRESHOLD) { | ||
| return reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks); | ||
| } | ||
| const oldKeyMap = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeyMap.set(keyFn(oldItems[i]), i); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| const oldIdx = oldKeyMap.get(key); | ||
| if (oldIdx !== void 0) { | ||
| oldIndices[i] = oldIdx; | ||
| oldUsed[oldIdx] = 1; | ||
| } else { | ||
| oldIndices[i] = -1; | ||
| } | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function createList(items, keyFn, renderFn, options) { | ||
| if (hydrating) { | ||
| return { type: "list", items, keyFn, renderFn, options }; | ||
| } | ||
| const startMarker = document.createComment("forma-list-start"); | ||
| const endMarker = document.createComment("forma-list-end"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| let currentNodes = []; | ||
| let currentItems = []; | ||
| const updateOnItemChange = options?.updateOnItemChange ?? "none"; | ||
| internalEffect(() => { | ||
| const newItems = items(); | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(newItems)) { | ||
| if (__DEV__) { | ||
| console.warn("[forma] createList: value is not an array, treating as empty"); | ||
| } | ||
| for (const node of currentNodes) { | ||
| if (node.parentNode === parent) parent.removeChild(node); | ||
| } | ||
| cache = /* @__PURE__ */ new Map(); | ||
| currentNodes = []; | ||
| currentItems = []; | ||
| return; | ||
| } | ||
| let cleanItems = newItems; | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| if (newItems[i] == null) { | ||
| cleanItems = newItems.filter((item) => item != null); | ||
| break; | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const item of cleanItems) { | ||
| const key = keyFn(item); | ||
| if (seen.has(key)) { | ||
| console.warn("[forma] createList: duplicate key detected:", key); | ||
| } | ||
| seen.add(key); | ||
| } | ||
| } | ||
| const updateRow = updateOnItemChange === "rerender" ? (node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (!cached) return; | ||
| if (cached.item === item) return; | ||
| cached.item = item; | ||
| if (!(node instanceof HTMLElement)) return; | ||
| if (node[ABORT_SYM3] || node[CACHE_SYM2] || node[DYNAMIC_CHILD_SYM2]) { | ||
| return; | ||
| } | ||
| const next = untrack(() => renderFn(item, cached.getIndex)); | ||
| if (canPatchStaticElement(node, next)) { | ||
| patchStaticElement(node, next); | ||
| cached.element = node; | ||
| } | ||
| } : (_node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }; | ||
| const result = reconcileList( | ||
| parent, | ||
| currentItems, | ||
| cleanItems, | ||
| currentNodes, | ||
| keyFn, | ||
| // createFn: create element + cache entry | ||
| (item) => { | ||
| const key = keyFn(item); | ||
| const [getIndex, setIndex] = createSignal(0); | ||
| const element = untrack(() => renderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| }, | ||
| updateRow, | ||
| // beforeNode: insert items before the end marker | ||
| endMarker | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < cleanItems.length; i++) { | ||
| const key = keyFn(cleanItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| currentNodes = result.nodes; | ||
| currentItems = result.items; | ||
| }); | ||
| return fragment2; | ||
| } | ||
| export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList }; | ||
| //# sourceMappingURL=chunk-FJGTMWKY.js.map | ||
| //# sourceMappingURL=chunk-FJGTMWKY.js.map |
Sorry, the diff of this file is too big to display
| import { createComputed, createSignal } from './chunk-OZCHIVAZ.js'; | ||
| import { effectScope, effect, startBatch, endBatch, setActiveSub } from 'alien-signals'; | ||
| export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals'; | ||
| var currentRoot = null; | ||
| var rootStack = []; | ||
| function createRoot(fn) { | ||
| const scope = { disposers: [], scopeDispose: null }; | ||
| rootStack.push(currentRoot); | ||
| currentRoot = scope; | ||
| const dispose = () => { | ||
| if (scope.scopeDispose) { | ||
| try { | ||
| scope.scopeDispose(); | ||
| } catch { | ||
| } | ||
| scope.scopeDispose = null; | ||
| } | ||
| for (const d of scope.disposers) { | ||
| try { | ||
| d(); | ||
| } catch { | ||
| } | ||
| } | ||
| scope.disposers.length = 0; | ||
| }; | ||
| let result; | ||
| try { | ||
| scope.scopeDispose = effectScope(() => { | ||
| result = fn(dispose); | ||
| }); | ||
| } finally { | ||
| currentRoot = rootStack.pop() ?? null; | ||
| } | ||
| return result; | ||
| } | ||
| function registerDisposer(dispose) { | ||
| if (currentRoot) { | ||
| currentRoot.disposers.push(dispose); | ||
| } | ||
| } | ||
| function hasActiveRoot() { | ||
| return currentRoot !== null; | ||
| } | ||
| // src/reactive/cleanup.ts | ||
| var currentCleanupCollector = null; | ||
| function onCleanup(fn) { | ||
| currentCleanupCollector?.(fn); | ||
| } | ||
| function setCleanupCollector(collector) { | ||
| const prev = currentCleanupCollector; | ||
| currentCleanupCollector = collector; | ||
| return prev; | ||
| } | ||
| // src/reactive/dev.ts | ||
| var __DEV__ = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true; | ||
| var _errorHandler = null; | ||
| function onError(handler) { | ||
| _errorHandler = handler; | ||
| } | ||
| function reportError(error, source) { | ||
| if (_errorHandler) { | ||
| try { | ||
| _errorHandler(error, source ? { source } : {}); | ||
| } catch { | ||
| } | ||
| } | ||
| if (__DEV__) { | ||
| console.error(`[forma] ${source ?? "Unknown"} error:`, error); | ||
| } | ||
| } | ||
| var POOL_SIZE = 32; | ||
| var MAX_REENTRANT_RUNS = 100; | ||
| var pool = []; | ||
| for (let i = 0; i < POOL_SIZE; i++) pool.push([]); | ||
| var poolIdx = POOL_SIZE; | ||
| function acquireArray() { | ||
| if (poolIdx > 0) { | ||
| const arr = pool[--poolIdx]; | ||
| arr.length = 0; | ||
| return arr; | ||
| } | ||
| return []; | ||
| } | ||
| function releaseArray(arr) { | ||
| arr.length = 0; | ||
| if (poolIdx < POOL_SIZE) { | ||
| pool[poolIdx++] = arr; | ||
| } | ||
| } | ||
| function runCleanup(fn) { | ||
| if (fn === void 0) return; | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| function runCleanups(bag) { | ||
| if (bag === void 0) return; | ||
| for (let i = 0; i < bag.length; i++) { | ||
| try { | ||
| bag[i](); | ||
| } catch (e) { | ||
| reportError(e, "effect cleanup"); | ||
| } | ||
| } | ||
| } | ||
| function internalEffect(fn) { | ||
| const dispose = effect(fn); | ||
| if (hasActiveRoot()) { | ||
| registerDisposer(dispose); | ||
| } | ||
| return dispose; | ||
| } | ||
| function createEffect(fn) { | ||
| const shouldRegister = hasActiveRoot(); | ||
| let cleanup; | ||
| let cleanupBag; | ||
| let nextCleanup; | ||
| let nextCleanupBag; | ||
| const addCleanup = (cb) => { | ||
| if (nextCleanupBag !== void 0) { | ||
| nextCleanupBag.push(cb); | ||
| return; | ||
| } | ||
| if (nextCleanup !== void 0) { | ||
| const bag = acquireArray(); | ||
| bag.push(nextCleanup, cb); | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = bag; | ||
| return; | ||
| } | ||
| nextCleanup = cb; | ||
| }; | ||
| let skipCleanupInfra = false; | ||
| let firstRun = true; | ||
| let running = false; | ||
| let rerunRequested = false; | ||
| const runOnce = () => { | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| if (skipCleanupInfra) { | ||
| try { | ||
| fn(); | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| } | ||
| return; | ||
| } | ||
| nextCleanup = void 0; | ||
| nextCleanupBag = void 0; | ||
| const prevCollector = setCleanupCollector(addCleanup); | ||
| try { | ||
| const result = fn(); | ||
| if (typeof result === "function") { | ||
| addCleanup(result); | ||
| } | ||
| if (nextCleanup === void 0 && nextCleanupBag === void 0) { | ||
| if (firstRun) skipCleanupInfra = true; | ||
| return; | ||
| } | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } catch (e) { | ||
| reportError(e, "effect"); | ||
| if (nextCleanupBag !== void 0) { | ||
| cleanupBag = nextCleanupBag; | ||
| } else { | ||
| cleanup = nextCleanup; | ||
| } | ||
| } finally { | ||
| setCleanupCollector(prevCollector); | ||
| firstRun = false; | ||
| } | ||
| }; | ||
| const safeFn = () => { | ||
| if (running) { | ||
| rerunRequested = true; | ||
| return; | ||
| } | ||
| running = true; | ||
| try { | ||
| let reentrantRuns = 0; | ||
| do { | ||
| rerunRequested = false; | ||
| runOnce(); | ||
| if (rerunRequested) { | ||
| reentrantRuns++; | ||
| if (reentrantRuns >= MAX_REENTRANT_RUNS) { | ||
| reportError( | ||
| new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`), | ||
| "effect" | ||
| ); | ||
| rerunRequested = false; | ||
| } | ||
| } | ||
| } while (rerunRequested); | ||
| } finally { | ||
| running = false; | ||
| } | ||
| }; | ||
| const dispose = effect(safeFn); | ||
| let disposed = false; | ||
| const wrappedDispose = () => { | ||
| if (disposed) return; | ||
| disposed = true; | ||
| dispose(); | ||
| if (cleanup !== void 0) { | ||
| runCleanup(cleanup); | ||
| cleanup = void 0; | ||
| } | ||
| if (cleanupBag !== void 0) { | ||
| runCleanups(cleanupBag); | ||
| releaseArray(cleanupBag); | ||
| cleanupBag = void 0; | ||
| } | ||
| }; | ||
| if (shouldRegister) { | ||
| registerDisposer(wrappedDispose); | ||
| } | ||
| return wrappedDispose; | ||
| } | ||
| // src/reactive/memo.ts | ||
| var createMemo = createComputed; | ||
| function batch(fn) { | ||
| startBatch(); | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| endBatch(); | ||
| } | ||
| } | ||
| function untrack(fn) { | ||
| const prev = setActiveSub(void 0); | ||
| try { | ||
| return fn(); | ||
| } finally { | ||
| setActiveSub(prev); | ||
| } | ||
| } | ||
| // src/reactive/on.ts | ||
| function on(deps, fn, options) { | ||
| let prev; | ||
| let isFirst = true; | ||
| return () => { | ||
| const value2 = deps(); | ||
| if (options?.defer && isFirst) { | ||
| isFirst = false; | ||
| prev = value2; | ||
| return void 0; | ||
| } | ||
| const result = untrack(() => fn(value2, prev)); | ||
| prev = value2; | ||
| return result; | ||
| }; | ||
| } | ||
| // src/reactive/ref.ts | ||
| function createRef(initialValue) { | ||
| return { current: initialValue }; | ||
| } | ||
| // src/reactive/reducer.ts | ||
| function createReducer(reducer, initialState) { | ||
| const [state, setState] = createSignal(initialState); | ||
| const dispatch = (action) => { | ||
| setState((prev) => reducer(prev, action)); | ||
| }; | ||
| return [state, dispatch]; | ||
| } | ||
| // src/reactive/suspense-context.ts | ||
| var currentSuspenseContext = null; | ||
| var suspenseStack = []; | ||
| function pushSuspenseContext(ctx) { | ||
| suspenseStack.push(currentSuspenseContext); | ||
| currentSuspenseContext = ctx; | ||
| } | ||
| function popSuspenseContext() { | ||
| currentSuspenseContext = suspenseStack.pop() ?? null; | ||
| } | ||
| function getSuspenseContext() { | ||
| return currentSuspenseContext; | ||
| } | ||
| // src/reactive/resource.ts | ||
| function createResource(source, fetcher, options) { | ||
| const [data, setData] = createSignal(options?.initialValue); | ||
| const [loading, setLoading] = createSignal(false); | ||
| const [error, setError] = createSignal(void 0); | ||
| const suspenseCtx = getSuspenseContext(); | ||
| let abortController = null; | ||
| let fetchVersion = 0; | ||
| const doFetch = () => { | ||
| const sourceValue = untrack(source); | ||
| if (abortController) { | ||
| abortController.abort(); | ||
| } | ||
| const controller = new AbortController(); | ||
| abortController = controller; | ||
| const version = ++fetchVersion; | ||
| const isLatest = () => version === fetchVersion; | ||
| let suspensePending = false; | ||
| if (suspenseCtx) { | ||
| suspenseCtx.increment(); | ||
| suspensePending = true; | ||
| } | ||
| setLoading(true); | ||
| setError(void 0); | ||
| Promise.resolve(fetcher(sourceValue)).then((result) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| setData(() => result); | ||
| } | ||
| }).catch((err) => { | ||
| if (isLatest() && !controller.signal.aborted) { | ||
| if (err?.name !== "AbortError") { | ||
| setError(err); | ||
| } | ||
| } | ||
| }).finally(() => { | ||
| if (suspensePending) suspenseCtx?.decrement(); | ||
| if (isLatest()) { | ||
| setLoading(false); | ||
| if (abortController === controller) { | ||
| abortController = null; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| internalEffect(() => { | ||
| source(); | ||
| doFetch(); | ||
| }); | ||
| const resource = (() => data()); | ||
| resource.loading = loading; | ||
| resource.error = error; | ||
| resource.refetch = doFetch; | ||
| resource.mutate = (value2) => setData(() => value2); | ||
| return resource; | ||
| } | ||
| export { __DEV__, batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, internalEffect, on, onCleanup, onError, popSuspenseContext, pushSuspenseContext, reportError, untrack }; | ||
| //# sourceMappingURL=chunk-OUVOAYIO.js.map | ||
| //# sourceMappingURL=chunk-OUVOAYIO.js.map |
| {"version":3,"sources":["../src/reactive/root.ts","../src/reactive/cleanup.ts","../src/reactive/dev.ts","../src/reactive/effect.ts","../src/reactive/memo.ts","../src/reactive/batch.ts","../src/reactive/untrack.ts","../src/reactive/on.ts","../src/reactive/ref.ts","../src/reactive/reducer.ts","../src/reactive/suspense-context.ts","../src/reactive/resource.ts"],"names":["rawEffectScope","rawEffect","value"],"mappings":"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","file":"chunk-OUVOAYIO.js","sourcesContent":["/**\n * Forma Reactive - Root\n *\n * Explicit reactive ownership scope. All effects created inside a root\n * are automatically disposed when the root is torn down.\n *\n * Uses alien-signals' `effectScope` under the hood for native graph-level\n * effect tracking, with a userland disposer list for non-effect cleanup\n * (e.g., event listeners, DOM references, timers).\n */\n\nimport { effectScope as rawEffectScope } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Root scope tracking\n// ---------------------------------------------------------------------------\n\nlet currentRoot: RootScope | null = null;\nconst rootStack: (RootScope | null)[] = [];\n\ninterface RootScope {\n /** Userland disposers (event listeners, DOM refs, timers, etc.) */\n disposers: (() => void)[];\n /** alien-signals effect scope dispose — tears down all reactive effects */\n scopeDispose: (() => void) | null;\n}\n\n/**\n * Create a reactive root scope.\n *\n * All effects created (via `createEffect`) inside the callback are tracked\n * at both the reactive graph level (via alien-signals effectScope) and the\n * userland level (via registerDisposer). The returned `dispose` function\n * tears down everything.\n *\n * ```ts\n * const dispose = createRoot(() => {\n * createEffect(() => console.log(count()));\n * createEffect(() => console.log(name()));\n * });\n * // later: dispose() stops both effects\n * ```\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const scope: RootScope = { disposers: [], scopeDispose: null };\n\n rootStack.push(currentRoot);\n currentRoot = scope;\n\n const dispose = () => {\n // Dispose alien-signals effect scope first (reactive graph cleanup)\n if (scope.scopeDispose) {\n try { scope.scopeDispose(); } catch { /* ensure userland disposers still run */ }\n scope.scopeDispose = null;\n }\n // Then run userland disposers\n for (const d of scope.disposers) {\n try { d(); } catch { /* ensure all disposers run */ }\n }\n scope.disposers.length = 0;\n };\n\n let result: T;\n try {\n // Wrap in alien-signals effectScope for native effect tracking\n scope.scopeDispose = rawEffectScope(() => {\n result = fn(dispose);\n });\n } finally {\n currentRoot = rootStack.pop() ?? null;\n }\n\n return result!;\n}\n\n/**\n * @internal — called by createEffect to register disposers in the current root.\n */\nexport function registerDisposer(dispose: () => void): void {\n if (currentRoot) {\n currentRoot.disposers.push(dispose);\n }\n}\n\n/**\n * @internal — check if we're inside a root scope.\n */\nexport function hasActiveRoot(): boolean {\n return currentRoot !== null;\n}\n","/**\n * Forma Reactive - Cleanup\n *\n * Register cleanup functions within reactive scopes.\n * Inspired by SolidJS onCleanup().\n */\n\n// ---------------------------------------------------------------------------\n// Cleanup context tracking\n// ---------------------------------------------------------------------------\n\ntype CleanupCollector = ((fn: () => void) => void) | null;\n\nlet currentCleanupCollector: CleanupCollector = null;\n\n/**\n * Register a cleanup function in the current reactive scope.\n * The cleanup runs before the effect re-executes and on disposal.\n *\n * More composable than returning a cleanup from the effect function,\n * since it can be called from helper functions.\n *\n * ```ts\n * createEffect(() => {\n * const timer = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timer));\n * });\n * ```\n */\nexport function onCleanup(fn: () => void): void {\n currentCleanupCollector?.(fn);\n}\n\n/**\n * @internal — Set the cleanup collector for the current effect execution.\n */\nexport function setCleanupCollector(collector: CleanupCollector): CleanupCollector {\n const prev = currentCleanupCollector;\n currentCleanupCollector = collector;\n return prev;\n}\n","/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by bundler (tsup define). Defaults to true for unbundled usage.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\nexport const __DEV__: boolean = typeof process !== 'undefined'\n ? (process!.env?.NODE_ENV !== 'production')\n : true;\n\n// ---------------------------------------------------------------------------\n// Global error handler\n// ---------------------------------------------------------------------------\n\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nlet _errorHandler: ErrorHandler | null = null;\n\n/**\n * Install a global error handler for FormaJS reactive errors.\n * Called when effects, computeds, or event handlers throw.\n *\n * ```ts\n * onError((err, info) => {\n * console.error(`[${info?.source}]`, err);\n * Sentry.captureException(err);\n * });\n * ```\n */\nexport function onError(handler: ErrorHandler): void {\n _errorHandler = handler;\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n if (_errorHandler) {\n try { _errorHandler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (__DEV__) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Effect\n *\n * Side-effectful reactive computation that auto-tracks signal dependencies.\n * Backed by alien-signals for automatic dependency tracking.\n *\n * TC39 Signals equivalent: Signal.subtle.Watcher (effect is a userland concept)\n */\n\nimport { effect as rawEffect } from 'alien-signals';\nimport { hasActiveRoot, registerDisposer } from './root.js';\nimport { setCleanupCollector } from './cleanup.js';\nimport { reportError } from './dev.js';\n\n// ---------------------------------------------------------------------------\n// Cleanup array pool — avoids allocating a new array every effect re-run\n// ---------------------------------------------------------------------------\n\nconst POOL_SIZE = 32;\nconst MAX_REENTRANT_RUNS = 100;\nconst pool: (() => void)[][] = [];\nfor (let i = 0; i < POOL_SIZE; i++) pool.push([]);\nlet poolIdx = POOL_SIZE;\n\nfunction acquireArray(): (() => void)[] {\n if (poolIdx > 0) {\n const arr = pool[--poolIdx]!;\n arr.length = 0;\n return arr;\n }\n return [];\n}\n\nfunction releaseArray(arr: (() => void)[]): void {\n arr.length = 0;\n if (poolIdx < POOL_SIZE) {\n pool[poolIdx++] = arr;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified cleanup runner — single function for both re-run and dispose paths\n// ---------------------------------------------------------------------------\n\nfunction runCleanup(fn: (() => void) | undefined): void {\n if (fn === undefined) return;\n try {\n fn();\n } catch (e) {\n reportError(e, 'effect cleanup');\n }\n}\n\nfunction runCleanups(bag: (() => void)[] | undefined): void {\n if (bag === undefined) return;\n for (let i = 0; i < bag.length; i++) {\n try { bag[i]!(); } catch (e) { reportError(e, 'effect cleanup'); }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive effect that auto-tracks signal dependencies.\n *\n * The provided function runs immediately and re-runs whenever any signal it\n * reads changes. If the function returns a cleanup function, that cleanup is\n * called before each re-run and on disposal.\n *\n * Additionally, `onCleanup()` can be called inside the effect to register\n * cleanup functions composably.\n *\n * Returns a dispose function that stops the effect.\n */\n/**\n * @internal — Lightweight effect for Forma's internal DOM bindings.\n *\n * Bypasses createEffect's cleanup infrastructure (pool, collector, error\n * reporting, engine compression) since internal effects never use\n * onCleanup() or return cleanup functions. This saves ~4 function calls\n * per effect creation and per re-run.\n *\n * ONLY use for effects that:\n * 1. Never call onCleanup()\n * 2. Never return a cleanup function\n * 3. Contain simple \"read signal → write DOM\" logic\n */\nexport function internalEffect(fn: () => void): () => void {\n const dispose = rawEffect(fn);\n if (hasActiveRoot()) {\n registerDisposer(dispose);\n }\n return dispose;\n}\n\nexport function createEffect(fn: () => void | (() => void)): () => void {\n const shouldRegister = hasActiveRoot();\n\n // Most effects have zero or one cleanup. Track the single-cleanup case\n // without array allocation; only promote to pooled array when needed.\n let cleanup: (() => void) | undefined;\n let cleanupBag: (() => void)[] | undefined;\n let nextCleanup: (() => void) | undefined;\n let nextCleanupBag: (() => void)[] | undefined;\n\n const addCleanup = (cb: () => void) => {\n if (nextCleanupBag !== undefined) {\n nextCleanupBag.push(cb);\n return;\n }\n if (nextCleanup !== undefined) {\n const bag = acquireArray();\n bag.push(nextCleanup, cb);\n nextCleanup = undefined;\n nextCleanupBag = bag;\n return;\n }\n nextCleanup = cb;\n };\n\n // \"Engine Compression\" exploit: most effects never use cleanup (onCleanup()\n // or return value). After the first clean run, we know this effect is\n // \"cleanup-free\" and can skip the entire cleanup infrastructure on re-runs.\n // This saves 4 function calls per re-run: acquireArray, setCleanupCollector,\n // releaseArray, and bag inspection. Like the engine compression ratio exploit —\n // the \"rules\" say we should always prepare for cleanup, but we measure at\n // \"room temperature\" (first run) and exploit the gap.\n let skipCleanupInfra = false;\n let firstRun = true;\n let running = false;\n let rerunRequested = false;\n\n const runOnce = () => {\n // Run and clear previous cleanups (only if any exist).\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n\n // Ultra-fast path: this effect has proven it doesn't use cleanup.\n // Skip acquireArray + setCleanupCollector + bag checks + releaseArray.\n if (skipCleanupInfra) {\n try { fn(); } catch (e) { reportError(e, 'effect'); }\n return;\n }\n\n nextCleanup = undefined;\n nextCleanupBag = undefined;\n\n // Full path: install collector for onCleanup() calls.\n const prevCollector = setCleanupCollector(addCleanup);\n\n try {\n const result = fn();\n\n if (typeof result === 'function') {\n addCleanup(result as () => void);\n }\n\n // Hot path: no cleanups registered or returned — enable ultra-fast path.\n if (nextCleanup === undefined && nextCleanupBag === undefined) {\n // First clean run → enable ultra-fast path for all subsequent runs\n if (firstRun) skipCleanupInfra = true;\n return;\n }\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } catch (e) {\n reportError(e, 'effect');\n\n if (nextCleanupBag !== undefined) {\n cleanupBag = nextCleanupBag;\n } else {\n cleanup = nextCleanup;\n }\n } finally {\n setCleanupCollector(prevCollector);\n firstRun = false;\n }\n };\n\n const safeFn = () => {\n if (running) {\n rerunRequested = true;\n return;\n }\n\n running = true;\n try {\n let reentrantRuns = 0;\n do {\n rerunRequested = false;\n runOnce();\n if (rerunRequested) {\n reentrantRuns++;\n if (reentrantRuns >= MAX_REENTRANT_RUNS) {\n reportError(\n new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`),\n 'effect',\n );\n rerunRequested = false;\n }\n }\n } while (rerunRequested);\n } finally {\n running = false;\n }\n };\n\n const dispose = rawEffect(safeFn);\n\n // Wrap dispose to also run final cleanups\n let disposed = false;\n const wrappedDispose = () => {\n if (disposed) return;\n disposed = true;\n dispose();\n if (cleanup !== undefined) {\n runCleanup(cleanup);\n cleanup = undefined;\n }\n if (cleanupBag !== undefined) {\n runCleanups(cleanupBag);\n releaseArray(cleanupBag);\n cleanupBag = undefined;\n }\n };\n\n // Register in current root scope (if any)\n if (shouldRegister) {\n registerDisposer(wrappedDispose);\n }\n\n return wrappedDispose;\n}\n","/**\n * Forma Reactive - Memo\n *\n * Alias for createComputed with SolidJS/React-familiar naming.\n * A memoized derived value that only recomputes when its dependencies change.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { createComputed } from './computed.js';\n\n/**\n * Create a memoized computed value.\n * Identical to `createComputed` — provided for React/SolidJS familiarity.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), both createComputed and createMemo in FormaJS are lazy\n * cached derivations — equivalent to SolidJS's createMemo. They are\n * identical.\n *\n * The computation runs lazily and caches the result. It only recomputes\n * when a signal it reads during computation changes.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createMemo(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n */\nexport const createMemo: typeof createComputed = createComputed;\n","/**\n * Forma Reactive - Batch\n *\n * Groups multiple signal updates and defers effect execution until the\n * outermost batch completes. Prevents intermediate re-renders.\n * Backed by alien-signals.\n *\n * TC39 Signals: no built-in batch — this is a userland optimization.\n */\n\nimport { startBatch, endBatch } from 'alien-signals';\n\n/**\n * Group multiple signal updates so that effects only run once after all\n * updates have been applied.\n *\n * ```ts\n * batch(() => {\n * setA(1);\n * setB(2);\n * // effects that depend on A and B won't run yet\n * });\n * // effects run here, once\n * ```\n *\n * Batches are nestable — only the outermost batch triggers the flush.\n */\nexport function batch(fn: () => void): void {\n startBatch();\n try {\n fn();\n } finally {\n endBatch();\n }\n}\n","/**\n * Forma Reactive - Untrack\n *\n * Read signals without subscribing to them in the reactive graph.\n * Essential for reading values inside effects without creating dependencies.\n *\n * TC39 Signals equivalent: Signal.subtle.untrack()\n */\n\nimport { setActiveSub } from 'alien-signals';\n\n/**\n * Execute a function without tracking signal reads.\n * Any signals read inside `fn` will NOT become dependencies of the\n * surrounding effect or computed.\n *\n * ```ts\n * createEffect(() => {\n * const a = count(); // tracked — effect re-runs when count changes\n * const b = untrack(() => other()); // NOT tracked — effect ignores other changes\n * });\n * ```\n */\nexport function untrack<T>(fn: () => T): T {\n const prev = setActiveSub(undefined);\n try {\n return fn();\n } finally {\n setActiveSub(prev);\n }\n}\n","/**\n * Forma Reactive - On\n *\n * Explicit dependency tracking for effects.\n * Only re-runs when the specified signals change, ignoring all other reads.\n *\n * SolidJS equivalent: on()\n * Vue equivalent: watch() with explicit deps\n * React equivalent: useEffect dependency array\n */\n\nimport { untrack } from './untrack.js';\n\n/**\n * Create a tracked effect body that only fires when specific dependencies change.\n *\n * Wraps a function so that only the `deps` signals are tracked.\n * All signal reads inside `fn` are untracked (won't cause re-runs).\n *\n * Use with `createEffect`:\n *\n * ```ts\n * const [a, setA] = createSignal(1);\n * const [b, setB] = createSignal(2);\n *\n * // Only re-runs when `a` changes, NOT when `b` changes:\n * createEffect(on(a, (value, prev) => {\n * console.log(`a changed: ${prev} → ${value}, b is ${b()}`);\n * }));\n *\n * setA(10); // fires: \"a changed: 1 → 10, b is 2\"\n * setB(20); // does NOT fire\n * ```\n *\n * Multiple dependencies:\n * ```ts\n * createEffect(on(\n * () => [a(), b()] as const,\n * ([aVal, bVal], prev) => { ... }\n * ));\n * ```\n */\nexport function on<T, U>(\n deps: () => T,\n fn: (value: T, prev: T | undefined) => U,\n options?: { defer?: boolean },\n): () => U | undefined {\n let prev: T | undefined;\n let isFirst = true;\n\n return () => {\n // Track only the deps\n const value = deps();\n\n if (options?.defer && isFirst) {\n isFirst = false;\n prev = value;\n return undefined;\n }\n\n // Run the body untracked so it doesn't add extra dependencies\n const result = untrack(() => fn(value, prev));\n prev = value;\n return result;\n };\n}\n","/**\n * Forma Reactive - Ref\n *\n * Mutable container that does NOT trigger reactivity.\n * Use for DOM references, previous values, instance variables —\n * anything that needs to persist across effect re-runs without\n * causing re-execution.\n *\n * React equivalent: useRef\n * SolidJS equivalent: (none — uses plain variables in setup)\n */\n\nexport interface Ref<T> {\n current: T;\n}\n\n/**\n * Create a mutable ref container.\n *\n * Unlike signals, writing to `.current` does NOT trigger effects.\n * Use when you need a stable reference across reactive scopes.\n *\n * ```ts\n * const timerRef = createRef<number | null>(null);\n *\n * createEffect(() => {\n * timerRef.current = setInterval(tick, 1000);\n * onCleanup(() => clearInterval(timerRef.current!));\n * });\n *\n * // DOM ref pattern:\n * const elRef = createRef<HTMLElement | null>(null);\n * h('div', { ref: (el) => { elRef.current = el; } });\n * ```\n */\nexport function createRef<T>(initialValue: T): Ref<T> {\n return { current: initialValue };\n}\n","/**\n * Forma Reactive - Reducer\n *\n * State machine pattern — dispatch actions to a pure reducer function.\n * Fine-grained: only the resulting state signal is reactive.\n *\n * React equivalent: useReducer\n * SolidJS equivalent: (none — uses createSignal + helpers)\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\n\nexport type Dispatch<A> = (action: A) => void;\n\n/**\n * Create a reducer — predictable state updates via dispatched actions.\n *\n * The reducer function must be pure: `(state, action) => newState`.\n * Returns a [state, dispatch] tuple.\n *\n * ```ts\n * type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };\n *\n * const [count, dispatch] = createReducer(\n * (state: number, action: Action) => {\n * switch (action.type) {\n * case 'increment': return state + 1;\n * case 'decrement': return state - 1;\n * case 'reset': return 0;\n * }\n * },\n * 0,\n * );\n *\n * dispatch({ type: 'increment' }); // count() === 1\n * dispatch({ type: 'increment' }); // count() === 2\n * dispatch({ type: 'reset' }); // count() === 0\n * ```\n */\nexport function createReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initialState: S,\n): [state: SignalGetter<S>, dispatch: Dispatch<A>] {\n const [state, setState] = createSignal(initialState);\n\n const dispatch: Dispatch<A> = (action) => {\n setState((prev) => reducer(prev, action));\n };\n\n return [state, dispatch];\n}\n","/**\n * Forma Reactive - Suspense Context\n *\n * Shared context stack for Suspense boundaries. Lives in the reactive layer\n * (not DOM) so that createResource can import it without circular dependencies.\n *\n * The pattern mirrors the lifecycle context stack in component/define.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SuspenseContext {\n /** Increment when a resource starts loading inside this boundary. */\n increment(): void;\n /** Decrement when a resource resolves/rejects inside this boundary. */\n decrement(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Context stack\n// ---------------------------------------------------------------------------\n\nlet currentSuspenseContext: SuspenseContext | null = null;\nconst suspenseStack: (SuspenseContext | null)[] = [];\n\nexport function pushSuspenseContext(ctx: SuspenseContext): void {\n suspenseStack.push(currentSuspenseContext);\n currentSuspenseContext = ctx;\n}\n\nexport function popSuspenseContext(): void {\n currentSuspenseContext = suspenseStack.pop() ?? null;\n}\n\n/** Get the current Suspense context (if any). Called by createResource. */\nexport function getSuspenseContext(): SuspenseContext | null {\n return currentSuspenseContext;\n}\n","/**\n * Forma Reactive - Resource\n *\n * Async data fetching primitive with reactive loading/error state.\n * Tracks a source signal and refetches when it changes.\n *\n * SolidJS equivalent: createResource\n * React equivalent: use() + Suspense (React 19), or useSWR/react-query\n */\n\nimport { createSignal, type SignalGetter } from './signal.js';\nimport { internalEffect } from './effect.js';\nimport { untrack } from './untrack.js';\nimport { getSuspenseContext } from './suspense-context.js';\n\nexport interface Resource<T> {\n /** The resolved data (or undefined while loading). */\n (): T | undefined;\n /** True while the fetcher is running. */\n loading: SignalGetter<boolean>;\n /** The error if the fetcher rejected (or undefined). */\n error: SignalGetter<unknown>;\n /** Manually refetch with the current source value. */\n refetch: () => void;\n /** Manually set the data (overrides fetcher result). */\n mutate: (value: T | undefined) => void;\n}\n\nexport interface ResourceOptions<T> {\n /** Initial value before first fetch resolves. */\n initialValue?: T;\n}\n\n/**\n * Create an async resource that fetches data reactively.\n *\n * When `source` changes, the fetcher re-runs automatically.\n * Provides reactive `loading` and `error` signals.\n *\n * ```ts\n * const [userId, setUserId] = createSignal(1);\n *\n * const user = createResource(\n * userId, // source signal\n * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher\n * );\n *\n * internalEffect(() => {\n * if (user.loading()) console.log('Loading...');\n * else if (user.error()) console.log('Error:', user.error());\n * else console.log('User:', user());\n * });\n *\n * setUserId(2); // automatically refetches\n * ```\n *\n * Without a source signal (static fetch):\n * ```ts\n * const posts = createResource(\n * () => true, // constant source — fetches once\n * () => fetch('/api/posts').then(r => r.json()),\n * );\n * ```\n */\nexport function createResource<T, S = true>(\n source: SignalGetter<S>,\n fetcher: (source: S) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> {\n const [data, setData] = createSignal<T | undefined>(options?.initialValue);\n const [loading, setLoading] = createSignal(false);\n const [error, setError] = createSignal<unknown>(undefined);\n\n // Capture the Suspense context at creation time (not at fetch time).\n // This is critical because the Suspense boundary pushes/pops its context\n // synchronously during children() execution.\n const suspenseCtx = getSuspenseContext();\n\n let abortController: AbortController | null = null;\n let fetchVersion = 0;\n\n const doFetch = () => {\n // Read source outside tracking to get current value\n const sourceValue = untrack(source);\n\n // Abort previous in-flight request\n if (abortController) {\n abortController.abort();\n }\n const controller = new AbortController();\n abortController = controller;\n\n const version = ++fetchVersion;\n const isLatest = () => version === fetchVersion;\n let suspensePending = false;\n\n // Notify Suspense boundary that a fetch has started\n if (suspenseCtx) {\n suspenseCtx.increment();\n suspensePending = true;\n }\n\n setLoading(true);\n setError(undefined);\n\n Promise.resolve(fetcher(sourceValue))\n .then((result) => {\n // Only apply if this is still the latest fetch and wasn't aborted.\n if (isLatest() && !controller.signal.aborted) {\n setData(() => result);\n }\n })\n .catch((err) => {\n if (isLatest() && !controller.signal.aborted) {\n // Ignore abort errors\n if (err?.name !== 'AbortError') {\n setError(err);\n }\n }\n })\n .finally(() => {\n if (suspensePending) suspenseCtx?.decrement();\n if (isLatest()) {\n setLoading(false);\n if (abortController === controller) {\n abortController = null; // Release controller for GC\n }\n }\n });\n };\n\n // Auto-fetch when source changes\n internalEffect(() => {\n source(); // track the source signal\n doFetch();\n });\n\n // Build the resource object\n const resource = (() => data()) as Resource<T>;\n resource.loading = loading;\n resource.error = error;\n resource.refetch = doFetch;\n resource.mutate = (value) => setData(() => value);\n\n return resource;\n}\n"]} |
| import { signal, computed, setActiveSub } from 'alien-signals'; | ||
| // src/reactive/signal.ts | ||
| function applySignalSet(s, v, equals) { | ||
| if (typeof v !== "function") { | ||
| if (equals) { | ||
| const prevSub2 = setActiveSub(void 0); | ||
| const prev2 = s(); | ||
| setActiveSub(prevSub2); | ||
| if (equals(prev2, v)) return; | ||
| } | ||
| s(v); | ||
| return; | ||
| } | ||
| const prevSub = setActiveSub(void 0); | ||
| const prev = s(); | ||
| setActiveSub(prevSub); | ||
| const next = v(prev); | ||
| if (equals && equals(prev, next)) return; | ||
| s(next); | ||
| } | ||
| function createSignal(initialValue, options) { | ||
| const s = signal(initialValue); | ||
| const getter = s; | ||
| const eq = options?.equals; | ||
| const setter = (v) => applySignalSet(s, v, eq); | ||
| return [getter, setter]; | ||
| } | ||
| function createComputed(fn) { | ||
| return computed(fn); | ||
| } | ||
| export { createComputed, createSignal }; | ||
| //# sourceMappingURL=chunk-OZCHIVAZ.js.map | ||
| //# sourceMappingURL=chunk-OZCHIVAZ.js.map |
| {"version":3,"sources":["../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","prev","createRawSignal","rawComputed"],"mappings":";;;AAkEA,SAAS,cAAA,CACP,CAAA,EACA,CAAA,EACA,MAAA,EACM;AACN,EAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC3B,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,MAAMA,QAAAA,GAAU,aAAa,MAAS,CAAA;AACtC,MAAA,MAAMC,QAAO,CAAA,EAAE;AACf,MAAA,YAAA,CAAaD,QAAO,CAAA;AACpB,MAAA,IAAI,MAAA,CAAOC,KAAAA,EAAM,CAAC,CAAA,EAAG;AAAA,IACvB;AACA,IAAA,CAAA,CAAE,CAAC,CAAA;AACH,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,OAAA,GAAU,aAAa,MAAS,CAAA;AACtC,EAAA,MAAM,OAAO,CAAA,EAAE;AACf,EAAA,YAAA,CAAa,OAAO,CAAA;AACpB,EAAA,MAAM,IAAA,GAAQ,EAAqB,IAAI,CAAA;AACvC,EAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAM,IAAI,CAAA,EAAG;AAClC,EAAA,CAAA,CAAE,IAAI,CAAA;AACR;AAqBO,SAAS,YAAA,CAAgB,cAAiB,OAAA,EAA0E;AACzH,EAAA,MAAM,CAAA,GAAIC,OAAmB,YAAY,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,CAAA;AACf,EAAA,MAAM,KAAK,OAAA,EAAS,MAAA;AACpB,EAAA,MAAM,SAA0B,CAAC,CAAA,KAA4B,cAAA,CAAe,CAAA,EAAG,GAAG,EAAE,CAAA;AAEpF,EAAA,OAAO,CAAC,QAAQ,MAAM,CAAA;AACxB;AC7EO,SAAS,eAAkB,EAAA,EAAuC;AACvE,EAAA,OAAOC,SAAY,EAAE,CAAA;AACvB","file":"chunk-OZCHIVAZ.js","sourcesContent":["/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub } from 'alien-signals';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport type SignalGetter<T> = () => T;\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n if (typeof v !== 'function') {\n if (equals) {\n // Read current value without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // skip — values are equal\n }\n s(v);\n return;\n }\n\n // Functional update: read prev without tracking\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // skip — values are equal\n s(next);\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n return rawComputed(fn);\n}\n"]} |
| 'use strict'; | ||
| var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs'); | ||
| var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs'); | ||
| // src/dom/element.ts | ||
| var Fragment = /* @__PURE__ */ Symbol.for("forma.fragment"); | ||
| var SVG_NS = "http://www.w3.org/2000/svg"; | ||
| var XLINK_NS = "http://www.w3.org/1999/xlink"; | ||
| var SVG_TAGS = /* @__PURE__ */ new Set([ | ||
| "svg", | ||
| "path", | ||
| "circle", | ||
| "rect", | ||
| "line", | ||
| "polyline", | ||
| "polygon", | ||
| "ellipse", | ||
| "g", | ||
| "text", | ||
| "tspan", | ||
| "textPath", | ||
| "defs", | ||
| "use", | ||
| "symbol", | ||
| "clipPath", | ||
| "mask", | ||
| "pattern", | ||
| "marker", | ||
| "linearGradient", | ||
| "radialGradient", | ||
| "stop", | ||
| "filter", | ||
| "feGaussianBlur", | ||
| "feColorMatrix", | ||
| "feOffset", | ||
| "feBlend", | ||
| "feMerge", | ||
| "feMergeNode", | ||
| "feComposite", | ||
| "feFlood", | ||
| "feMorphology", | ||
| "feTurbulence", | ||
| "feDisplacementMap", | ||
| "feImage", | ||
| "foreignObject", | ||
| "animate", | ||
| "animateTransform", | ||
| "animateMotion", | ||
| "set", | ||
| "image", | ||
| "switch", | ||
| "desc", | ||
| "title", | ||
| "metadata" | ||
| ]); | ||
| var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([ | ||
| "disabled", | ||
| "checked", | ||
| "readonly", | ||
| "required", | ||
| "autofocus", | ||
| "autoplay", | ||
| "controls", | ||
| "default", | ||
| "defer", | ||
| "formnovalidate", | ||
| "hidden", | ||
| "ismap", | ||
| "loop", | ||
| "multiple", | ||
| "muted", | ||
| "nomodule", | ||
| "novalidate", | ||
| "open", | ||
| "playsinline", | ||
| "reversed", | ||
| "selected", | ||
| "async" | ||
| ]); | ||
| var ELEMENT_PROTOS = null; | ||
| function getProto(tag) { | ||
| if (!ELEMENT_PROTOS) { | ||
| ELEMENT_PROTOS = /* @__PURE__ */ Object.create(null); | ||
| for (const t of [ | ||
| "div", | ||
| "span", | ||
| "p", | ||
| "a", | ||
| "li", | ||
| "ul", | ||
| "ol", | ||
| "button", | ||
| "input", | ||
| "label", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "section", | ||
| "header", | ||
| "footer", | ||
| "main", | ||
| "nav", | ||
| "table", | ||
| "tr", | ||
| "td", | ||
| "th", | ||
| "tbody", | ||
| "img", | ||
| "form", | ||
| "select", | ||
| "option", | ||
| "textarea", | ||
| "i", | ||
| "b", | ||
| "strong", | ||
| "em", | ||
| "small", | ||
| "article", | ||
| "aside", | ||
| "details", | ||
| "summary" | ||
| ]) { | ||
| ELEMENT_PROTOS[t] = document.createElement(t); | ||
| } | ||
| } | ||
| return ELEMENT_PROTOS[tag] ?? (ELEMENT_PROTOS[tag] = document.createElement(tag)); | ||
| } | ||
| var EVENT_NAMES = /* @__PURE__ */ Object.create(null); | ||
| function eventName(key) { | ||
| return EVENT_NAMES[key] ?? (EVENT_NAMES[key] = key.slice(2).toLowerCase()); | ||
| } | ||
| var ABORT_SYM = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| function getAbortController(el) { | ||
| let controller = el[ABORT_SYM]; | ||
| if (!controller) { | ||
| controller = new AbortController(); | ||
| el[ABORT_SYM] = controller; | ||
| } | ||
| return controller; | ||
| } | ||
| function cleanup(el) { | ||
| const controller = el[ABORT_SYM]; | ||
| if (controller) { | ||
| controller.abort(); | ||
| delete el[ABORT_SYM]; | ||
| } | ||
| } | ||
| var CACHE_SYM = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function getCache(el) { | ||
| return el[CACHE_SYM] ?? (el[CACHE_SYM] = /* @__PURE__ */ Object.create(null)); | ||
| } | ||
| function handleClass(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache["class"] === v) return; | ||
| cache["class"] = v; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = v; | ||
| } else { | ||
| el.setAttribute("class", v); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache["class"] === value) return; | ||
| cache["class"] = value; | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| } | ||
| } | ||
| function handleStyle(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| let prevKeys = []; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (typeof v === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === v) return; | ||
| cache["style"] = v; | ||
| prevKeys = []; | ||
| el.style.cssText = v; | ||
| } else if (v && typeof v === "object") { | ||
| const style = el.style; | ||
| const nextKeys = Object.keys(v); | ||
| for (const k of prevKeys) { | ||
| if (!(k in v)) { | ||
| style.removeProperty(k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())); | ||
| } | ||
| } | ||
| Object.assign(style, v); | ||
| prevKeys = nextKeys; | ||
| } | ||
| }); | ||
| } else if (typeof value === "string") { | ||
| const cache = getCache(el); | ||
| if (cache["style"] === value) return; | ||
| cache["style"] = value; | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| } | ||
| function handleEvent(el, key, value) { | ||
| const controller = getAbortController(el); | ||
| el.addEventListener( | ||
| eventName(key), | ||
| value, | ||
| { signal: controller.signal } | ||
| ); | ||
| } | ||
| function handleInnerHTML(el, _key, value) { | ||
| if (typeof value === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const resolved = value(); | ||
| if (resolved == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof resolved !== "object" || !("__html" in resolved)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof resolved | ||
| ); | ||
| } | ||
| const html = resolved.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| const cache = getCache(el); | ||
| if (cache["innerHTML"] === html) return; | ||
| cache["innerHTML"] = html; | ||
| el.innerHTML = html; | ||
| }); | ||
| } else { | ||
| if (value == null) { | ||
| el.innerHTML = ""; | ||
| return; | ||
| } | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| } | ||
| } | ||
| function handleXLink(el, key, value) { | ||
| const localName = key.slice(6); | ||
| if (typeof value === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(v)); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| el.removeAttributeNS(XLINK_NS, localName); | ||
| } else { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| } | ||
| } | ||
| } | ||
| function handleBooleanAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| const cache = getCache(el); | ||
| if (cache[key] === v) return; | ||
| cache[key] = v; | ||
| if (v) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| }); | ||
| } else { | ||
| const cache = getCache(el); | ||
| if (cache[key] === value) return; | ||
| cache[key] = value; | ||
| if (value) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.removeAttribute(key); | ||
| } | ||
| } | ||
| } | ||
| function handleGenericAttr(el, key, value) { | ||
| if (typeof value === "function") { | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = value(); | ||
| if (v == null || v === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(v); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| }); | ||
| } else { | ||
| if (value == null || value === false) { | ||
| const cache = getCache(el); | ||
| if (cache[key] === null) return; | ||
| cache[key] = null; | ||
| el.removeAttribute(key); | ||
| } else { | ||
| const strVal = String(value); | ||
| const cache = getCache(el); | ||
| if (cache[key] === strVal) return; | ||
| cache[key] = strVal; | ||
| el.setAttribute(key, strVal); | ||
| } | ||
| } | ||
| } | ||
| var PROP_HANDLERS = /* @__PURE__ */ new Map(); | ||
| PROP_HANDLERS.set("class", handleClass); | ||
| PROP_HANDLERS.set("className", handleClass); | ||
| PROP_HANDLERS.set("style", handleStyle); | ||
| PROP_HANDLERS.set("ref", () => { | ||
| }); | ||
| PROP_HANDLERS.set("dangerouslySetInnerHTML", handleInnerHTML); | ||
| for (const attr of BOOLEAN_ATTRS) { | ||
| PROP_HANDLERS.set(attr, handleBooleanAttr); | ||
| } | ||
| function applyProp(el, key, value) { | ||
| if (key === "class") { | ||
| handleClass(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| return; | ||
| } | ||
| const handler = PROP_HANDLERS.get(key); | ||
| if (handler) { | ||
| handler(el, key, value); | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| handleXLink(el, key, value); | ||
| return; | ||
| } | ||
| handleGenericAttr(el, key, value); | ||
| } | ||
| function applyStaticProp(el, key, value) { | ||
| if (value == null || value === false) return; | ||
| if (key === "class" || key === "className") { | ||
| if (el instanceof HTMLElement) { | ||
| el.className = value; | ||
| } else { | ||
| el.setAttribute("class", value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "style") { | ||
| if (typeof value === "string") { | ||
| el.style.cssText = value; | ||
| } else if (value && typeof value === "object") { | ||
| Object.assign(el.style, value); | ||
| } | ||
| return; | ||
| } | ||
| if (key === "dangerouslySetInnerHTML") { | ||
| if (typeof value !== "object" || !("__html" in value)) { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: expected { __html: string }, got " + typeof value | ||
| ); | ||
| } | ||
| const html = value.__html; | ||
| if (typeof html !== "string") { | ||
| throw new TypeError( | ||
| "dangerouslySetInnerHTML: __html must be a string, got " + typeof html | ||
| ); | ||
| } | ||
| el.innerHTML = html; | ||
| return; | ||
| } | ||
| if (key.charCodeAt(0) === 120 && key.startsWith("xlink:")) { | ||
| el.setAttributeNS(XLINK_NS, key, String(value)); | ||
| return; | ||
| } | ||
| if (BOOLEAN_ATTRS.has(key)) { | ||
| if (value) el.setAttribute(key, ""); | ||
| return; | ||
| } | ||
| if (value === true) { | ||
| el.setAttribute(key, ""); | ||
| } else { | ||
| el.setAttribute(key, String(value)); | ||
| } | ||
| } | ||
| function appendChild(parent, child) { | ||
| if (child instanceof Node) { | ||
| parent.appendChild(child); | ||
| return; | ||
| } | ||
| if (typeof child === "string") { | ||
| parent.appendChild(new Text(child)); | ||
| return; | ||
| } | ||
| if (child == null || child === false || child === true) { | ||
| return; | ||
| } | ||
| if (typeof child === "number") { | ||
| parent.appendChild(new Text(String(child))); | ||
| return; | ||
| } | ||
| if (typeof child === "function") { | ||
| if (parent instanceof Element) { | ||
| parent[DYNAMIC_CHILD_SYM] = true; | ||
| } | ||
| let currentNode = null; | ||
| let currentFragChildren = null; | ||
| let warnedArray = false; | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const clearCurrent = () => { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| if (currentNode && currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } | ||
| currentNode = null; | ||
| }; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = child(); | ||
| let resolved = v; | ||
| if (Array.isArray(v)) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const item of v) { | ||
| if (item instanceof Node) frag.appendChild(item); | ||
| else if (Array.isArray(item)) { | ||
| if (DEBUG) console.warn("[forma] Nested arrays in function children are not supported. Flatten the array or use createList()."); | ||
| } else if (item != null && item !== false && item !== true) { | ||
| frag.appendChild(new Text(String(item))); | ||
| } | ||
| } | ||
| resolved = frag.childNodes.length > 0 ? frag : null; | ||
| if (DEBUG && !warnedArray) { | ||
| warnedArray = true; | ||
| console.warn("[forma] Function child returned an array \u2014 auto-wrapped in DocumentFragment. Consider using createList() or wrapping in a container element for better performance."); | ||
| } | ||
| } | ||
| if (resolved instanceof Node) { | ||
| clearCurrent(); | ||
| const isNewFrag = resolved instanceof DocumentFragment; | ||
| if (isNewFrag) { | ||
| currentFragChildren = Array.from(resolved.childNodes); | ||
| } | ||
| parent.appendChild(resolved); | ||
| currentNode = isNewFrag ? null : resolved; | ||
| } else if (resolved == null || resolved === false || resolved === true) { | ||
| clearCurrent(); | ||
| } else { | ||
| if (currentFragChildren) { | ||
| for (const c of currentFragChildren) { | ||
| if (c.parentNode === parent) parent.removeChild(c); | ||
| } | ||
| currentFragChildren = null; | ||
| } | ||
| const text = typeof resolved === "symbol" ? String(resolved) : String(resolved ?? ""); | ||
| if (!currentNode) { | ||
| currentNode = new Text(text); | ||
| parent.appendChild(currentNode); | ||
| } else if (currentNode.nodeType === 3) { | ||
| currentNode.data = text; | ||
| } else { | ||
| const tn = new Text(text); | ||
| parent.replaceChild(tn, currentNode); | ||
| currentNode = tn; | ||
| } | ||
| } | ||
| }); | ||
| return; | ||
| } | ||
| if (Array.isArray(child)) { | ||
| for (const item of child) { | ||
| appendChild(parent, item); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| function h(tag, props, ...children) { | ||
| if (typeof tag === "function" && tag !== Fragment) { | ||
| const mergedProps = { ...props ?? {}, children }; | ||
| return tag(mergedProps); | ||
| } | ||
| if (tag === Fragment) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| const tagName = tag; | ||
| if (hydrating) { | ||
| return { type: "element", tag: tagName, props: props ?? null, children }; | ||
| } | ||
| let el; | ||
| if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) { | ||
| el = ELEMENT_PROTOS[tagName].cloneNode(false); | ||
| } else if (SVG_TAGS.has(tagName)) { | ||
| el = document.createElementNS(SVG_NS, tagName); | ||
| } else { | ||
| el = getProto(tagName).cloneNode(false); | ||
| } | ||
| if (props) { | ||
| let hasDynamic = false; | ||
| for (const key in props) { | ||
| if (key === "ref") continue; | ||
| const value = props[key]; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| handleEvent(el, key, value); | ||
| continue; | ||
| } | ||
| if (typeof value === "function") { | ||
| if (!hasDynamic) { | ||
| el[CACHE_SYM] = /* @__PURE__ */ Object.create(null); | ||
| hasDynamic = true; | ||
| } | ||
| applyProp(el, key, value); | ||
| continue; | ||
| } | ||
| applyStaticProp(el, key, value); | ||
| } | ||
| } | ||
| const childLen = children.length; | ||
| if (childLen === 1) { | ||
| const only = children[0]; | ||
| if (typeof only === "string") { | ||
| el.textContent = only; | ||
| } else if (typeof only === "number") { | ||
| el.textContent = String(only); | ||
| } else { | ||
| appendChild(el, only); | ||
| } | ||
| } else if (childLen > 1) { | ||
| for (const child of children) { | ||
| appendChild(el, child); | ||
| } | ||
| } | ||
| if (props && typeof props["ref"] === "function") { | ||
| props["ref"](el); | ||
| } | ||
| return el; | ||
| } | ||
| function fragment(...children) { | ||
| const frag = document.createDocumentFragment(); | ||
| for (const child of children) { | ||
| appendChild(frag, child); | ||
| } | ||
| return frag; | ||
| } | ||
| // src/dom/show.ts | ||
| function createShow(when, thenFn, elseFn) { | ||
| if (hydrating) { | ||
| const branch = when() ? thenFn() : elseFn?.() ?? null; | ||
| return { | ||
| type: "show", | ||
| condition: when, | ||
| whenTrue: thenFn, | ||
| whenFalse: elseFn, | ||
| initialBranch: branch | ||
| }; | ||
| } | ||
| const startMarker = document.createComment("forma-show"); | ||
| const endMarker = document.createComment("/forma-show"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let currentNode = null; | ||
| let lastTruthy = null; | ||
| let currentDispose = null; | ||
| const showDispose = chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const truthy = !!when(); | ||
| const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined"; | ||
| const DEBUG_LABEL = DEBUG ? thenFn.toString().slice(0, 60) : ""; | ||
| if (truthy === lastTruthy) { | ||
| if (DEBUG) console.log("[forma:show] skip (same)", truthy, DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show]", lastTruthy, "\u2192", truthy, DEBUG_LABEL); | ||
| lastTruthy = truthy; | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| if (DEBUG) console.warn("[forma:show] parentNode is null! skipping.", DEBUG_LABEL); | ||
| return; | ||
| } | ||
| if (DEBUG) console.log("[forma:show] parent:", parent.nodeName, "inDoc:", document.contains(parent)); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| if (currentNode) { | ||
| if (currentNode.parentNode === parent) { | ||
| parent.removeChild(currentNode); | ||
| } else { | ||
| while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) { | ||
| parent.removeChild(startMarker.nextSibling); | ||
| } | ||
| } | ||
| } | ||
| const branchFn = truthy ? thenFn : elseFn; | ||
| if (branchFn) { | ||
| let branchDispose; | ||
| currentNode = chunkDCTOXHPF_cjs.createRoot((dispose) => { | ||
| branchDispose = dispose; | ||
| return chunkDCTOXHPF_cjs.untrack(() => branchFn()); | ||
| }); | ||
| currentDispose = branchDispose; | ||
| } else { | ||
| currentNode = null; | ||
| } | ||
| if (currentNode) { | ||
| parent.insertBefore(currentNode, endMarker); | ||
| } | ||
| }); | ||
| fragment2.__showDispose = () => { | ||
| showDispose(); | ||
| if (currentDispose) { | ||
| currentDispose(); | ||
| currentDispose = null; | ||
| } | ||
| }; | ||
| return fragment2; | ||
| } | ||
| // src/dom/hydrate.ts | ||
| var ABORT_SYM2 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var hydrating = false; | ||
| function setHydrating(value) { | ||
| hydrating = value; | ||
| } | ||
| function isDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "element"; | ||
| } | ||
| function isShowDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "show"; | ||
| } | ||
| function isListDescriptor(v) { | ||
| return v != null && typeof v === "object" && "type" in v && v.type === "list"; | ||
| } | ||
| function applyDynamicProps(el, props) { | ||
| if (!props) return; | ||
| for (const key in props) { | ||
| const value = props[key]; | ||
| if (typeof value !== "function") continue; | ||
| if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.length > 2) { | ||
| let ac = el[ABORT_SYM2]; | ||
| if (!ac) { | ||
| ac = new AbortController(); | ||
| el[ABORT_SYM2] = ac; | ||
| } | ||
| el.addEventListener(key.slice(2).toLowerCase(), value, { signal: ac.signal }); | ||
| continue; | ||
| } | ||
| const fn = value; | ||
| const attrKey = key; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const v = fn(); | ||
| if (v === false || v == null) { | ||
| el.removeAttribute(attrKey); | ||
| } else if (v === true) { | ||
| el.setAttribute(attrKey, ""); | ||
| } else { | ||
| el.setAttribute(attrKey, String(v)); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function ensureNode(value) { | ||
| if (value instanceof Node) return value; | ||
| if (value == null || value === false || value === true) return null; | ||
| if (typeof value === "string") return new Text(value); | ||
| if (typeof value === "number") return new Text(String(value)); | ||
| if (isDescriptor(value)) return descriptorToElement(value); | ||
| if (isShowDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createShow( | ||
| value.condition, | ||
| () => ensureNode(value.whenTrue()) ?? document.createComment("empty"), | ||
| value.whenFalse ? () => ensureNode(value.whenFalse()) ?? document.createComment("empty") : void 0 | ||
| ); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| if (isListDescriptor(value)) { | ||
| const prevH = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| return createList(value.items, value.keyFn, value.renderFn, value.options); | ||
| } finally { | ||
| hydrating = prevH; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function descriptorToElement(desc) { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const children = desc.children.map((child) => { | ||
| if (isDescriptor(child)) return descriptorToElement(child); | ||
| if (isShowDescriptor(child)) return ensureNode(child); | ||
| if (isListDescriptor(child)) return ensureNode(child); | ||
| return child; | ||
| }); | ||
| return h(desc.tag, desc.props, ...children); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| function isIslandStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 105; | ||
| } | ||
| function isShowStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 115; | ||
| } | ||
| function isTextStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 116; | ||
| } | ||
| function isListStart(data) { | ||
| return data.length >= 4 && data.charCodeAt(0) === 102 && data.charCodeAt(1) === 58 && data.charCodeAt(2) === 108; | ||
| } | ||
| function findClosingMarker(start) { | ||
| const closing = "/" + start.data; | ||
| let node = start.nextSibling; | ||
| while (node) { | ||
| if (node.nodeType === 8 && node.data === closing) { | ||
| return node; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function findTextBetween(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 3) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return null; | ||
| } | ||
| function nextElementBetweenMarkers(start, end) { | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) return node; | ||
| node = node.nextSibling; | ||
| } | ||
| return void 0; | ||
| } | ||
| function extractContentBetweenMarkers(start, end) { | ||
| const frag = document.createDocumentFragment(); | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| const next = node.nextSibling; | ||
| frag.appendChild(node); | ||
| node = next; | ||
| } | ||
| return frag; | ||
| } | ||
| function setupShowEffect(desc, marker) { | ||
| let currentCondition = !!desc.condition(); | ||
| let thenFragment = null; | ||
| let elseFragment = null; | ||
| const hasSSRContent = marker.start.nextSibling !== marker.end; | ||
| if (!hasSSRContent && currentCondition) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn("[forma] Hydration: show condition mismatch \u2014 SSR empty but client condition is true"); | ||
| const trueBranch = desc.whenTrue(); | ||
| if (trueBranch instanceof Node) { | ||
| marker.start.parentNode.insertBefore(trueBranch, marker.end); | ||
| } | ||
| } | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const next = !!desc.condition(); | ||
| if (next === currentCondition) return; | ||
| currentCondition = next; | ||
| const parent = marker.start.parentNode; | ||
| if (!parent) return; | ||
| const current = extractContentBetweenMarkers(marker.start, marker.end); | ||
| if (!next) { | ||
| thenFragment = current; | ||
| } else { | ||
| elseFragment = current; | ||
| } | ||
| let branch = next ? thenFragment ?? desc.whenTrue() : desc.whenFalse ? elseFragment ?? desc.whenFalse() : null; | ||
| if (next && thenFragment) thenFragment = null; | ||
| if (!next && elseFragment) elseFragment = null; | ||
| if (branch != null && !(branch instanceof Node)) { | ||
| branch = ensureNode(branch); | ||
| } | ||
| if (branch instanceof Node) { | ||
| parent.insertBefore(branch, marker.end); | ||
| } | ||
| }); | ||
| } | ||
| function adoptBranchContent(desc, regionStart, regionEnd) { | ||
| if (isDescriptor(desc)) { | ||
| const el = nextElementBetweenMarkers(regionStart, regionEnd); | ||
| if (el) adoptNode(desc, el); | ||
| } else if (isShowDescriptor(desc)) { | ||
| let node = regionStart.nextSibling; | ||
| while (node && node !== regionEnd) { | ||
| if (node.nodeType === 8 && isShowStart(node.data)) { | ||
| const innerStart = node; | ||
| const innerEnd = findClosingMarker(innerStart); | ||
| if (innerEnd) { | ||
| if (desc.initialBranch) { | ||
| adoptBranchContent(desc.initialBranch, innerStart, innerEnd); | ||
| } | ||
| setupShowEffect(desc, { start: innerStart, end: innerEnd}); | ||
| } | ||
| break; | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| function adoptNode(desc, ssrEl) { | ||
| if (!ssrEl || ssrEl.tagName !== desc.tag.toUpperCase()) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`Hydration mismatch: expected <${desc.tag}>, got <${ssrEl?.tagName?.toLowerCase() ?? "nothing"}>`); | ||
| const fresh = descriptorToElement(desc); | ||
| if (ssrEl) ssrEl.replaceWith(fresh); | ||
| return; | ||
| } | ||
| applyDynamicProps(ssrEl, desc.props); | ||
| let cursor = ssrEl.firstChild; | ||
| for (const child of desc.children) { | ||
| if (child === false || child == null) continue; | ||
| if (isDescriptor(child)) { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| while (cursor && cursor.nodeType === 1 && cursor.hasAttribute("data-forma-island")) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (!cursor) { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| continue; | ||
| } | ||
| if (cursor.nodeType === 1) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(child, el); | ||
| } else if (cursor.nodeType === 8 && isIslandStart(cursor.data)) { | ||
| const end = findClosingMarker(cursor); | ||
| const fresh = descriptorToElement(child); | ||
| if (end) { | ||
| end.parentNode.insertBefore(fresh, end); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| ssrEl.appendChild(fresh); | ||
| cursor = null; | ||
| } | ||
| } else { | ||
| ssrEl.appendChild(descriptorToElement(child)); | ||
| } | ||
| } else if (isShowDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isShowStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| if (child.initialBranch) { | ||
| adoptBranchContent(child.initialBranch, start, end); | ||
| } | ||
| setupShowEffect(child, { start, end}); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (isListDescriptor(child)) { | ||
| while (cursor && !(cursor.nodeType === 8 && isListStart(cursor.data))) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| const ssrKeyMap = /* @__PURE__ */ new Map(); | ||
| const ssrElements = []; | ||
| let node = start.nextSibling; | ||
| while (node && node !== end) { | ||
| if (node.nodeType === 1) { | ||
| const el = node; | ||
| ssrElements.push(el); | ||
| const key = el.getAttribute("data-forma-key"); | ||
| if (key != null) { | ||
| ssrKeyMap.set(key, el); | ||
| } | ||
| } | ||
| node = node.nextSibling; | ||
| } | ||
| const currentItems = chunkDCTOXHPF_cjs.untrack(() => child.items()); | ||
| const listKeyFn = child.keyFn; | ||
| const listRenderFn = child.renderFn; | ||
| const useIndexFallback = ssrKeyMap.size === 0 && ssrElements.length > 0; | ||
| const adoptedNodes = []; | ||
| const adoptedItems = []; | ||
| const usedIndices = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < currentItems.length; i++) { | ||
| const item = currentItems[i]; | ||
| const key = listKeyFn(item); | ||
| let ssrNode; | ||
| if (useIndexFallback) { | ||
| if (i < ssrElements.length) { | ||
| ssrNode = ssrElements[i]; | ||
| usedIndices.add(i); | ||
| } | ||
| } else { | ||
| ssrNode = ssrKeyMap.get(String(key)); | ||
| if (ssrNode) ssrKeyMap.delete(String(key)); | ||
| } | ||
| if (ssrNode) { | ||
| adoptedNodes.push(ssrNode); | ||
| adoptedItems.push(item); | ||
| } else { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[FormaJS] Hydration: list item key "${key}" not found in SSR \u2014 rendering fresh`); | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const [getIndex] = chunk3U57L2TY_cjs.createSignal(i); | ||
| const fresh = listRenderFn(item, getIndex); | ||
| end.parentNode.insertBefore(fresh, end); | ||
| adoptedNodes.push(fresh); | ||
| adoptedItems.push(item); | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| } | ||
| } | ||
| if (useIndexFallback) { | ||
| for (let i = 0; i < ssrElements.length; i++) { | ||
| if (!usedIndices.has(i) && ssrElements[i].parentNode) { | ||
| ssrElements[i].parentNode.removeChild(ssrElements[i]); | ||
| } | ||
| } | ||
| } else { | ||
| for (const [unusedKey, unusedNode] of ssrKeyMap) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[FormaJS] Hydration: removing extra SSR list item with key "${unusedKey}"`); | ||
| if (unusedNode.parentNode) { | ||
| unusedNode.parentNode.removeChild(unusedNode); | ||
| } | ||
| } | ||
| } | ||
| const parent = start.parentNode; | ||
| for (const adoptedNode of adoptedNodes) { | ||
| parent.insertBefore(adoptedNode, end); | ||
| } | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < adoptedItems.length; i++) { | ||
| const item = adoptedItems[i]; | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = chunk3U57L2TY_cjs.createSignal(i); | ||
| cache.set(key, { | ||
| element: adoptedNodes[i], | ||
| item, | ||
| getIndex, | ||
| setIndex | ||
| }); | ||
| } | ||
| let reconcileNodes = adoptedNodes.slice(); | ||
| let reconcileItems = adoptedItems.slice(); | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const newItems = child.items(); | ||
| const parent2 = start.parentNode; | ||
| if (!parent2) return; | ||
| const result = reconcileList( | ||
| parent2, | ||
| reconcileItems, | ||
| newItems, | ||
| reconcileNodes, | ||
| listKeyFn, | ||
| (item) => { | ||
| const prevHydrating = hydrating; | ||
| hydrating = false; | ||
| try { | ||
| const key = listKeyFn(item); | ||
| const [getIndex, setIndex] = chunk3U57L2TY_cjs.createSignal(0); | ||
| const element = chunkDCTOXHPF_cjs.untrack(() => listRenderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| } finally { | ||
| hydrating = prevHydrating; | ||
| } | ||
| }, | ||
| (_node, item) => { | ||
| const key = listKeyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }, | ||
| end | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| const key = listKeyFn(newItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| reconcileNodes = result.nodes; | ||
| reconcileItems = result.items; | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } | ||
| } | ||
| } else if (typeof child === "function") { | ||
| while (cursor && cursor.nodeType === 3 && !cursor.data.trim()) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| if (cursor && cursor.nodeType === 1) { | ||
| const initial = child(); | ||
| if (isDescriptor(initial)) { | ||
| const el = cursor; | ||
| cursor = cursor.nextSibling; | ||
| adoptNode(initial, el); | ||
| continue; | ||
| } | ||
| } | ||
| if (cursor && cursor.nodeType === 8) { | ||
| const data = cursor.data; | ||
| if (isTextStart(data)) { | ||
| const endMarker = findClosingMarker(cursor); | ||
| let textNode = cursor.nextSibling; | ||
| if (!textNode || textNode.nodeType !== 3) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node for marker ${data} \u2014 SSR walker should emit content between markers`); | ||
| const created = document.createTextNode(""); | ||
| cursor.parentNode.insertBefore(created, endMarker || cursor.nextSibling); | ||
| textNode = created; | ||
| } | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = endMarker ? endMarker.nextSibling : textNode.nextSibling; | ||
| } else if (isShowStart(data)) { | ||
| const start = cursor; | ||
| const end = findClosingMarker(start); | ||
| if (end) { | ||
| let textNode = findTextBetween(start, end); | ||
| if (!textNode) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node for show marker ${start.data} \u2014 SSR walker should emit content between markers`); | ||
| textNode = document.createTextNode(""); | ||
| start.parentNode.insertBefore(textNode, end); | ||
| } | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| cursor = end.nextSibling; | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } else if (cursor && cursor.nodeType === 3) { | ||
| const textNode = cursor; | ||
| cursor = cursor.nextSibling; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } else { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) console.warn(`[FormaJS] Hydration: created text node in empty <${ssrEl.tagName.toLowerCase()}> \u2014 IR may not cover this component`); | ||
| const textNode = document.createTextNode(""); | ||
| ssrEl.appendChild(textNode); | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| textNode.data = String(child()); | ||
| }); | ||
| } | ||
| } else if (typeof child === "string" || typeof child === "number") { | ||
| if (cursor && cursor.nodeType === 3) { | ||
| cursor = cursor.nextSibling; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function hydrateIsland(component, target) { | ||
| const hasSSRContent = target.childElementCount > 0 || target.childNodes.length > 0 && Array.from(target.childNodes).some((n) => n.nodeType === 1 || n.nodeType === 3 && n.data.trim()); | ||
| if (!hasSSRContent) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) { | ||
| const name = target.getAttribute("data-forma-component") || "unknown"; | ||
| console.warn( | ||
| `[forma] Island "${name}" has no SSR content \u2014 falling back to CSR. This means the IR walker did not render content between ISLAND_START and ISLAND_END.` | ||
| ); | ||
| } | ||
| const result = component(); | ||
| if (result instanceof Element) { | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (attr.name.startsWith("data-forma-")) { | ||
| result.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| target.replaceWith(result); | ||
| return result; | ||
| } else if (result instanceof Node) { | ||
| target.appendChild(result); | ||
| } | ||
| return target; | ||
| } | ||
| setHydrating(true); | ||
| let descriptor; | ||
| try { | ||
| descriptor = component(); | ||
| } finally { | ||
| setHydrating(false); | ||
| } | ||
| if (!descriptor || !isDescriptor(descriptor)) { | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| if (target.hasAttribute("data-forma-island")) { | ||
| adoptNode(descriptor, target); | ||
| } else { | ||
| adoptNode(descriptor, target.children[0]); | ||
| } | ||
| target.removeAttribute("data-forma-ssr"); | ||
| return target; | ||
| } | ||
| // src/dom/list.ts | ||
| function longestIncreasingSubsequence(arr) { | ||
| const n = arr.length; | ||
| if (n === 0) return []; | ||
| const tails = new Int32Array(n); | ||
| const tailIndices = new Int32Array(n); | ||
| const predecessor = new Int32Array(n).fill(-1); | ||
| let tailsLen = 0; | ||
| for (let i = 0; i < n; i++) { | ||
| const val = arr[i]; | ||
| let lo = 0, hi = tailsLen; | ||
| while (lo < hi) { | ||
| const mid = lo + hi >> 1; | ||
| if (tails[mid] < val) lo = mid + 1; | ||
| else hi = mid; | ||
| } | ||
| tails[lo] = val; | ||
| tailIndices[lo] = i; | ||
| if (lo > 0) predecessor[i] = tailIndices[lo - 1]; | ||
| if (lo >= tailsLen) tailsLen++; | ||
| } | ||
| const result = new Array(tailsLen); | ||
| let idx = tailIndices[tailsLen - 1]; | ||
| for (let i = tailsLen - 1; i >= 0; i--) { | ||
| result[i] = idx; | ||
| idx = predecessor[idx]; | ||
| } | ||
| return result; | ||
| } | ||
| var SMALL_LIST_THRESHOLD = 32; | ||
| var ABORT_SYM3 = /* @__PURE__ */ Symbol.for("forma-abort"); | ||
| var CACHE_SYM2 = /* @__PURE__ */ Symbol.for("forma-attr-cache"); | ||
| var DYNAMIC_CHILD_SYM2 = /* @__PURE__ */ Symbol.for("forma-dynamic-child"); | ||
| function canPatchStaticElement(target, source) { | ||
| return target instanceof HTMLElement && source instanceof HTMLElement && target.tagName === source.tagName && !target[ABORT_SYM3] && !target[CACHE_SYM2] && !target[DYNAMIC_CHILD_SYM2] && !source[ABORT_SYM3] && !source[CACHE_SYM2] && !source[DYNAMIC_CHILD_SYM2]; | ||
| } | ||
| function patchStaticElement(target, source) { | ||
| const sourceAttrNames = /* @__PURE__ */ new Set(); | ||
| for (const attr of Array.from(source.attributes)) { | ||
| sourceAttrNames.add(attr.name); | ||
| if (target.getAttribute(attr.name) !== attr.value) { | ||
| target.setAttribute(attr.name, attr.value); | ||
| } | ||
| } | ||
| for (const attr of Array.from(target.attributes)) { | ||
| if (!sourceAttrNames.has(attr.name)) { | ||
| target.removeAttribute(attr.name); | ||
| } | ||
| } | ||
| target.replaceChildren(...Array.from(source.childNodes)); | ||
| } | ||
| function reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| const oldKeys = new Array(oldLen); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeys[i] = keyFn(oldItems[i]); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| let found = -1; | ||
| for (let j = 0; j < oldLen; j++) { | ||
| if (!oldUsed[j] && oldKeys[j] === key) { | ||
| found = j; | ||
| oldUsed[j] = 1; | ||
| break; | ||
| } | ||
| } | ||
| oldIndices[i] = found; | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function reconcileList(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) { | ||
| const oldLen = oldItems.length; | ||
| const newLen = newItems.length; | ||
| if (newLen === 0) { | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| return { nodes: [], items: [] }; | ||
| } | ||
| if (oldLen === 0) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = createFn(newItems[i]); | ||
| if (beforeNode) { | ||
| parent.insertBefore(node, beforeNode); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| hooks?.onInsert?.(node); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| if (oldLen < SMALL_LIST_THRESHOLD) { | ||
| return reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks); | ||
| } | ||
| const oldKeyMap = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < oldLen; i++) { | ||
| oldKeyMap.set(keyFn(oldItems[i]), i); | ||
| } | ||
| const oldIndices = new Array(newLen); | ||
| const oldUsed = new Uint8Array(oldLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const key = keyFn(newItems[i]); | ||
| const oldIdx = oldKeyMap.get(key); | ||
| if (oldIdx !== void 0) { | ||
| oldIndices[i] = oldIdx; | ||
| oldUsed[oldIdx] = 1; | ||
| } else { | ||
| oldIndices[i] = -1; | ||
| } | ||
| } | ||
| for (let i = 0; i < oldLen; i++) { | ||
| if (!oldUsed[i]) { | ||
| if (hooks?.onBeforeRemove) { | ||
| const node = oldNodes[i]; | ||
| hooks.onBeforeRemove(node, () => { | ||
| if (node.parentNode) node.parentNode.removeChild(node); | ||
| }); | ||
| } else { | ||
| parent.removeChild(oldNodes[i]); | ||
| } | ||
| } | ||
| } | ||
| if (oldLen === newLen) { | ||
| let allSameOrder = true; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== i) { | ||
| allSameOrder = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allSameOrder) { | ||
| const nodes = new Array(newLen); | ||
| for (let i = 0; i < newLen; i++) { | ||
| const node = oldNodes[i]; | ||
| updateFn(node, newItems[i]); | ||
| nodes[i] = node; | ||
| } | ||
| return { nodes, items: newItems }; | ||
| } | ||
| } | ||
| const reusedIndices = []; | ||
| const reusedPositions = []; | ||
| for (let i = 0; i < newLen; i++) { | ||
| if (oldIndices[i] !== -1) { | ||
| reusedIndices.push(oldIndices[i]); | ||
| reusedPositions.push(i); | ||
| } | ||
| } | ||
| const lisOfReused = longestIncreasingSubsequence(reusedIndices); | ||
| const lisFlags = new Uint8Array(newLen); | ||
| for (const li of lisOfReused) { | ||
| lisFlags[reusedPositions[li]] = 1; | ||
| } | ||
| const newNodes = new Array(newLen); | ||
| let nextSibling = beforeNode ?? null; | ||
| for (let i = newLen - 1; i >= 0; i--) { | ||
| let node; | ||
| let isNew = false; | ||
| if (oldIndices[i] === -1) { | ||
| node = createFn(newItems[i]); | ||
| isNew = true; | ||
| } else { | ||
| node = oldNodes[oldIndices[i]]; | ||
| updateFn(node, newItems[i]); | ||
| if (lisFlags[i]) { | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| continue; | ||
| } | ||
| } | ||
| if (nextSibling) { | ||
| parent.insertBefore(node, nextSibling); | ||
| } else { | ||
| parent.appendChild(node); | ||
| } | ||
| if (isNew) hooks?.onInsert?.(node); | ||
| newNodes[i] = node; | ||
| nextSibling = node; | ||
| } | ||
| return { nodes: newNodes, items: newItems }; | ||
| } | ||
| function createList(items, keyFn, renderFn, options) { | ||
| if (hydrating) { | ||
| return { type: "list", items, keyFn, renderFn, options }; | ||
| } | ||
| const startMarker = document.createComment("forma-list-start"); | ||
| const endMarker = document.createComment("forma-list-end"); | ||
| const fragment2 = document.createDocumentFragment(); | ||
| fragment2.appendChild(startMarker); | ||
| fragment2.appendChild(endMarker); | ||
| let cache = /* @__PURE__ */ new Map(); | ||
| let currentNodes = []; | ||
| let currentItems = []; | ||
| const updateOnItemChange = options?.updateOnItemChange ?? "none"; | ||
| chunkDCTOXHPF_cjs.internalEffect(() => { | ||
| const newItems = items(); | ||
| const parent = startMarker.parentNode; | ||
| if (!parent) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(newItems)) { | ||
| if (chunkDCTOXHPF_cjs.__DEV__) { | ||
| console.warn("[forma] createList: value is not an array, treating as empty"); | ||
| } | ||
| for (const node of currentNodes) { | ||
| if (node.parentNode === parent) parent.removeChild(node); | ||
| } | ||
| cache = /* @__PURE__ */ new Map(); | ||
| currentNodes = []; | ||
| currentItems = []; | ||
| return; | ||
| } | ||
| let cleanItems = newItems; | ||
| for (let i = 0; i < newItems.length; i++) { | ||
| if (newItems[i] == null) { | ||
| cleanItems = newItems.filter((item) => item != null); | ||
| break; | ||
| } | ||
| } | ||
| if (chunkDCTOXHPF_cjs.__DEV__) { | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const item of cleanItems) { | ||
| const key = keyFn(item); | ||
| if (seen.has(key)) { | ||
| console.warn("[forma] createList: duplicate key detected:", key); | ||
| } | ||
| seen.add(key); | ||
| } | ||
| } | ||
| const updateRow = updateOnItemChange === "rerender" ? (node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (!cached) return; | ||
| if (cached.item === item) return; | ||
| cached.item = item; | ||
| if (!(node instanceof HTMLElement)) return; | ||
| if (node[ABORT_SYM3] || node[CACHE_SYM2] || node[DYNAMIC_CHILD_SYM2]) { | ||
| return; | ||
| } | ||
| const next = chunkDCTOXHPF_cjs.untrack(() => renderFn(item, cached.getIndex)); | ||
| if (canPatchStaticElement(node, next)) { | ||
| patchStaticElement(node, next); | ||
| cached.element = node; | ||
| } | ||
| } : (_node, item) => { | ||
| const key = keyFn(item); | ||
| const cached = cache.get(key); | ||
| if (cached) cached.item = item; | ||
| }; | ||
| const result = reconcileList( | ||
| parent, | ||
| currentItems, | ||
| cleanItems, | ||
| currentNodes, | ||
| keyFn, | ||
| // createFn: create element + cache entry | ||
| (item) => { | ||
| const key = keyFn(item); | ||
| const [getIndex, setIndex] = chunk3U57L2TY_cjs.createSignal(0); | ||
| const element = chunkDCTOXHPF_cjs.untrack(() => renderFn(item, getIndex)); | ||
| cache.set(key, { element, item, getIndex, setIndex }); | ||
| return element; | ||
| }, | ||
| updateRow, | ||
| // beforeNode: insert items before the end marker | ||
| endMarker | ||
| ); | ||
| const newCache = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < cleanItems.length; i++) { | ||
| const key = keyFn(cleanItems[i]); | ||
| const cached = cache.get(key); | ||
| if (cached) { | ||
| cached.setIndex(i); | ||
| newCache.set(key, cached); | ||
| } | ||
| } | ||
| cache = newCache; | ||
| currentNodes = result.nodes; | ||
| currentItems = result.items; | ||
| }); | ||
| return fragment2; | ||
| } | ||
| exports.Fragment = Fragment; | ||
| exports.cleanup = cleanup; | ||
| exports.createList = createList; | ||
| exports.createShow = createShow; | ||
| exports.fragment = fragment; | ||
| exports.h = h; | ||
| exports.hydrateIsland = hydrateIsland; | ||
| exports.reconcileList = reconcileList; | ||
| //# sourceMappingURL=chunk-SZSKW57A.cjs.map | ||
| //# sourceMappingURL=chunk-SZSKW57A.cjs.map |
Sorry, the diff of this file is too big to display
| import { S as SignalGetter } from './signal-YlS1kgfh.js'; | ||
| /** | ||
| * Forma Reactive - Resource | ||
| * | ||
| * Async data fetching primitive with reactive loading/error state. | ||
| * Tracks a source signal and refetches when it changes. | ||
| * | ||
| * SolidJS equivalent: createResource | ||
| * React equivalent: use() + Suspense (React 19), or useSWR/react-query | ||
| */ | ||
| interface Resource<T> { | ||
| /** The resolved data (or undefined while loading). */ | ||
| (): T | undefined; | ||
| /** True while the fetcher is running. */ | ||
| loading: SignalGetter<boolean>; | ||
| /** The error if the fetcher rejected (or undefined). */ | ||
| error: SignalGetter<unknown>; | ||
| /** Manually refetch with the current source value. */ | ||
| refetch: () => void; | ||
| /** Manually set the data (overrides fetcher result). */ | ||
| mutate: (value: T | undefined) => void; | ||
| } | ||
| interface ResourceOptions<T> { | ||
| /** Initial value before first fetch resolves. */ | ||
| initialValue?: T; | ||
| } | ||
| /** | ||
| * Create an async resource that fetches data reactively. | ||
| * | ||
| * When `source` changes, the fetcher re-runs automatically. | ||
| * Provides reactive `loading` and `error` signals. | ||
| * | ||
| * ```ts | ||
| * const [userId, setUserId] = createSignal(1); | ||
| * | ||
| * const user = createResource( | ||
| * userId, // source signal | ||
| * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher | ||
| * ); | ||
| * | ||
| * internalEffect(() => { | ||
| * if (user.loading()) console.log('Loading...'); | ||
| * else if (user.error()) console.log('Error:', user.error()); | ||
| * else console.log('User:', user()); | ||
| * }); | ||
| * | ||
| * setUserId(2); // automatically refetches | ||
| * ``` | ||
| * | ||
| * Without a source signal (static fetch): | ||
| * ```ts | ||
| * const posts = createResource( | ||
| * () => true, // constant source — fetches once | ||
| * () => fetch('/api/posts').then(r => r.json()), | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S) => Promise<T>, options?: ResourceOptions<T>): Resource<T>; | ||
| export { type Resource as R, type ResourceOptions as a, createResource as c }; |
| import { S as SignalGetter } from './signal-YlS1kgfh.cjs'; | ||
| /** | ||
| * Forma Reactive - Resource | ||
| * | ||
| * Async data fetching primitive with reactive loading/error state. | ||
| * Tracks a source signal and refetches when it changes. | ||
| * | ||
| * SolidJS equivalent: createResource | ||
| * React equivalent: use() + Suspense (React 19), or useSWR/react-query | ||
| */ | ||
| interface Resource<T> { | ||
| /** The resolved data (or undefined while loading). */ | ||
| (): T | undefined; | ||
| /** True while the fetcher is running. */ | ||
| loading: SignalGetter<boolean>; | ||
| /** The error if the fetcher rejected (or undefined). */ | ||
| error: SignalGetter<unknown>; | ||
| /** Manually refetch with the current source value. */ | ||
| refetch: () => void; | ||
| /** Manually set the data (overrides fetcher result). */ | ||
| mutate: (value: T | undefined) => void; | ||
| } | ||
| interface ResourceOptions<T> { | ||
| /** Initial value before first fetch resolves. */ | ||
| initialValue?: T; | ||
| } | ||
| /** | ||
| * Create an async resource that fetches data reactively. | ||
| * | ||
| * When `source` changes, the fetcher re-runs automatically. | ||
| * Provides reactive `loading` and `error` signals. | ||
| * | ||
| * ```ts | ||
| * const [userId, setUserId] = createSignal(1); | ||
| * | ||
| * const user = createResource( | ||
| * userId, // source signal | ||
| * (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher | ||
| * ); | ||
| * | ||
| * internalEffect(() => { | ||
| * if (user.loading()) console.log('Loading...'); | ||
| * else if (user.error()) console.log('Error:', user.error()); | ||
| * else console.log('User:', user()); | ||
| * }); | ||
| * | ||
| * setUserId(2); // automatically refetches | ||
| * ``` | ||
| * | ||
| * Without a source signal (static fetch): | ||
| * ```ts | ||
| * const posts = createResource( | ||
| * () => true, // constant source — fetches once | ||
| * () => fetch('/api/posts').then(r => r.json()), | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S) => Promise<T>, options?: ResourceOptions<T>): Resource<T>; | ||
| export { type Resource as R, type ResourceOptions as a, createResource as c }; |
| /** | ||
| * Forma Reactive - Signal | ||
| * | ||
| * Fine-grained reactive primitive backed by alien-signals. | ||
| * API: createSignal returns [getter, setter] tuple following SolidJS conventions. | ||
| * | ||
| * TC39 Signals equivalent: Signal.State | ||
| */ | ||
| type SignalGetter<T> = () => T; | ||
| type SignalSetter<T> = (v: T | ((prev: T) => T)) => void; | ||
| interface SignalOptions<T> { | ||
| /** Debug name — attached to getter in dev mode for devtools inspection. */ | ||
| name?: string; | ||
| /** | ||
| * Custom equality check. When provided, the setter will read the current | ||
| * value and only update the signal if `equals(prev, next)` returns `false`. | ||
| * | ||
| * Default: none (alien-signals uses strict inequality `!==` internally). | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * | ||
| * setPos({ x: 0, y: 0 }); // skipped — equals returns true | ||
| * setPos({ x: 1, y: 0 }); // applied — equals returns false | ||
| * ``` | ||
| */ | ||
| equals?: (prev: T, next: T) => boolean; | ||
| } | ||
| /** | ||
| * Create a reactive signal. | ||
| * | ||
| * ```ts | ||
| * const [count, setCount] = createSignal(0); | ||
| * console.log(count()); // 0 | ||
| * setCount(1); | ||
| * setCount(prev => prev + 1); | ||
| * ``` | ||
| * | ||
| * With custom equality: | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>]; | ||
| export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c }; |
| /** | ||
| * Forma Reactive - Signal | ||
| * | ||
| * Fine-grained reactive primitive backed by alien-signals. | ||
| * API: createSignal returns [getter, setter] tuple following SolidJS conventions. | ||
| * | ||
| * TC39 Signals equivalent: Signal.State | ||
| */ | ||
| type SignalGetter<T> = () => T; | ||
| type SignalSetter<T> = (v: T | ((prev: T) => T)) => void; | ||
| interface SignalOptions<T> { | ||
| /** Debug name — attached to getter in dev mode for devtools inspection. */ | ||
| name?: string; | ||
| /** | ||
| * Custom equality check. When provided, the setter will read the current | ||
| * value and only update the signal if `equals(prev, next)` returns `false`. | ||
| * | ||
| * Default: none (alien-signals uses strict inequality `!==` internally). | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * | ||
| * setPos({ x: 0, y: 0 }); // skipped — equals returns true | ||
| * setPos({ x: 1, y: 0 }); // applied — equals returns false | ||
| * ``` | ||
| */ | ||
| equals?: (prev: T, next: T) => boolean; | ||
| } | ||
| /** | ||
| * Create a reactive signal. | ||
| * | ||
| * ```ts | ||
| * const [count, setCount] = createSignal(0); | ||
| * console.log(count()); // 0 | ||
| * setCount(1); | ||
| * setCount(prev => prev + 1); | ||
| * ``` | ||
| * | ||
| * With custom equality: | ||
| * | ||
| * ```ts | ||
| * const [pos, setPos] = createSignal( | ||
| * { x: 0, y: 0 }, | ||
| * { equals: (a, b) => a.x === b.x && a.y === b.y }, | ||
| * ); | ||
| * ``` | ||
| */ | ||
| declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>]; | ||
| export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
3968134
0.8%39042
0.35%116
5.45%