what-core 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/{chunk-RI7T5VFD.min.js → chunk-D5YDPQ57.min.js} +1 -2
  2. package/dist/chunk-O3SKPRTY.min.js +0 -1
  3. package/dist/chunk-W33M3HL5.min.js +1 -0
  4. package/dist/index.min.js +2 -3
  5. package/dist/jsx-dev-runtime.min.js +0 -1
  6. package/dist/jsx-runtime.min.js +0 -1
  7. package/dist/render.min.js +1 -2
  8. package/dist/testing.min.js +1 -2
  9. package/package.json +2 -2
  10. package/src/agent-context.js +1 -1
  11. package/src/reactive.js +20 -4
  12. package/dist/a11y.js +0 -440
  13. package/dist/animation.js +0 -548
  14. package/dist/chunk-2IZMPODD.min.js +0 -2
  15. package/dist/chunk-2IZMPODD.min.js.map +0 -7
  16. package/dist/chunk-2P7OVL2L.js +0 -1386
  17. package/dist/chunk-2P7OVL2L.js.map +0 -7
  18. package/dist/chunk-5EQUBJWQ.js +0 -1365
  19. package/dist/chunk-5EQUBJWQ.js.map +0 -7
  20. package/dist/chunk-6DAIK77K.min.js +0 -2
  21. package/dist/chunk-6DAIK77K.min.js.map +0 -7
  22. package/dist/chunk-AW3BAPIK.js +0 -1685
  23. package/dist/chunk-AW3BAPIK.js.map +0 -7
  24. package/dist/chunk-AZP2EOGX.js +0 -188
  25. package/dist/chunk-AZP2EOGX.js.map +0 -7
  26. package/dist/chunk-CCINITLW.js +0 -1692
  27. package/dist/chunk-CCINITLW.js.map +0 -7
  28. package/dist/chunk-F2HUXI22.js +0 -1675
  29. package/dist/chunk-F2HUXI22.js.map +0 -7
  30. package/dist/chunk-GZRA4IAJ.js +0 -1699
  31. package/dist/chunk-GZRA4IAJ.js.map +0 -7
  32. package/dist/chunk-H3GA34JK.js +0 -1384
  33. package/dist/chunk-H3GA34JK.js.map +0 -7
  34. package/dist/chunk-KBM6CWG4.min.js +0 -2
  35. package/dist/chunk-KBM6CWG4.min.js.map +0 -7
  36. package/dist/chunk-KL7TNUIU.min.js +0 -2
  37. package/dist/chunk-KL7TNUIU.min.js.map +0 -7
  38. package/dist/chunk-L6XOF7P4.min.js +0 -2
  39. package/dist/chunk-L6XOF7P4.min.js.map +0 -7
  40. package/dist/chunk-M7UEET5O.js +0 -1323
  41. package/dist/chunk-M7UEET5O.js.map +0 -7
  42. package/dist/chunk-MH7L756Y.min.js +0 -2
  43. package/dist/chunk-MH7L756Y.min.js.map +0 -7
  44. package/dist/chunk-O3SKPRTY.min.js.map +0 -7
  45. package/dist/chunk-RI7T5VFD.min.js.map +0 -7
  46. package/dist/chunk-RN6QIBWL.min.js +0 -2
  47. package/dist/chunk-RN6QIBWL.min.js.map +0 -7
  48. package/dist/chunk-VKCFJ4OT.min.js +0 -2
  49. package/dist/chunk-VKCFJ4OT.min.js.map +0 -7
  50. package/dist/chunk-VMTTYB4L.min.js +0 -2
  51. package/dist/chunk-VMTTYB4L.min.js.map +0 -7
  52. package/dist/chunk-VP4WLF5A.js +0 -1323
  53. package/dist/chunk-VP4WLF5A.js.map +0 -7
  54. package/dist/chunk-YA3W4XKH.js +0 -1323
  55. package/dist/chunk-YA3W4XKH.js.map +0 -7
  56. package/dist/compiler.js +0 -1799
  57. package/dist/compiler.js.map +0 -7
  58. package/dist/compiler.min.js +0 -2
  59. package/dist/compiler.min.js.map +0 -7
  60. package/dist/components.js +0 -229
  61. package/dist/data.js +0 -638
  62. package/dist/devtools.js +0 -10
  63. package/dist/devtools.js.map +0 -7
  64. package/dist/devtools.min.js +0 -2
  65. package/dist/devtools.min.js.map +0 -7
  66. package/dist/dom.js +0 -439
  67. package/dist/form.js +0 -509
  68. package/dist/h.js +0 -152
  69. package/dist/head.js +0 -51
  70. package/dist/helpers.js +0 -140
  71. package/dist/hooks.js +0 -210
  72. package/dist/index.js +0 -3576
  73. package/dist/index.js.map +0 -7
  74. package/dist/index.min.js.map +0 -7
  75. package/dist/jsx-dev-runtime.js +0 -23
  76. package/dist/jsx-dev-runtime.js.map +0 -7
  77. package/dist/jsx-dev-runtime.min.js.map +0 -7
  78. package/dist/jsx-runtime.js +0 -21
  79. package/dist/jsx-runtime.js.map +0 -7
  80. package/dist/jsx-runtime.min.js.map +0 -7
  81. package/dist/reactive.js +0 -432
  82. package/dist/render.js +0 -53
  83. package/dist/render.js.map +0 -7
  84. package/dist/render.min.js.map +0 -7
  85. package/dist/scheduler.js +0 -246
  86. package/dist/skeleton.js +0 -363
  87. package/dist/store.js +0 -83
  88. package/dist/testing.js +0 -439
  89. package/dist/testing.js.map +0 -7
  90. package/dist/testing.min.js.map +0 -7
  91. package/dist/what.js +0 -117
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/reactive.js"],
4
- "sourcesContent": ["// What Framework - Reactive Primitives\n// Signals + Effects: fine-grained reactivity without virtual DOM overhead\n//\n// Upgrades:\n// - Topological ordering: computed/effects sorted by _level to prevent diamond glitches\n// - Iterative computed evaluation: no recursion, handles 10K+ depth chains\n// - Ownership tree: createRoot children auto-dispose when parent disposes\n// - Performance: cached levels, lazy sort, fast-path notify, minimal allocation\n\n// Dev-mode flag \u2014 build tools can dead-code-eliminate when false\nexport const __DEV__ = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n// DevTools hooks \u2014 set by what-devtools when installed.\n// These are no-ops in production (dead-code eliminated with __DEV__).\nexport let __devtools = null;\n\n/** @internal Install devtools hooks. Called by what-devtools. */\nexport function __setDevToolsHooks(hooks) {\n if (__DEV__) __devtools = hooks;\n}\n\nlet currentEffect = null;\nlet currentRoot = null;\nlet currentOwner = null; // Ownership tree: tracks current owner context\nlet insideComputed = false; // Track whether we're inside a computed() callback (dev-mode warning)\nlet batchDepth = 0;\nlet pendingEffects = [];\nlet pendingNeedSort = false; // Track whether pendingEffects actually needs sorting\n\n// WeakMap: subscriber Set \u2192 owning computed's inner effect (null/absent for signals)\n// Used for topological level computation.\nconst subSetOwner = new WeakMap();\n\n// --- Iterative Computed Evaluation State ---\n// Uses a throw/catch trampoline to convert recursive computed evaluation\n// to iterative. When a computed fn() reads another dirty computed, instead\n// of recursing, we throw a sentinel that gets caught by the outer loop.\nconst NEEDS_UPSTREAM = Symbol('needs_upstream');\nlet iterativeEvalStack = null; // array when inside evaluation loop, null otherwise\n\n// --- Signal ---\n// A reactive value. Reading inside an effect auto-tracks the dependency.\n// Writing triggers only the effects that depend on this signal.\n\nexport function signal(initial, debugName) {\n let value = initial;\n const subs = new Set();\n\n // Unified getter/setter: sig() reads, sig(newVal) writes\n function sig(...args) {\n if (args.length === 0) {\n // Read\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n return value;\n }\n // Write\n if (__DEV__ && insideComputed) {\n console.warn(\n '[what] Signal.set() called inside a computed function. ' +\n 'This may cause infinite loops. Use effect() instead.' +\n (debugName ? ` (signal: ${debugName})` : '')\n );\n }\n const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];\n if (Object.is(value, nextVal)) return;\n value = nextVal;\n if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);\n if (subs.size > 0) notify(subs);\n }\n\n sig.set = (next) => {\n if (__DEV__ && insideComputed) {\n console.warn(\n '[what] Signal.set() called inside a computed function. ' +\n 'This may cause infinite loops. Use effect() instead.' +\n (debugName ? ` (signal: ${debugName})` : '')\n );\n }\n const nextVal = typeof next === 'function' ? next(value) : next;\n if (Object.is(value, nextVal)) return;\n value = nextVal;\n if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);\n if (subs.size > 0) notify(subs);\n };\n\n sig.peek = () => value;\n\n sig.subscribe = (fn) => {\n return effect(() => fn(sig()));\n };\n\n sig._signal = true;\n if (__DEV__) {\n sig._subs = subs;\n if (debugName) sig._debugName = debugName;\n }\n\n // Notify devtools of signal creation\n if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);\n\n return sig;\n}\n\n// --- Computed ---\n// Derived signal. Lazy: only recomputes when a dependency changes AND it's read.\n// Topological level: max(dependency levels) + 1, computed from source signals (level 0).\n\nexport function computed(fn) {\n let value, dirty = true;\n const subs = new Set();\n\n const inner = _createEffect(() => {\n const prevInsideComputed = insideComputed;\n if (__DEV__) insideComputed = true;\n try {\n value = fn();\n dirty = false;\n } finally {\n if (__DEV__) insideComputed = prevInsideComputed;\n }\n }, true);\n\n // Computed nodes start at level 1. Updated when graph structure changes.\n inner._level = 1;\n inner._computed = true;\n inner._computedSubs = subs;\n\n // Register this subscriber set as owned by this computed\n subSetOwner.set(subs, inner);\n\n // Store markDirty/isDirty closures on the inner effect for iterative eval\n inner._markDirty = () => { dirty = true; };\n inner._isDirty = () => dirty;\n\n function read() {\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n if (dirty) _evaluateComputed(inner);\n return value;\n }\n\n // When a dependency changes, mark dirty AND propagate to our subscribers.\n inner._onNotify = () => {\n dirty = true;\n if (subs.size > 0) notify(subs);\n };\n\n read._signal = true;\n read.peek = () => {\n if (dirty) _evaluateComputed(inner);\n return value;\n };\n\n return read;\n}\n\n// --- Iterative Computed Evaluation ---\n//\n// Problem: A chain of N dirty computeds causes O(N) recursive calls:\n// C_N.read() \u2192 eval \u2192 fn() \u2192 C_{N-1}.read() \u2192 eval \u2192 fn() \u2192 ... \u2192 C_1.read() \u2192 eval \u2192 fn()\n// This overflows the stack at ~3500 depth.\n//\n// Solution: Throw/catch trampoline. The outermost _evaluateComputed manages a\n// stack (array). When a computed's fn() reads another dirty computed during\n// evaluation, _evaluateComputed throws NEEDS_UPSTREAM. The outer loop catches\n// this, adds the upstream to the stack, and processes from the bottom up.\n// This converts O(N) call depth to O(1) per computed (just the outermost loop).\n\nfunction _evaluateComputed(computedEffect) {\n if (iterativeEvalStack !== null) {\n // We're inside the outermost evaluation loop, and a computed's fn()\n // is reading another dirty computed. Push it onto the stack and throw\n // to abort the current fn() so the outer loop can process it first.\n iterativeEvalStack.push(computedEffect);\n throw NEEDS_UPSTREAM;\n }\n\n // Outermost call \u2014 enter the iterative evaluation loop.\n // The stack grows as we discover dirty upstream computeds.\n const stack = [computedEffect];\n iterativeEvalStack = stack;\n\n try {\n while (stack.length > 0) {\n const current = stack[stack.length - 1];\n\n if (!current._isDirty || !current._isDirty()) {\n // Already clean \u2014 pop and continue\n stack.pop();\n continue;\n }\n\n // Pre-scan known deps: if any are dirty computeds, push them onto\n // the stack first (bottom-up). This avoids the O(N^2) worst case\n // where throw/catch restarts from the top on each dirty upstream.\n let pushedUpstream = false;\n const deps = current.deps;\n for (let i = 0; i < deps.length; i++) {\n const depOwner = subSetOwner.get(deps[i]);\n if (depOwner && depOwner._computed && depOwner._isDirty && depOwner._isDirty()) {\n stack.push(depOwner);\n pushedUpstream = true;\n }\n }\n if (pushedUpstream) {\n // Process dirty upstreams first before re-evaluating current\n continue;\n }\n\n // All known deps are clean \u2014 evaluate. throw/catch is fallback\n // for newly-discovered deps only.\n try {\n const prevDepsLen = current.deps.length;\n _runEffect(current);\n // Only recompute level when graph structure changes\n if (current.deps.length !== prevDepsLen) {\n _updateLevel(current);\n }\n stack.pop(); // Successfully evaluated\n } catch (err) {\n if (err === NEEDS_UPSTREAM) {\n // A dirty upstream was discovered and pushed onto the stack.\n // Re-mark this computed dirty since its fn() was aborted mid-execution.\n current._markDirty();\n // The upstream is now at stack[stack.length-1]. Loop continues.\n } else {\n throw err; // Re-throw real errors\n }\n }\n }\n } finally {\n iterativeEvalStack = null;\n }\n}\n\n// Update the topological level of a computed/effect based on its current dependencies.\nfunction _updateLevel(e) {\n let maxDepLevel = 0;\n const deps = e.deps;\n for (let i = 0; i < deps.length; i++) {\n const owner = subSetOwner.get(deps[i]);\n if (owner) {\n const depLevel = owner._level;\n if (depLevel > maxDepLevel) maxDepLevel = depLevel;\n }\n }\n e._level = maxDepLevel + 1;\n}\n\n// --- Effect ---\n// Runs a function, auto-tracking signal reads. Re-runs when deps change.\n// Returns a dispose function.\n\nexport function effect(fn, opts) {\n const e = _createEffect(fn);\n e._level = 1;\n // First run: skip cleanup (deps is empty), just run and track\n const prev = currentEffect;\n currentEffect = e;\n try {\n const result = e.fn();\n if (typeof result === 'function') e._cleanup = result;\n } finally {\n currentEffect = prev;\n }\n // Compute level after first run based on actual dependencies (cached).\n _updateLevel(e);\n // Mark as stable after first run \u2014 subsequent re-runs skip cleanup/re-subscribe\n if (opts?.stable) e._stable = true;\n const dispose = () => _disposeEffect(e);\n // Register with current root for automatic cleanup\n if (currentRoot) {\n currentRoot.disposals.push(dispose);\n }\n return dispose;\n}\n\n// --- Batch ---\n// Group multiple signal writes; effects run once at the end.\n\nexport function batch(fn) {\n batchDepth++;\n try {\n fn();\n } finally {\n batchDepth--;\n if (batchDepth === 0) flush();\n }\n}\n\n// --- Internals ---\n\nfunction _createEffect(fn, lazy) {\n // Minimal object shape \u2014 computed() adds extra properties after creation.\n // Keeping the base object small helps V8 optimize for the common (effect) case.\n const e = {\n fn,\n deps: [], // array of subscriber sets (cheaper than Set for typical 1-3 deps)\n lazy: lazy || false,\n _onNotify: null,\n disposed: false,\n _pending: false,\n _stable: false, // stable effects skip cleanup/re-subscribe on re-run\n _level: 0, // topological depth: signals=0, computed/effects=max(deps)+1\n _computed: false, // true for computed inner effects\n _computedSubs: null, // reference to the computed's subscriber set\n _isDirty: null, // function to check if computed is dirty (set by computed())\n _markDirty: null, // function to mark computed dirty (set by computed())\n };\n if (__DEV__ && __devtools) __devtools.onEffectCreate(e);\n return e;\n}\n\nfunction _runEffect(e) {\n if (e.disposed) return;\n\n // Stable effect fast path: deps don't change, skip cleanup/re-subscribe.\n if (e._stable) {\n runStableEffect(e);\n return;\n }\n\n cleanup(e);\n // Run effect cleanup from previous run\n runEffectCleanup(e, 'effect cleanup');\n const prev = currentEffect;\n currentEffect = e;\n try {\n const result = e.fn();\n // Capture cleanup function if returned\n if (typeof result === 'function') {\n e._cleanup = result;\n }\n } catch (err) {\n if (err === NEEDS_UPSTREAM) throw err; // Iterative eval sentinel \u2014 not a real error\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });\n throw err;\n } finally {\n currentEffect = prev;\n }\n if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);\n}\n\nfunction _disposeEffect(e) {\n e.disposed = true;\n if (__DEV__ && __devtools) __devtools.onEffectDispose(e);\n cleanup(e);\n // Run cleanup on dispose\n runEffectCleanup(e, 'effect cleanup on dispose');\n}\n\nfunction reportEffectCleanupError(err, e, phase) {\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e, phase });\n if (__DEV__) console.warn(`[what] Error in ${phase}:`, err);\n}\n\nfunction runEffectCleanup(e, phase) {\n if (!e._cleanup) return;\n const cleanupFn = e._cleanup;\n e._cleanup = null;\n try {\n cleanupFn();\n } catch (err) {\n reportEffectCleanupError(err, e, phase);\n }\n}\n\nfunction runStableEffect(e) {\n const prev = currentEffect;\n currentEffect = null; // Don't re-track deps (already subscribed)\n try {\n runEffectCleanup(e, 'stable effect cleanup');\n const result = e.fn();\n if (typeof result === 'function') e._cleanup = result;\n } catch (err) {\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });\n if (__DEV__) console.warn('[what] Error in stable effect:', err);\n } finally {\n currentEffect = prev;\n }\n if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);\n}\n\nfunction cleanup(e) {\n const deps = e.deps;\n for (let i = 0; i < deps.length; i++) deps[i].delete(e);\n deps.length = 0;\n}\n\n// --- Notification ---\n// Iterative notification to prevent stack overflow on deep computed chains.\n// Uses a reusable queue to avoid per-call array allocation.\n// When notify() encounters _onNotify callbacks (from computeds), those may\n// call notify() recursively. The queue drains iteratively in the outermost call.\n\nlet notifyDepth = 0; // Tracks recursive notify depth\nlet notifyQueue = null; // Reusable queue, allocated on first recursive call\nlet notifyQueueLen = 0; // Length of the queue\n\nfunction notify(subs) {\n // Fast path: no recursive notifications in progress \u2014 iterate directly.\n // This avoids array allocation for the common case (signal \u2192 effects).\n if (notifyDepth === 0) {\n notifyDepth = 1;\n try {\n for (const e of subs) {\n if (e.disposed) continue;\n if (e._onNotify) {\n // Computed subscriber: mark dirty and propagate.\n // _onNotify may call notify() recursively \u2014 tracked by notifyDepth.\n e._onNotify();\n } else if (batchDepth === 0 && e._stable) {\n runStableEffect(e);\n } else if (!e._pending) {\n e._pending = true;\n const level = e._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(e);\n }\n }\n // Drain any queued subscriber sets from recursive notify calls\n if (notifyQueueLen > 0) {\n let qi = 0;\n while (qi < notifyQueueLen) {\n const queuedSubs = notifyQueue[qi];\n notifyQueue[qi] = null; // Allow GC\n qi++;\n for (const e of queuedSubs) {\n if (e.disposed) continue;\n if (e._onNotify) {\n e._onNotify();\n } else if (batchDepth === 0 && e._stable) {\n runStableEffect(e);\n } else if (!e._pending) {\n e._pending = true;\n const level = e._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(e);\n }\n }\n }\n notifyQueueLen = 0;\n }\n } finally {\n notifyDepth = 0;\n }\n if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();\n } else {\n // Recursive call \u2014 queue the subscriber set for the outermost call to drain.\n if (notifyQueue === null) notifyQueue = [];\n if (notifyQueueLen >= notifyQueue.length) {\n notifyQueue.push(subs);\n } else {\n notifyQueue[notifyQueueLen] = subs;\n }\n notifyQueueLen++;\n }\n}\n\nlet microtaskScheduled = false;\nfunction scheduleMicrotask() {\n if (!microtaskScheduled) {\n microtaskScheduled = true;\n queueMicrotask(() => {\n microtaskScheduled = false;\n flush();\n });\n }\n}\n\nlet isFlushing = false;\n\nfunction flush() {\n // Re-entrancy guard: if flush() is called during an active flush (e.g., via\n // flushSync() inside a component render or effect), skip to prevent infinite\n // recursion. Pending effects will be picked up by the outer flush's while-loop.\n if (isFlushing) return;\n isFlushing = true;\n\n try {\n let iterations = 0;\n while (pendingEffects.length > 0 && iterations < 25) {\n const batch = pendingEffects;\n pendingEffects = [];\n\n // Topological sort: execute effects in level order (lowest first).\n // Fast paths:\n // 1. Single effect \u2014 no sort needed (most common case for microtask flush)\n // 2. Already sorted \u2014 skip sort (common when effects added in level order)\n // 3. Multiple effects at different levels \u2014 sort required\n if (batch.length > 1 && pendingNeedSort) {\n batch.sort((a, b) => a._level - b._level);\n }\n pendingNeedSort = false;\n\n for (let i = 0; i < batch.length; i++) {\n const e = batch[i];\n e._pending = false;\n if (!e.disposed && !e._onNotify) {\n const prevDepsLen = e.deps.length;\n _runEffect(e);\n // Update level only if deps changed (graph structure change)\n if (!e._computed && e.deps.length !== prevDepsLen) {\n _updateLevel(e);\n }\n }\n }\n iterations++;\n }\n if (iterations >= 25) {\n // Clear pending effects to prevent further damage\n for (let i = 0; i < pendingEffects.length; i++) pendingEffects[i]._pending = false;\n pendingEffects.length = 0;\n\n if (__DEV__) {\n const remaining = pendingEffects.slice(0, 3);\n const effectNames = remaining.map(e => e.fn?.name || e.fn?.toString().slice(0, 60) || '(anonymous)');\n console.warn(\n `[what] Possible infinite effect loop detected (25 iterations). ` +\n `Likely cause: an effect writes to a signal it also reads, creating a cycle. ` +\n `Use untrack() to read signals without subscribing. ` +\n `Looping effects: ${effectNames.join(', ')}`\n );\n } else {\n console.warn('[what] Possible infinite effect loop detected');\n }\n }\n } finally {\n isFlushing = false;\n }\n}\n\n// --- Memo ---\n// Eager computed that only propagates when the value actually changes.\n// Fix: Instead of calling notify(subs) inline (which bypasses topological sort\n// and causes diamond-dependency glitches), push memo subscribers into\n// pendingEffects and let them go through the sorted flush() path.\nexport function memo(fn) {\n let value;\n const subs = new Set();\n\n const e = _createEffect(() => {\n const next = fn();\n if (!Object.is(value, next)) {\n value = next;\n // Push subscribers into pendingEffects for topological flush\n // instead of inline notify() which can cause diamond glitches\n for (const sub of subs) {\n if (sub.disposed) continue;\n if (sub._onNotify) {\n // Computed subscriber: mark dirty and propagate\n sub._onNotify();\n } else if (!sub._pending) {\n sub._pending = true;\n const level = sub._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(sub);\n }\n }\n }\n });\n\n e._level = 1;\n\n _runEffect(e);\n _updateLevel(e);\n\n // Register subscriber set owner for level tracking\n subSetOwner.set(subs, e);\n\n // Register with current root\n if (currentRoot) {\n currentRoot.disposals.push(() => _disposeEffect(e));\n }\n\n function read() {\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n return value;\n }\n\n read._signal = true;\n read.peek = () => value;\n return read;\n}\n\n// --- flushSync ---\n// Force all pending effects to run synchronously. Use sparingly.\n// Calling during render or effect execution is a no-op (prevents infinite loops).\nexport function flushSync() {\n if (isFlushing) {\n // Re-entrant call \u2014 silently skip (Solid approach).\n // This prevents infinite loops when flushSync() is called during component\n // render or effect execution. Pending effects will be picked up by the\n // outer flush's while-loop.\n if (__DEV__) {\n console.warn(\n '[what] flushSync() called during an active flush (e.g., inside a component render or effect). ' +\n 'This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback.'\n );\n }\n return;\n }\n if (currentEffect) {\n // Called inside an effect/render \u2014 skip with warning\n if (__DEV__) {\n console.warn(\n '[what] flushSync() called during effect execution. ' +\n 'This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback.'\n );\n }\n return;\n }\n microtaskScheduled = false;\n flush();\n}\n\n// --- Untrack ---\n// Read signals without subscribing\nexport function untrack(fn) {\n const prev = currentEffect;\n currentEffect = null;\n try {\n return fn();\n } finally {\n currentEffect = prev;\n }\n}\n\n// --- getOwner / runWithOwner ---\n// Expose ownership context for advanced use cases (e.g., async operations\n// that need to register disposals with the correct owner).\n\nexport function getOwner() {\n return currentOwner;\n}\n\nexport function runWithOwner(owner, fn) {\n const prev = currentOwner;\n const prevRoot = currentRoot;\n currentOwner = owner;\n currentRoot = owner;\n try {\n return fn();\n } finally {\n currentOwner = prev;\n currentRoot = prevRoot;\n }\n}\n\n// --- createRoot ---\n// Isolated reactive scope with ownership tree.\n// All effects created inside are tracked and disposed together.\n// Child createRoot scopes register with parent owner \u2014 disposing parent\n// automatically disposes all children (prevents orphaned subscriptions).\nexport function createRoot(fn) {\n const prevRoot = currentRoot;\n const prevOwner = currentOwner;\n const root = {\n disposals: [],\n owner: currentOwner, // parent owner for ownership tree\n children: [], // child roots (ownership tree)\n _disposed: false,\n };\n\n // Register this root as a child of the parent owner\n if (currentOwner) {\n currentOwner.children.push(root);\n }\n\n currentRoot = root;\n currentOwner = root;\n\n try {\n const dispose = () => {\n if (root._disposed) return;\n root._disposed = true;\n\n // Dispose children first (depth-first, reverse order)\n for (let i = root.children.length - 1; i >= 0; i--) {\n _disposeRoot(root.children[i]);\n }\n root.children.length = 0;\n\n // Dispose own effects (reverse order for LIFO cleanup)\n for (let i = root.disposals.length - 1; i >= 0; i--) {\n root.disposals[i]();\n }\n root.disposals.length = 0;\n\n // Remove from parent's children list\n if (root.owner) {\n const idx = root.owner.children.indexOf(root);\n if (idx >= 0) root.owner.children.splice(idx, 1);\n }\n };\n return fn(dispose);\n } finally {\n currentRoot = prevRoot;\n currentOwner = prevOwner;\n }\n}\n\n// Internal: dispose a root and all its children\nfunction _disposeRoot(root) {\n if (root._disposed) return;\n root._disposed = true;\n\n // Dispose children first\n for (let i = root.children.length - 1; i >= 0; i--) {\n _disposeRoot(root.children[i]);\n }\n root.children.length = 0;\n\n // Dispose own effects\n for (let i = root.disposals.length - 1; i >= 0; i--) {\n root.disposals[i]();\n }\n root.disposals.length = 0;\n}\n\n// --- onCleanup ---\n// Register a cleanup function with the current owner/root.\n// Runs when the owner is disposed.\nexport function onCleanup(fn) {\n if (currentRoot) {\n currentRoot.disposals.push(fn);\n }\n}\n"],
5
- "mappings": ";AAUO,IAAM,UAAU,OAAO,YAAY,cACtC,OACA;AAIG,IAAI,aAAa;AAGjB,SAAS,mBAAmB,OAAO;AACxC,MAAI,QAAS,cAAa;AAC5B;",
6
- "names": []
7
- }
@@ -1,2 +0,0 @@
1
- var n=!(typeof process<"u"),t=null;function i(e){n&&(t=e)}export{i as __setDevToolsHooks};
2
- //# sourceMappingURL=devtools.min.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/reactive.js"],
4
- "sourcesContent": ["// What Framework - Reactive Primitives\n// Signals + Effects: fine-grained reactivity without virtual DOM overhead\n//\n// Upgrades:\n// - Topological ordering: computed/effects sorted by _level to prevent diamond glitches\n// - Iterative computed evaluation: no recursion, handles 10K+ depth chains\n// - Ownership tree: createRoot children auto-dispose when parent disposes\n// - Performance: cached levels, lazy sort, fast-path notify, minimal allocation\n\n// Dev-mode flag \u2014 build tools can dead-code-eliminate when false\nexport const __DEV__ = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n// DevTools hooks \u2014 set by what-devtools when installed.\n// These are no-ops in production (dead-code eliminated with __DEV__).\nexport let __devtools = null;\n\n/** @internal Install devtools hooks. Called by what-devtools. */\nexport function __setDevToolsHooks(hooks) {\n if (__DEV__) __devtools = hooks;\n}\n\nlet currentEffect = null;\nlet currentRoot = null;\nlet currentOwner = null; // Ownership tree: tracks current owner context\nlet insideComputed = false; // Track whether we're inside a computed() callback (dev-mode warning)\nlet batchDepth = 0;\nlet pendingEffects = [];\nlet pendingNeedSort = false; // Track whether pendingEffects actually needs sorting\n\n// WeakMap: subscriber Set \u2192 owning computed's inner effect (null/absent for signals)\n// Used for topological level computation.\nconst subSetOwner = new WeakMap();\n\n// --- Iterative Computed Evaluation State ---\n// Uses a throw/catch trampoline to convert recursive computed evaluation\n// to iterative. When a computed fn() reads another dirty computed, instead\n// of recursing, we throw a sentinel that gets caught by the outer loop.\nconst NEEDS_UPSTREAM = Symbol('needs_upstream');\nlet iterativeEvalStack = null; // array when inside evaluation loop, null otherwise\n\n// --- Signal ---\n// A reactive value. Reading inside an effect auto-tracks the dependency.\n// Writing triggers only the effects that depend on this signal.\n\nexport function signal(initial, debugName) {\n let value = initial;\n const subs = new Set();\n\n // Unified getter/setter: sig() reads, sig(newVal) writes\n function sig(...args) {\n if (args.length === 0) {\n // Read\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n return value;\n }\n // Write\n if (__DEV__ && insideComputed) {\n console.warn(\n '[what] Signal.set() called inside a computed function. ' +\n 'This may cause infinite loops. Use effect() instead.' +\n (debugName ? ` (signal: ${debugName})` : '')\n );\n }\n const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];\n if (Object.is(value, nextVal)) return;\n value = nextVal;\n if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);\n if (subs.size > 0) notify(subs);\n }\n\n sig.set = (next) => {\n if (__DEV__ && insideComputed) {\n console.warn(\n '[what] Signal.set() called inside a computed function. ' +\n 'This may cause infinite loops. Use effect() instead.' +\n (debugName ? ` (signal: ${debugName})` : '')\n );\n }\n const nextVal = typeof next === 'function' ? next(value) : next;\n if (Object.is(value, nextVal)) return;\n value = nextVal;\n if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);\n if (subs.size > 0) notify(subs);\n };\n\n sig.peek = () => value;\n\n sig.subscribe = (fn) => {\n return effect(() => fn(sig()));\n };\n\n sig._signal = true;\n if (__DEV__) {\n sig._subs = subs;\n if (debugName) sig._debugName = debugName;\n }\n\n // Notify devtools of signal creation\n if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);\n\n return sig;\n}\n\n// --- Computed ---\n// Derived signal. Lazy: only recomputes when a dependency changes AND it's read.\n// Topological level: max(dependency levels) + 1, computed from source signals (level 0).\n\nexport function computed(fn) {\n let value, dirty = true;\n const subs = new Set();\n\n const inner = _createEffect(() => {\n const prevInsideComputed = insideComputed;\n if (__DEV__) insideComputed = true;\n try {\n value = fn();\n dirty = false;\n } finally {\n if (__DEV__) insideComputed = prevInsideComputed;\n }\n }, true);\n\n // Computed nodes start at level 1. Updated when graph structure changes.\n inner._level = 1;\n inner._computed = true;\n inner._computedSubs = subs;\n\n // Register this subscriber set as owned by this computed\n subSetOwner.set(subs, inner);\n\n // Store markDirty/isDirty closures on the inner effect for iterative eval\n inner._markDirty = () => { dirty = true; };\n inner._isDirty = () => dirty;\n\n function read() {\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n if (dirty) _evaluateComputed(inner);\n return value;\n }\n\n // When a dependency changes, mark dirty AND propagate to our subscribers.\n inner._onNotify = () => {\n dirty = true;\n if (subs.size > 0) notify(subs);\n };\n\n read._signal = true;\n read.peek = () => {\n if (dirty) _evaluateComputed(inner);\n return value;\n };\n\n return read;\n}\n\n// --- Iterative Computed Evaluation ---\n//\n// Problem: A chain of N dirty computeds causes O(N) recursive calls:\n// C_N.read() \u2192 eval \u2192 fn() \u2192 C_{N-1}.read() \u2192 eval \u2192 fn() \u2192 ... \u2192 C_1.read() \u2192 eval \u2192 fn()\n// This overflows the stack at ~3500 depth.\n//\n// Solution: Throw/catch trampoline. The outermost _evaluateComputed manages a\n// stack (array). When a computed's fn() reads another dirty computed during\n// evaluation, _evaluateComputed throws NEEDS_UPSTREAM. The outer loop catches\n// this, adds the upstream to the stack, and processes from the bottom up.\n// This converts O(N) call depth to O(1) per computed (just the outermost loop).\n\nfunction _evaluateComputed(computedEffect) {\n if (iterativeEvalStack !== null) {\n // We're inside the outermost evaluation loop, and a computed's fn()\n // is reading another dirty computed. Push it onto the stack and throw\n // to abort the current fn() so the outer loop can process it first.\n iterativeEvalStack.push(computedEffect);\n throw NEEDS_UPSTREAM;\n }\n\n // Outermost call \u2014 enter the iterative evaluation loop.\n // The stack grows as we discover dirty upstream computeds.\n const stack = [computedEffect];\n iterativeEvalStack = stack;\n\n try {\n while (stack.length > 0) {\n const current = stack[stack.length - 1];\n\n if (!current._isDirty || !current._isDirty()) {\n // Already clean \u2014 pop and continue\n stack.pop();\n continue;\n }\n\n // Pre-scan known deps: if any are dirty computeds, push them onto\n // the stack first (bottom-up). This avoids the O(N^2) worst case\n // where throw/catch restarts from the top on each dirty upstream.\n let pushedUpstream = false;\n const deps = current.deps;\n for (let i = 0; i < deps.length; i++) {\n const depOwner = subSetOwner.get(deps[i]);\n if (depOwner && depOwner._computed && depOwner._isDirty && depOwner._isDirty()) {\n stack.push(depOwner);\n pushedUpstream = true;\n }\n }\n if (pushedUpstream) {\n // Process dirty upstreams first before re-evaluating current\n continue;\n }\n\n // All known deps are clean \u2014 evaluate. throw/catch is fallback\n // for newly-discovered deps only.\n try {\n const prevDepsLen = current.deps.length;\n _runEffect(current);\n // Only recompute level when graph structure changes\n if (current.deps.length !== prevDepsLen) {\n _updateLevel(current);\n }\n stack.pop(); // Successfully evaluated\n } catch (err) {\n if (err === NEEDS_UPSTREAM) {\n // A dirty upstream was discovered and pushed onto the stack.\n // Re-mark this computed dirty since its fn() was aborted mid-execution.\n current._markDirty();\n // The upstream is now at stack[stack.length-1]. Loop continues.\n } else {\n throw err; // Re-throw real errors\n }\n }\n }\n } finally {\n iterativeEvalStack = null;\n }\n}\n\n// Update the topological level of a computed/effect based on its current dependencies.\nfunction _updateLevel(e) {\n let maxDepLevel = 0;\n const deps = e.deps;\n for (let i = 0; i < deps.length; i++) {\n const owner = subSetOwner.get(deps[i]);\n if (owner) {\n const depLevel = owner._level;\n if (depLevel > maxDepLevel) maxDepLevel = depLevel;\n }\n }\n e._level = maxDepLevel + 1;\n}\n\n// --- Effect ---\n// Runs a function, auto-tracking signal reads. Re-runs when deps change.\n// Returns a dispose function.\n\nexport function effect(fn, opts) {\n const e = _createEffect(fn);\n e._level = 1;\n // First run: skip cleanup (deps is empty), just run and track\n const prev = currentEffect;\n currentEffect = e;\n try {\n const result = e.fn();\n if (typeof result === 'function') e._cleanup = result;\n } finally {\n currentEffect = prev;\n }\n // Compute level after first run based on actual dependencies (cached).\n _updateLevel(e);\n // Mark as stable after first run \u2014 subsequent re-runs skip cleanup/re-subscribe\n if (opts?.stable) e._stable = true;\n const dispose = () => _disposeEffect(e);\n // Register with current root for automatic cleanup\n if (currentRoot) {\n currentRoot.disposals.push(dispose);\n }\n return dispose;\n}\n\n// --- Batch ---\n// Group multiple signal writes; effects run once at the end.\n\nexport function batch(fn) {\n batchDepth++;\n try {\n fn();\n } finally {\n batchDepth--;\n if (batchDepth === 0) flush();\n }\n}\n\n// --- Internals ---\n\nfunction _createEffect(fn, lazy) {\n // Minimal object shape \u2014 computed() adds extra properties after creation.\n // Keeping the base object small helps V8 optimize for the common (effect) case.\n const e = {\n fn,\n deps: [], // array of subscriber sets (cheaper than Set for typical 1-3 deps)\n lazy: lazy || false,\n _onNotify: null,\n disposed: false,\n _pending: false,\n _stable: false, // stable effects skip cleanup/re-subscribe on re-run\n _level: 0, // topological depth: signals=0, computed/effects=max(deps)+1\n _computed: false, // true for computed inner effects\n _computedSubs: null, // reference to the computed's subscriber set\n _isDirty: null, // function to check if computed is dirty (set by computed())\n _markDirty: null, // function to mark computed dirty (set by computed())\n };\n if (__DEV__ && __devtools) __devtools.onEffectCreate(e);\n return e;\n}\n\nfunction _runEffect(e) {\n if (e.disposed) return;\n\n // Stable effect fast path: deps don't change, skip cleanup/re-subscribe.\n if (e._stable) {\n runStableEffect(e);\n return;\n }\n\n cleanup(e);\n // Run effect cleanup from previous run\n runEffectCleanup(e, 'effect cleanup');\n const prev = currentEffect;\n currentEffect = e;\n try {\n const result = e.fn();\n // Capture cleanup function if returned\n if (typeof result === 'function') {\n e._cleanup = result;\n }\n } catch (err) {\n if (err === NEEDS_UPSTREAM) throw err; // Iterative eval sentinel \u2014 not a real error\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });\n throw err;\n } finally {\n currentEffect = prev;\n }\n if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);\n}\n\nfunction _disposeEffect(e) {\n e.disposed = true;\n if (__DEV__ && __devtools) __devtools.onEffectDispose(e);\n cleanup(e);\n // Run cleanup on dispose\n runEffectCleanup(e, 'effect cleanup on dispose');\n}\n\nfunction reportEffectCleanupError(err, e, phase) {\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e, phase });\n if (__DEV__) console.warn(`[what] Error in ${phase}:`, err);\n}\n\nfunction runEffectCleanup(e, phase) {\n if (!e._cleanup) return;\n const cleanupFn = e._cleanup;\n e._cleanup = null;\n try {\n cleanupFn();\n } catch (err) {\n reportEffectCleanupError(err, e, phase);\n }\n}\n\nfunction runStableEffect(e) {\n const prev = currentEffect;\n currentEffect = null; // Don't re-track deps (already subscribed)\n try {\n runEffectCleanup(e, 'stable effect cleanup');\n const result = e.fn();\n if (typeof result === 'function') e._cleanup = result;\n } catch (err) {\n if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });\n if (__DEV__) console.warn('[what] Error in stable effect:', err);\n } finally {\n currentEffect = prev;\n }\n if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);\n}\n\nfunction cleanup(e) {\n const deps = e.deps;\n for (let i = 0; i < deps.length; i++) deps[i].delete(e);\n deps.length = 0;\n}\n\n// --- Notification ---\n// Iterative notification to prevent stack overflow on deep computed chains.\n// Uses a reusable queue to avoid per-call array allocation.\n// When notify() encounters _onNotify callbacks (from computeds), those may\n// call notify() recursively. The queue drains iteratively in the outermost call.\n\nlet notifyDepth = 0; // Tracks recursive notify depth\nlet notifyQueue = null; // Reusable queue, allocated on first recursive call\nlet notifyQueueLen = 0; // Length of the queue\n\nfunction notify(subs) {\n // Fast path: no recursive notifications in progress \u2014 iterate directly.\n // This avoids array allocation for the common case (signal \u2192 effects).\n if (notifyDepth === 0) {\n notifyDepth = 1;\n try {\n for (const e of subs) {\n if (e.disposed) continue;\n if (e._onNotify) {\n // Computed subscriber: mark dirty and propagate.\n // _onNotify may call notify() recursively \u2014 tracked by notifyDepth.\n e._onNotify();\n } else if (batchDepth === 0 && e._stable) {\n runStableEffect(e);\n } else if (!e._pending) {\n e._pending = true;\n const level = e._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(e);\n }\n }\n // Drain any queued subscriber sets from recursive notify calls\n if (notifyQueueLen > 0) {\n let qi = 0;\n while (qi < notifyQueueLen) {\n const queuedSubs = notifyQueue[qi];\n notifyQueue[qi] = null; // Allow GC\n qi++;\n for (const e of queuedSubs) {\n if (e.disposed) continue;\n if (e._onNotify) {\n e._onNotify();\n } else if (batchDepth === 0 && e._stable) {\n runStableEffect(e);\n } else if (!e._pending) {\n e._pending = true;\n const level = e._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(e);\n }\n }\n }\n notifyQueueLen = 0;\n }\n } finally {\n notifyDepth = 0;\n }\n if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();\n } else {\n // Recursive call \u2014 queue the subscriber set for the outermost call to drain.\n if (notifyQueue === null) notifyQueue = [];\n if (notifyQueueLen >= notifyQueue.length) {\n notifyQueue.push(subs);\n } else {\n notifyQueue[notifyQueueLen] = subs;\n }\n notifyQueueLen++;\n }\n}\n\nlet microtaskScheduled = false;\nfunction scheduleMicrotask() {\n if (!microtaskScheduled) {\n microtaskScheduled = true;\n queueMicrotask(() => {\n microtaskScheduled = false;\n flush();\n });\n }\n}\n\nlet isFlushing = false;\n\nfunction flush() {\n // Re-entrancy guard: if flush() is called during an active flush (e.g., via\n // flushSync() inside a component render or effect), skip to prevent infinite\n // recursion. Pending effects will be picked up by the outer flush's while-loop.\n if (isFlushing) return;\n isFlushing = true;\n\n try {\n let iterations = 0;\n while (pendingEffects.length > 0 && iterations < 25) {\n const batch = pendingEffects;\n pendingEffects = [];\n\n // Topological sort: execute effects in level order (lowest first).\n // Fast paths:\n // 1. Single effect \u2014 no sort needed (most common case for microtask flush)\n // 2. Already sorted \u2014 skip sort (common when effects added in level order)\n // 3. Multiple effects at different levels \u2014 sort required\n if (batch.length > 1 && pendingNeedSort) {\n batch.sort((a, b) => a._level - b._level);\n }\n pendingNeedSort = false;\n\n for (let i = 0; i < batch.length; i++) {\n const e = batch[i];\n e._pending = false;\n if (!e.disposed && !e._onNotify) {\n const prevDepsLen = e.deps.length;\n _runEffect(e);\n // Update level only if deps changed (graph structure change)\n if (!e._computed && e.deps.length !== prevDepsLen) {\n _updateLevel(e);\n }\n }\n }\n iterations++;\n }\n if (iterations >= 25) {\n // Clear pending effects to prevent further damage\n for (let i = 0; i < pendingEffects.length; i++) pendingEffects[i]._pending = false;\n pendingEffects.length = 0;\n\n if (__DEV__) {\n const remaining = pendingEffects.slice(0, 3);\n const effectNames = remaining.map(e => e.fn?.name || e.fn?.toString().slice(0, 60) || '(anonymous)');\n console.warn(\n `[what] Possible infinite effect loop detected (25 iterations). ` +\n `Likely cause: an effect writes to a signal it also reads, creating a cycle. ` +\n `Use untrack() to read signals without subscribing. ` +\n `Looping effects: ${effectNames.join(', ')}`\n );\n } else {\n console.warn('[what] Possible infinite effect loop detected');\n }\n }\n } finally {\n isFlushing = false;\n }\n}\n\n// --- Memo ---\n// Eager computed that only propagates when the value actually changes.\n// Fix: Instead of calling notify(subs) inline (which bypasses topological sort\n// and causes diamond-dependency glitches), push memo subscribers into\n// pendingEffects and let them go through the sorted flush() path.\nexport function memo(fn) {\n let value;\n const subs = new Set();\n\n const e = _createEffect(() => {\n const next = fn();\n if (!Object.is(value, next)) {\n value = next;\n // Push subscribers into pendingEffects for topological flush\n // instead of inline notify() which can cause diamond glitches\n for (const sub of subs) {\n if (sub.disposed) continue;\n if (sub._onNotify) {\n // Computed subscriber: mark dirty and propagate\n sub._onNotify();\n } else if (!sub._pending) {\n sub._pending = true;\n const level = sub._level;\n const len = pendingEffects.length;\n if (len > 0 && pendingEffects[len - 1]._level > level) {\n pendingNeedSort = true;\n }\n pendingEffects.push(sub);\n }\n }\n }\n });\n\n e._level = 1;\n\n _runEffect(e);\n _updateLevel(e);\n\n // Register subscriber set owner for level tracking\n subSetOwner.set(subs, e);\n\n // Register with current root\n if (currentRoot) {\n currentRoot.disposals.push(() => _disposeEffect(e));\n }\n\n function read() {\n if (currentEffect) {\n subs.add(currentEffect);\n currentEffect.deps.push(subs);\n }\n return value;\n }\n\n read._signal = true;\n read.peek = () => value;\n return read;\n}\n\n// --- flushSync ---\n// Force all pending effects to run synchronously. Use sparingly.\n// Calling during render or effect execution is a no-op (prevents infinite loops).\nexport function flushSync() {\n if (isFlushing) {\n // Re-entrant call \u2014 silently skip (Solid approach).\n // This prevents infinite loops when flushSync() is called during component\n // render or effect execution. Pending effects will be picked up by the\n // outer flush's while-loop.\n if (__DEV__) {\n console.warn(\n '[what] flushSync() called during an active flush (e.g., inside a component render or effect). ' +\n 'This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback.'\n );\n }\n return;\n }\n if (currentEffect) {\n // Called inside an effect/render \u2014 skip with warning\n if (__DEV__) {\n console.warn(\n '[what] flushSync() called during effect execution. ' +\n 'This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback.'\n );\n }\n return;\n }\n microtaskScheduled = false;\n flush();\n}\n\n// --- Untrack ---\n// Read signals without subscribing\nexport function untrack(fn) {\n const prev = currentEffect;\n currentEffect = null;\n try {\n return fn();\n } finally {\n currentEffect = prev;\n }\n}\n\n// --- getOwner / runWithOwner ---\n// Expose ownership context for advanced use cases (e.g., async operations\n// that need to register disposals with the correct owner).\n\nexport function getOwner() {\n return currentOwner;\n}\n\nexport function runWithOwner(owner, fn) {\n const prev = currentOwner;\n const prevRoot = currentRoot;\n currentOwner = owner;\n currentRoot = owner;\n try {\n return fn();\n } finally {\n currentOwner = prev;\n currentRoot = prevRoot;\n }\n}\n\n// --- createRoot ---\n// Isolated reactive scope with ownership tree.\n// All effects created inside are tracked and disposed together.\n// Child createRoot scopes register with parent owner \u2014 disposing parent\n// automatically disposes all children (prevents orphaned subscriptions).\nexport function createRoot(fn) {\n const prevRoot = currentRoot;\n const prevOwner = currentOwner;\n const root = {\n disposals: [],\n owner: currentOwner, // parent owner for ownership tree\n children: [], // child roots (ownership tree)\n _disposed: false,\n };\n\n // Register this root as a child of the parent owner\n if (currentOwner) {\n currentOwner.children.push(root);\n }\n\n currentRoot = root;\n currentOwner = root;\n\n try {\n const dispose = () => {\n if (root._disposed) return;\n root._disposed = true;\n\n // Dispose children first (depth-first, reverse order)\n for (let i = root.children.length - 1; i >= 0; i--) {\n _disposeRoot(root.children[i]);\n }\n root.children.length = 0;\n\n // Dispose own effects (reverse order for LIFO cleanup)\n for (let i = root.disposals.length - 1; i >= 0; i--) {\n root.disposals[i]();\n }\n root.disposals.length = 0;\n\n // Remove from parent's children list\n if (root.owner) {\n const idx = root.owner.children.indexOf(root);\n if (idx >= 0) root.owner.children.splice(idx, 1);\n }\n };\n return fn(dispose);\n } finally {\n currentRoot = prevRoot;\n currentOwner = prevOwner;\n }\n}\n\n// Internal: dispose a root and all its children\nfunction _disposeRoot(root) {\n if (root._disposed) return;\n root._disposed = true;\n\n // Dispose children first\n for (let i = root.children.length - 1; i >= 0; i--) {\n _disposeRoot(root.children[i]);\n }\n root.children.length = 0;\n\n // Dispose own effects\n for (let i = root.disposals.length - 1; i >= 0; i--) {\n root.disposals[i]();\n }\n root.disposals.length = 0;\n}\n\n// --- onCleanup ---\n// Register a cleanup function with the current owner/root.\n// Runs when the owner is disposed.\nexport function onCleanup(fn) {\n if (currentRoot) {\n currentRoot.disposals.push(fn);\n }\n}\n"],
5
- "mappings": "AAUO,IAAMA,EAAU,SAAO,QAAY,KAM/BC,EAAa,KAGjB,SAASC,EAAmBC,EAAO,CACpCH,IAASC,EAAaE,EAC5B",
6
- "names": ["__DEV__", "__devtools", "__setDevToolsHooks", "hooks"]
7
- }
package/dist/dom.js DELETED
@@ -1,439 +0,0 @@
1
- import { effect, batch, untrack, signal, __DEV__, __devtools } from './reactive.js';
2
- import { reportError, _injectGetCurrentComponent, shallowEqual } from './components.js';
3
- import { _setComponentRef } from './helpers.js';
4
- const SVG_ELEMENTS = new Set([
5
- 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
6
- 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern', 'image',
7
- 'text', 'tspan', 'textPath', 'foreignObject', 'linearGradient', 'radialGradient', 'stop',
8
- 'marker', 'animate', 'animateTransform', 'animateMotion', 'set', 'filter',
9
- 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix',
10
- 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage',
11
- 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting',
12
- 'feTile', 'feTurbulence',
13
- ]);
14
- const SVG_NS = 'http://www.w3.org/2000/svg';
15
- const mountedComponents = new Set();
16
- function isDomNode(value) {
17
- if (!value || typeof value !== 'object') return false;
18
- if (typeof Node !== 'undefined' && value instanceof Node) return true;
19
- return typeof value.nodeType === 'number' && typeof value.nodeName === 'string';
20
- }
21
- function isVNode(value) {
22
- return !!value && typeof value === 'object' && (value._vnode === true || 'tag' in value);
23
- }
24
- function disposeComponent(ctx) {
25
- if (ctx.disposed) return;
26
- ctx.disposed = true;
27
- if (ctx.cleanups) {
28
- for (const cleanup of ctx.cleanups) {
29
- try { cleanup(); } catch (e) { console.error('[what] cleanup error:', e); }
30
- }
31
- }
32
- if (ctx.effects) {
33
- for (const dispose of ctx.effects) {
34
- try { dispose(); } catch (e) { }
35
- }
36
- }
37
- if (ctx.hooks) {
38
- for (const hook of ctx.hooks) {
39
- if (hook && typeof hook.cleanup === 'function') {
40
- try { hook.cleanup(); } catch (e) { console.error('[what] hook cleanup error:', e); }
41
- }
42
- }
43
- }
44
- if (ctx._cleanupCallbacks) {
45
- for (const fn of ctx._cleanupCallbacks) {
46
- try { fn(); } catch (e) { console.error('[what] onCleanup error:', e); }
47
- }
48
- }
49
- if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount(ctx);
50
- mountedComponents.delete(ctx);
51
- }
52
- export function disposeTree(node) {
53
- if (!node) return;
54
- if (node._componentCtx) {
55
- disposeComponent(node._componentCtx);
56
- }
57
- if (node._dispose) {
58
- try { node._dispose(); } catch (e) { }
59
- }
60
- if (node._propEffects) {
61
- for (const key in node._propEffects) {
62
- try { node._propEffects[key](); } catch (e) { }
63
- }
64
- }
65
- if (node.childNodes) {
66
- for (const child of node.childNodes) {
67
- disposeTree(child);
68
- }
69
- }
70
- }
71
- export function mount(vnode, container) {
72
- if (typeof container === 'string') {
73
- container = document.querySelector(container);
74
- }
75
- disposeTree(container);
76
- container.textContent = '';
77
- const node = createDOM(vnode, container);
78
- if (node) container.appendChild(node);
79
- return () => {
80
- disposeTree(container);
81
- container.textContent = '';
82
- };
83
- }
84
- export function createDOM(vnode, parent, isSvg) {
85
- if (vnode == null || vnode === false || vnode === true) {
86
- return document.createComment('');
87
- }
88
- if (typeof vnode === 'string' || typeof vnode === 'number') {
89
- return document.createTextNode(String(vnode));
90
- }
91
- if (isDomNode(vnode)) {
92
- return vnode;
93
- }
94
- if (typeof vnode === 'function') {
95
- const container = document.createDocumentFragment ? document.createElement('span') : document.createElement('span');
96
- container.style.display = 'contents';
97
- let currentNodes = [];
98
- const dispose = effect(() => {
99
- const val = vnode();
100
- const vnodes = (val == null || val === false || val === true)
101
- ? []
102
- : Array.isArray(val) ? val : [val];
103
- for (const old of currentNodes) {
104
- disposeTree(old);
105
- if (old.parentNode === container) container.removeChild(old);
106
- }
107
- currentNodes = [];
108
- for (const v of vnodes) {
109
- const node = createDOM(v, container, parent?._isSvg);
110
- if (node) {
111
- container.appendChild(node);
112
- currentNodes.push(node);
113
- }
114
- }
115
- });
116
- container._dispose = dispose;
117
- return container;
118
- }
119
- if (Array.isArray(vnode)) {
120
- const frag = document.createDocumentFragment();
121
- for (const child of vnode) {
122
- const node = createDOM(child, parent, isSvg);
123
- if (node) frag.appendChild(node);
124
- }
125
- return frag;
126
- }
127
- if (isVNode(vnode) && typeof vnode.tag === 'function') {
128
- return createComponent(vnode, parent, isSvg);
129
- }
130
- if (isVNode(vnode)) {
131
- return createElementFromVNode(vnode, parent, isSvg);
132
- }
133
- return document.createTextNode(String(vnode));
134
- }
135
- const componentStack = [];
136
- export function getCurrentComponent() {
137
- return componentStack[componentStack.length - 1];
138
- }
139
- _injectGetCurrentComponent(getCurrentComponent);
140
- _setComponentRef(getCurrentComponent);
141
- export function getComponentStack() {
142
- return componentStack;
143
- }
144
- function createComponent(vnode, parent, isSvg) {
145
- let { tag: Component, props, children } = vnode;
146
- if (typeof Component === 'function' &&
147
- (Component.prototype?.isReactComponent || Component.prototype?.render)) {
148
- const ClassComp = Component;
149
- Component = function ClassComponentBridge(props) {
150
- const instance = new ClassComp(props);
151
- return instance.render();
152
- };
153
- Component.displayName = ClassComp.displayName || ClassComp.name || 'ClassComponent';
154
- }
155
- if (Component === '__errorBoundary' || vnode.tag === '__errorBoundary') {
156
- return createErrorBoundary(vnode, parent);
157
- }
158
- if (Component === '__suspense' || vnode.tag === '__suspense') {
159
- return createSuspenseBoundary(vnode, parent);
160
- }
161
- if (Component === '__portal' || vnode.tag === '__portal') {
162
- return createPortalDOM(vnode, parent);
163
- }
164
- const ctx = {
165
- hooks: [],
166
- hookIndex: 0,
167
- effects: [],
168
- cleanups: [],
169
- mounted: false,
170
- disposed: false,
171
- Component,
172
- _parentCtx: componentStack[componentStack.length - 1] || null,
173
- _errorBoundary: (() => {
174
- let p = componentStack[componentStack.length - 1];
175
- while (p) {
176
- if (p._errorBoundary) return p._errorBoundary;
177
- p = p._parentCtx;
178
- }
179
- return null;
180
- })()
181
- };
182
- const container = document.createElement('span');
183
- container.style.display = 'contents';
184
- container._componentCtx = ctx;
185
- container._isSvg = !!isSvg;
186
- ctx._wrapper = container;
187
- mountedComponents.add(ctx);
188
- if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount(ctx);
189
- const propsChildren = children.length === 0 ? undefined : children.length === 1 ? children[0] : children;
190
- const propsSignal = signal({ ...props, children: propsChildren });
191
- ctx._propsSignal = propsSignal;
192
- componentStack.push(ctx);
193
- let result;
194
- try {
195
- result = Component(propsSignal());
196
- } catch (error) {
197
- componentStack.pop();
198
- if (!reportError(error, ctx)) {
199
- console.error('[what] Uncaught error in component:', Component.name || 'Anonymous', error);
200
- throw error;
201
- }
202
- return container;
203
- }
204
- componentStack.pop();
205
- ctx.mounted = true;
206
- if (ctx._mountCallbacks) {
207
- queueMicrotask(() => {
208
- if (ctx.disposed) return;
209
- for (const fn of ctx._mountCallbacks) {
210
- try { fn(); } catch (e) { console.error('[what] onMount error:', e); }
211
- }
212
- });
213
- }
214
- const vnodes = Array.isArray(result) ? result : [result];
215
- for (const v of vnodes) {
216
- const node = createDOM(v, container, isSvg);
217
- if (node) container.appendChild(node);
218
- }
219
- container._vnode = vnode;
220
- return container;
221
- }
222
- function createErrorBoundary(vnode, parent) {
223
- const { errorState, handleError, fallback, reset } = vnode.props;
224
- const children = vnode.children;
225
- const wrapper = document.createElement('span');
226
- wrapper.style.display = 'contents';
227
- const boundaryCtx = {
228
- hooks: [], hookIndex: 0, effects: [], cleanups: [],
229
- mounted: false, disposed: false,
230
- _parentCtx: componentStack[componentStack.length - 1] || null,
231
- _errorBoundary: handleError,
232
- };
233
- wrapper._componentCtx = boundaryCtx;
234
- const dispose = effect(() => {
235
- const error = errorState();
236
- componentStack.push(boundaryCtx);
237
- while (wrapper.firstChild) {
238
- disposeTree(wrapper.firstChild);
239
- wrapper.removeChild(wrapper.firstChild);
240
- }
241
- let vnodes;
242
- if (error) {
243
- vnodes = typeof fallback === 'function' ? [fallback({ error, reset })] : [fallback];
244
- } else {
245
- vnodes = children;
246
- }
247
- vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
248
- for (const v of vnodes) {
249
- const node = createDOM(v, wrapper);
250
- if (node) wrapper.appendChild(node);
251
- }
252
- componentStack.pop();
253
- });
254
- boundaryCtx.effects.push(dispose);
255
- return wrapper;
256
- }
257
- function createSuspenseBoundary(vnode, parent) {
258
- const { boundary, fallback, loading } = vnode.props;
259
- const children = vnode.children;
260
- const wrapper = document.createElement('span');
261
- wrapper.style.display = 'contents';
262
- const boundaryCtx = {
263
- hooks: [], hookIndex: 0, effects: [], cleanups: [],
264
- mounted: false, disposed: false,
265
- _parentCtx: componentStack[componentStack.length - 1] || null,
266
- };
267
- wrapper._componentCtx = boundaryCtx;
268
- const dispose = effect(() => {
269
- const isLoading = loading();
270
- const vnodes = isLoading ? [fallback] : children;
271
- const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
272
- componentStack.push(boundaryCtx);
273
- while (wrapper.firstChild) {
274
- disposeTree(wrapper.firstChild);
275
- wrapper.removeChild(wrapper.firstChild);
276
- }
277
- for (const v of normalized) {
278
- const node = createDOM(v, wrapper);
279
- if (node) wrapper.appendChild(node);
280
- }
281
- componentStack.pop();
282
- });
283
- boundaryCtx.effects.push(dispose);
284
- return wrapper;
285
- }
286
- function createPortalDOM(vnode, parent) {
287
- const { container } = vnode.props;
288
- const children = vnode.children;
289
- if (!container) {
290
- console.warn('[what] Portal: target container not found');
291
- return document.createComment('portal:empty');
292
- }
293
- const portalCtx = {
294
- hooks: [], hookIndex: 0, effects: [], cleanups: [],
295
- mounted: false, disposed: false,
296
- _parentCtx: componentStack[componentStack.length - 1] || null,
297
- };
298
- const placeholder = document.createComment('portal');
299
- placeholder._componentCtx = portalCtx;
300
- const portalNodes = [];
301
- for (const child of children) {
302
- const node = createDOM(child, container);
303
- if (node) {
304
- container.appendChild(node);
305
- portalNodes.push(node);
306
- }
307
- }
308
- portalCtx._cleanupCallbacks = [() => {
309
- for (const node of portalNodes) {
310
- disposeTree(node);
311
- if (node.parentNode) node.parentNode.removeChild(node);
312
- }
313
- }];
314
- return placeholder;
315
- }
316
- function createElementFromVNode(vnode, parent, isSvg) {
317
- const { tag, props, children } = vnode;
318
- const svgContext = isSvg || SVG_ELEMENTS.has(tag);
319
- const el = svgContext
320
- ? document.createElementNS(SVG_NS, tag)
321
- : document.createElement(tag);
322
- if (props) {
323
- applyProps(el, props, {}, svgContext);
324
- }
325
- for (const child of children) {
326
- const node = createDOM(child, el, svgContext && tag !== 'foreignObject');
327
- if (node) el.appendChild(node);
328
- }
329
- el._vnode = vnode;
330
- return el;
331
- }
332
- function applyProps(el, newProps, oldProps, isSvg) {
333
- newProps = newProps || {};
334
- oldProps = oldProps || {};
335
- for (const key in newProps) {
336
- if (key === 'key' || key === 'children') continue;
337
- if (key === 'ref') {
338
- if (typeof newProps.ref === 'function') newProps.ref(el);
339
- else if (newProps.ref) newProps.ref.current = el;
340
- continue;
341
- }
342
- setProp(el, key, newProps[key], isSvg);
343
- }
344
- }
345
- function setProp(el, key, value, isSvg) {
346
- if (typeof value === 'function' && !(key.startsWith('on') && key.length > 2) && key !== 'ref') {
347
- if (!el._propEffects) el._propEffects = {};
348
- if (el._propEffects[key]) {
349
- try { el._propEffects[key](); } catch (e) { }
350
- }
351
- el._propEffects[key] = effect(() => {
352
- const resolved = value();
353
- setProp(el, key, resolved, isSvg);
354
- });
355
- return;
356
- }
357
- if (key.startsWith('on') && key.length > 2) {
358
- let eventName = key.slice(2);
359
- let useCapture = false;
360
- if (eventName.endsWith('Capture')) {
361
- eventName = eventName.slice(0, -7);
362
- useCapture = true;
363
- }
364
- const event = eventName.toLowerCase();
365
- const storageKey = useCapture ? event + '_capture' : event;
366
- const old = el._events?.[storageKey];
367
- if (old && old._original === value) return;
368
- if (old) el.removeEventListener(event, old, useCapture);
369
- if (value == null) return;
370
- if (!el._events) el._events = {};
371
- const wrappedHandler = (e) => {
372
- if (!e.nativeEvent) e.nativeEvent = e;
373
- return untrack(() => value(e));
374
- };
375
- wrappedHandler._original = value;
376
- el._events[storageKey] = wrappedHandler;
377
- const eventOpts = value._eventOpts;
378
- el.addEventListener(event, wrappedHandler, eventOpts || useCapture || undefined);
379
- return;
380
- }
381
- if (key === 'className' || key === 'class') {
382
- if (isSvg) {
383
- el.setAttribute('class', value || '');
384
- } else {
385
- el.className = value || '';
386
- }
387
- return;
388
- }
389
- if (key === 'style') {
390
- if (typeof value === 'string') {
391
- el.style.cssText = value;
392
- el._prevStyle = null;
393
- } else if (typeof value === 'object') {
394
- const oldStyle = el._prevStyle || {};
395
- for (const prop in oldStyle) {
396
- if (!(prop in value)) el.style[prop] = '';
397
- }
398
- for (const prop in value) {
399
- el.style[prop] = value[prop] ?? '';
400
- }
401
- el._prevStyle = { ...value };
402
- }
403
- return;
404
- }
405
- if (key === 'dangerouslySetInnerHTML') {
406
- el.innerHTML = value?.__html ?? '';
407
- return;
408
- }
409
- if (key === 'innerHTML') {
410
- if (value && typeof value === 'object' && '__html' in value) {
411
- el.innerHTML = value.__html ?? '';
412
- } else {
413
- el.innerHTML = value ?? '';
414
- }
415
- return;
416
- }
417
- if (typeof value === 'boolean') {
418
- if (value) el.setAttribute(key, '');
419
- else el.removeAttribute(key);
420
- return;
421
- }
422
- if (key.startsWith('data-') || key.startsWith('aria-')) {
423
- el.setAttribute(key, value);
424
- return;
425
- }
426
- if (isSvg) {
427
- if (value === false || value == null) {
428
- el.removeAttribute(key);
429
- } else {
430
- el.setAttribute(key, value === true ? '' : String(value));
431
- }
432
- return;
433
- }
434
- if (key in el) {
435
- el[key] = value;
436
- } else {
437
- el.setAttribute(key, value);
438
- }
439
- }