@elurjs/core 3.6.1 → 3.7.0-beta.0

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 (213) hide show
  1. package/dist/lib/async.cjs +1 -1
  2. package/dist/lib/async.js +1 -1
  3. package/dist/lib/component.cjs +1 -2
  4. package/dist/lib/component.js +2 -66
  5. package/dist/lib/context.cjs +1 -1
  6. package/dist/lib/context.js +35 -23
  7. package/dist/lib/devtools.cjs +1 -1
  8. package/dist/lib/devtools.js +1 -1
  9. package/dist/lib/elur/async.cjs +1 -1
  10. package/dist/lib/elur/async.cjs.map +1 -1
  11. package/dist/lib/elur/async.js +1 -1
  12. package/dist/lib/elur/component.cjs +1 -2
  13. package/dist/lib/elur/component.d.cts +1 -17
  14. package/dist/lib/elur/component.d.ts +1 -17
  15. package/dist/lib/elur/component.js +2 -66
  16. package/dist/lib/elur/context.cjs +1 -1
  17. package/dist/lib/elur/context.cjs.map +1 -1
  18. package/dist/lib/elur/context.d.cts +5 -0
  19. package/dist/lib/elur/context.d.ts +5 -0
  20. package/dist/lib/elur/context.js +35 -23
  21. package/dist/lib/elur/context.js.map +1 -1
  22. package/dist/lib/elur/devtools.cjs +1 -1
  23. package/dist/lib/elur/devtools.cjs.map +1 -1
  24. package/dist/lib/elur/devtools.js +1 -1
  25. package/dist/lib/elur/form.cjs +1 -1
  26. package/dist/lib/elur/form.cjs.map +1 -1
  27. package/dist/lib/elur/form.js +1 -1
  28. package/dist/lib/elur/hydrate/index.cjs +1 -2
  29. package/dist/lib/elur/hydrate/index.d.cts +1 -13
  30. package/dist/lib/elur/hydrate/index.d.ts +1 -13
  31. package/dist/lib/elur/hydrate/index.js +2 -410
  32. package/dist/lib/elur/lifecycle.cjs +1 -1
  33. package/dist/lib/elur/lifecycle.cjs.map +1 -1
  34. package/dist/lib/elur/lifecycle.d.cts +27 -0
  35. package/dist/lib/elur/lifecycle.d.ts +27 -0
  36. package/dist/lib/elur/lifecycle.js +4 -0
  37. package/dist/lib/elur/lifecycle.js.map +1 -1
  38. package/dist/lib/elur/next/component.d.cts +281 -0
  39. package/dist/lib/elur/next/component.d.ts +281 -0
  40. package/dist/lib/elur/next/dom-write.d.cts +18 -0
  41. package/dist/lib/elur/next/dom-write.d.ts +18 -0
  42. package/dist/lib/elur/next/hydrate.d.cts +14 -0
  43. package/dist/lib/elur/next/hydrate.d.ts +14 -0
  44. package/dist/lib/elur/next/keyed-diff.d.cts +46 -0
  45. package/dist/lib/elur/next/keyed-diff.d.ts +46 -0
  46. package/dist/lib/elur/next/mount-helpers.d.cts +25 -0
  47. package/dist/lib/elur/next/mount-helpers.d.ts +25 -0
  48. package/dist/lib/elur/next/mount.d.cts +29 -0
  49. package/dist/lib/elur/next/mount.d.ts +29 -0
  50. package/dist/lib/elur/next/node-binding.d.cts +6 -0
  51. package/dist/lib/elur/next/node-binding.d.ts +6 -0
  52. package/dist/lib/elur/next/reactivity.d.cts +193 -0
  53. package/dist/lib/elur/next/reactivity.d.ts +193 -0
  54. package/dist/lib/elur/next-2/component.cjs +2 -0
  55. package/dist/lib/elur/next-2/component.cjs.map +1 -0
  56. package/dist/lib/elur/next-2/component.d.cts +283 -0
  57. package/dist/lib/elur/next-2/component.d.ts +283 -0
  58. package/dist/lib/elur/next-2/component.js +340 -0
  59. package/dist/lib/elur/next-2/component.js.map +1 -0
  60. package/dist/lib/elur/next-2/dom-write.cjs +2 -0
  61. package/dist/lib/elur/next-2/dom-write.cjs.map +1 -0
  62. package/dist/lib/elur/next-2/dom-write.d.cts +18 -0
  63. package/dist/lib/elur/next-2/dom-write.d.ts +18 -0
  64. package/dist/lib/elur/next-2/dom-write.js +31 -0
  65. package/dist/lib/elur/next-2/dom-write.js.map +1 -0
  66. package/dist/lib/elur/next-2/hydrate.cjs +2 -0
  67. package/dist/lib/elur/next-2/hydrate.cjs.map +1 -0
  68. package/dist/lib/elur/next-2/hydrate.d.cts +30 -0
  69. package/dist/lib/elur/next-2/hydrate.d.ts +30 -0
  70. package/dist/lib/elur/next-2/hydrate.js +449 -0
  71. package/dist/lib/elur/next-2/hydrate.js.map +1 -0
  72. package/dist/lib/elur/next-2/keyed-diff.cjs +2 -0
  73. package/dist/lib/elur/next-2/keyed-diff.cjs.map +1 -0
  74. package/dist/lib/elur/next-2/keyed-diff.d.cts +79 -0
  75. package/dist/lib/elur/next-2/keyed-diff.d.ts +79 -0
  76. package/dist/lib/elur/next-2/keyed-diff.js +208 -0
  77. package/dist/lib/elur/next-2/keyed-diff.js.map +1 -0
  78. package/dist/lib/elur/next-2/mount-helpers.cjs +2 -0
  79. package/dist/lib/elur/next-2/mount-helpers.cjs.map +1 -0
  80. package/dist/lib/elur/next-2/mount-helpers.d.cts +25 -0
  81. package/dist/lib/elur/next-2/mount-helpers.d.ts +25 -0
  82. package/dist/lib/elur/next-2/mount-helpers.js +87 -0
  83. package/dist/lib/elur/next-2/mount-helpers.js.map +1 -0
  84. package/dist/lib/elur/next-2/mount.cjs +2 -0
  85. package/dist/lib/elur/next-2/mount.cjs.map +1 -0
  86. package/dist/lib/elur/next-2/mount.d.cts +29 -0
  87. package/dist/lib/elur/next-2/mount.d.ts +29 -0
  88. package/dist/lib/elur/next-2/mount.js +34 -0
  89. package/dist/lib/elur/next-2/mount.js.map +1 -0
  90. package/dist/lib/elur/next-2/node-binding.cjs +2 -0
  91. package/dist/lib/elur/next-2/node-binding.cjs.map +1 -0
  92. package/dist/lib/elur/next-2/node-binding.d.cts +6 -0
  93. package/dist/lib/elur/next-2/node-binding.d.ts +6 -0
  94. package/dist/lib/elur/next-2/node-binding.js +130 -0
  95. package/dist/lib/elur/next-2/node-binding.js.map +1 -0
  96. package/dist/lib/elur/next-2/reactivity.cjs +2 -0
  97. package/dist/lib/elur/next-2/reactivity.cjs.map +1 -0
  98. package/dist/lib/elur/next-2/reactivity.d.cts +220 -0
  99. package/dist/lib/elur/next-2/reactivity.d.ts +220 -0
  100. package/dist/lib/elur/next-2/reactivity.js +614 -0
  101. package/dist/lib/elur/next-2/reactivity.js.map +1 -0
  102. package/dist/lib/elur/next-2/scheduler.cjs +2 -0
  103. package/dist/lib/elur/next-2/scheduler.cjs.map +1 -0
  104. package/dist/lib/elur/next-2/scheduler.d.cts +23 -0
  105. package/dist/lib/elur/next-2/scheduler.d.ts +23 -0
  106. package/dist/lib/elur/next-2/scheduler.js +26 -0
  107. package/dist/lib/elur/next-2/scheduler.js.map +1 -0
  108. package/dist/lib/elur/plugins.cjs +1 -1
  109. package/dist/lib/elur/plugins.cjs.map +1 -1
  110. package/dist/lib/elur/plugins.js +1 -1
  111. package/dist/lib/elur/reactivity.cjs +1 -2
  112. package/dist/lib/elur/reactivity.d.cts +1 -67
  113. package/dist/lib/elur/reactivity.d.ts +1 -67
  114. package/dist/lib/elur/reactivity.js +2 -218
  115. package/dist/lib/elur/router.cjs +2 -2
  116. package/dist/lib/elur/router.cjs.map +1 -1
  117. package/dist/lib/elur/router.js +2 -2
  118. package/dist/lib/elur/router.js.map +1 -1
  119. package/dist/lib/elur/server/index.cjs +1 -1
  120. package/dist/lib/elur/server/index.cjs.map +1 -1
  121. package/dist/lib/elur/server/index.js +180 -108
  122. package/dist/lib/elur/server/index.js.map +1 -1
  123. package/dist/lib/elur/store.cjs +1 -1
  124. package/dist/lib/elur/store.cjs.map +1 -1
  125. package/dist/lib/elur/store.js +10 -4
  126. package/dist/lib/elur/store.js.map +1 -1
  127. package/dist/lib/elur/template/bindings.cjs +1 -1
  128. package/dist/lib/elur/template/bindings.cjs.map +1 -1
  129. package/dist/lib/elur/template/bindings.js +98 -84
  130. package/dist/lib/elur/template/bindings.js.map +1 -1
  131. package/dist/lib/elur/template/dom-write.d.cts +1 -5
  132. package/dist/lib/elur/template/dom-write.d.ts +1 -5
  133. package/dist/lib/elur/template/error-boundary.cjs +1 -1
  134. package/dist/lib/elur/template/error-boundary.cjs.map +1 -1
  135. package/dist/lib/elur/template/error-boundary.js +56 -50
  136. package/dist/lib/elur/template/error-boundary.js.map +1 -1
  137. package/dist/lib/elur/template/html.cjs +1 -1
  138. package/dist/lib/elur/template/html.cjs.map +1 -1
  139. package/dist/lib/elur/template/html.js +40 -37
  140. package/dist/lib/elur/template/html.js.map +1 -1
  141. package/dist/lib/elur/template/index.cjs +1 -1
  142. package/dist/lib/elur/template/index.d.cts +2 -1
  143. package/dist/lib/elur/template/index.d.ts +2 -1
  144. package/dist/lib/elur/template/index.js +13 -12
  145. package/dist/lib/elur/template/keyed-diff.d.cts +1 -32
  146. package/dist/lib/elur/template/keyed-diff.d.ts +1 -32
  147. package/dist/lib/elur/template/keyed.cjs +1 -1
  148. package/dist/lib/elur/template/keyed.cjs.map +1 -1
  149. package/dist/lib/elur/template/keyed.d.cts +10 -2
  150. package/dist/lib/elur/template/keyed.d.ts +10 -2
  151. package/dist/lib/elur/template/keyed.js +12 -1
  152. package/dist/lib/elur/template/keyed.js.map +1 -1
  153. package/dist/lib/elur/template/mount-helpers.d.cts +1 -29
  154. package/dist/lib/elur/template/mount-helpers.d.ts +1 -29
  155. package/dist/lib/elur/template/node-binding.d.cts +1 -6
  156. package/dist/lib/elur/template/node-binding.d.ts +1 -6
  157. package/dist/lib/elur/template/portal.cjs +1 -1
  158. package/dist/lib/elur/template/portal.cjs.map +1 -1
  159. package/dist/lib/elur/template/portal.js +1 -1
  160. package/dist/lib/elur/template/transitions.cjs +1 -1
  161. package/dist/lib/elur/template/transitions.cjs.map +1 -1
  162. package/dist/lib/elur/template/transitions.js +2 -2
  163. package/dist/lib/elur/template/types.cjs +1 -1
  164. package/dist/lib/elur/template/types.cjs.map +1 -1
  165. package/dist/lib/elur/template/types.d.cts +55 -0
  166. package/dist/lib/elur/template/types.d.ts +55 -0
  167. package/dist/lib/elur/template/types.js +5 -1
  168. package/dist/lib/elur/template/types.js.map +1 -1
  169. package/dist/lib/elur.cjs +1 -1
  170. package/dist/lib/elur.js +2 -2
  171. package/dist/lib/form.cjs +1 -1
  172. package/dist/lib/form.js +1 -1
  173. package/dist/lib/hydrate.cjs +1 -2
  174. package/dist/lib/hydrate.js +2 -410
  175. package/dist/lib/index.cjs +1 -1
  176. package/dist/lib/index.js +2 -2
  177. package/dist/lib/lifecycle.cjs +1 -1
  178. package/dist/lib/lifecycle.js +4 -0
  179. package/dist/lib/plugins.cjs +1 -1
  180. package/dist/lib/plugins.js +1 -1
  181. package/dist/lib/router.cjs +2 -2
  182. package/dist/lib/router.js +2 -2
  183. package/dist/lib/server.cjs +1 -1
  184. package/dist/lib/server.js +180 -108
  185. package/dist/lib/signals.cjs +1 -2
  186. package/dist/lib/signals.js +2 -218
  187. package/dist/lib/store.cjs +1 -1
  188. package/dist/lib/store.js +10 -4
  189. package/dist/lib/template.cjs +1 -1
  190. package/dist/lib/template.js +13 -12
  191. package/package.json +7 -9
  192. package/dist/lib/elur/component.cjs.map +0 -1
  193. package/dist/lib/elur/component.js.map +0 -1
  194. package/dist/lib/elur/hydrate/index.cjs.map +0 -1
  195. package/dist/lib/elur/hydrate/index.js.map +0 -1
  196. package/dist/lib/elur/reactivity.cjs.map +0 -1
  197. package/dist/lib/elur/reactivity.js.map +0 -1
  198. package/dist/lib/elur/template/dom-write.cjs +0 -2
  199. package/dist/lib/elur/template/dom-write.cjs.map +0 -1
  200. package/dist/lib/elur/template/dom-write.js +0 -16
  201. package/dist/lib/elur/template/dom-write.js.map +0 -1
  202. package/dist/lib/elur/template/keyed-diff.cjs +0 -2
  203. package/dist/lib/elur/template/keyed-diff.cjs.map +0 -1
  204. package/dist/lib/elur/template/keyed-diff.js +0 -100
  205. package/dist/lib/elur/template/keyed-diff.js.map +0 -1
  206. package/dist/lib/elur/template/mount-helpers.cjs +0 -2
  207. package/dist/lib/elur/template/mount-helpers.cjs.map +0 -1
  208. package/dist/lib/elur/template/mount-helpers.js +0 -132
  209. package/dist/lib/elur/template/mount-helpers.js.map +0 -1
  210. package/dist/lib/elur/template/node-binding.cjs +0 -2
  211. package/dist/lib/elur/template/node-binding.cjs.map +0 -1
  212. package/dist/lib/elur/template/node-binding.js +0 -95
  213. package/dist/lib/elur/template/node-binding.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store.cjs","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n for (const guard of _guardFns) {\n guard(current, current);\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = _baseline[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
1
+ {"version":3,"file":"store.cjs","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n let next: Partial<T> = _baseline;\n for (const guard of _guardFns) {\n const result = guard(next, current);\n if (result !== undefined) {\n next = { ...next, ...result };\n }\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = (next as T)[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Signal as e, batch as t, computed as n, signal as r, watch as i } from "./signals.js";
1
+ import { Signal as e, batch as t, computed as n, signal as r, watch as i } from "./elur/next-2/reactivity.js";
2
2
  //#region src/elur/store.ts
3
3
  var a = class extends e {
4
4
  label;
@@ -50,10 +50,16 @@ function u(a, u) {
50
50
  }
51
51
  let S = [];
52
52
  function C() {
53
- let e = T();
54
- for (let t of S) t(e, e);
53
+ let e = T(), n = x;
54
+ for (let t of S) {
55
+ let r = t(n, e);
56
+ r !== void 0 && (n = {
57
+ ...n,
58
+ ...r
59
+ });
60
+ }
55
61
  t(() => {
56
- for (let e of g) _[e].value = x[e];
62
+ for (let e of g) _[e].value = n[e];
57
63
  });
58
64
  }
59
65
  function w(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n for (const guard of _guardFns) {\n guard(current, current);\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = _baseline[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":[],"sources":["../../src/elur/store.ts"],"sourcesContent":["import {\n Signal,\n signal,\n computed,\n batch,\n watch,\n type WatchOptions,\n} from \"./reactivity.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a plain state object T into a record of Signals — one per key.\n * This is what the actions/getters factories receive as their argument.\n *\n * Given: { count: number, name: string }\n * Becomes: { count: Signal<number>, name: Signal<string> }\n */\nexport type StoreSignals<T extends object> = {\n readonly [K in keyof T]: Signal<T[K]>;\n};\n\n/**\n * A read-only Signal — extends Signal so it satisfies `instanceof Signal`\n * checks (used by `watch()`), but throws on any mutation attempt.\n */\nexport class ReadonlySignal<T> extends Signal<T> {\n private readonly label: string;\n constructor(source: Signal<T>, label: string = \"ReadonlySignal\") {\n super(source.peek());\n this.label = label;\n\n Object.defineProperty(this, \"value\", {\n get: () => source.value,\n set: () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); },\n configurable: false,\n });\n\n this.update = () => { throw new Error(`[elur] \"${this.label}\" is read-only.`); };\n this.dispose = () => { throw new Error(`[elur] Cannot dispose \"${this.label}\" directly.`); };\n }\n}\n\n/**\n * Maps a getters factory result (a record of Signals) into a record of\n * read-only Signals exposed on the store.\n *\n * The Omit pattern enforces `readonly value` at the type level — TypeScript\n * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class\n * also throws on mutation as defense in depth.\n */\nexport type StoreGetters<G extends Record<string, Signal<unknown>>> = {\n readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, \"value\"> & {\n readonly value: G[K] extends Signal<infer V> ? V : never;\n };\n};\n\n/**\n * The full store type — combines reactive state signals, action methods,\n * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.\n */\nexport type Store<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = StoreSignals<T> & A & StoreGetters<G> & {\n readonly $id: string;\n /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */\n readonly $state: T;\n /**\n * Passive snapshot — returns the current state values WITHOUT creating\n * a reactive subscription. Use this in plugins, loggers, persistence,\n * or anywhere you need a one-shot read.\n */\n $snapshot(): T;\n /**\n * The computed Signal that backs $state. Plugins receive this to\n * compose new reactive nodes on top.\n */\n readonly $stateSignal: ReadonlySignal<T>;\n /** Reset to initial values (batched). */\n $reset(): void;\n /** Partial update (batched). */\n $patch(partial: Partial<T>): void;\n /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */\n $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;\n /** Disposes the store and runs all plugin cleanups. */\n $dispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory types\n// ---------------------------------------------------------------------------\n\nexport type ActionsFactory<\n T extends object,\n A extends object,\n> = (signals: StoreSignals<T>) => A;\n\nexport type GettersFactory<\n T extends object,\n G extends Record<string, Signal<unknown>>,\n> = (signals: StoreSignals<T>) => G;\n\n// ---------------------------------------------------------------------------\n// Plugin type\n// ---------------------------------------------------------------------------\n\n/**\n * Function type for mutation guards.\n * Intercepts $patch and $reset to validate or transform state before it is applied.\n */\nexport type GuardFn<T extends object> = (\n next: Partial<T>,\n current: T,\n) => Partial<T> | void;\n\n/**\n * A ElurPlugin is a function that receives the assembled store and\n * optionally returns a cleanup function called on $dispose().\n *\n * There are NO lifecycle hooks. Plugins extend the signal graph directly\n * using the framework primitives:\n *\n * watch(store.$stateSignal, ...) — react to any state change\n * computed(() => store.someSignal.value) — derive new nodes\n * store.$snapshot() — passive read for logging/persistence\n */\nexport type ElurPlugin<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n> = (store: Store<T, A, G>) => (() => void) | void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options object for createStore. NoInfer<T> on factory parameters ensures\n * T is inferred ONLY from initialState, never from the factories — this is\n * what restores the type inference that broke in v2.2.1.\n */\nexport type CreateStoreOptions<\n T extends object,\n A extends object,\n G extends Record<string, Signal<unknown>>,\n> = {\n /** Display name. Used in error messages, devtools, and $id. */\n name?: string;\n /** Action factory — receives raw signals, returns methods exposed on the store. */\n actions?: (signals: StoreSignals<NoInfer<T>>) => A;\n /** Getter factory — receives raw signals, returns computed Signals exposed as ReadonlySignals. */\n getters?: (signals: StoreSignals<NoInfer<T>>) => G;\n /** Plugins to extend the store. Each receives the assembled store. */\n plugins?: ElurPlugin<NoInfer<T>, A, G>[];\n /**\n * Custom serializer for the store baseline (used by `$reset`).\n * Defaults to `structuredClone`. Provide this when your state contains\n * non-serializable values (Map, Set, class instances, etc.) that\n * `structuredClone` cannot handle.\n */\n serialize?: (state: NoInfer<T>) => T;\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst RESERVED = new Set([\n \"$id\", \"$state\", \"$stateSignal\", \"$snapshot\",\n \"$reset\", \"$patch\", \"$watch\", \"$dispose\",\n]);\n\nfunction assertKey(key: string): void {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new Error(`[elur] Store key \"${key}\" is not allowed for security reasons.`);\n }\n if (RESERVED.has(key)) throw new Error(`[elur] Store key \"${key}\" is reserved.`);\n}\n\nfunction warnReserved(key: string, ctx: \"action\" | \"getter\"): boolean {\n if (!RESERVED.has(key)) return true;\n console.warn(`[elur] Store ${ctx} \"${key}\" is reserved and will be ignored.`);\n return false;\n}\n\nfunction makeReadonly<T>(sig: Signal<T>, label: string): ReadonlySignal<T> {\n return new ReadonlySignal(sig, label);\n}\n\n// ---------------------------------------------------------------------------\n// createStore\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store with optional actions, getters, and plugins.\n *\n * @example Just state\n * ```ts\n * const counter = createStore({ count: 0 });\n * counter.count.value++;\n * ```\n *\n * @example State + actions\n * ```ts\n * const counter = createStore({ count: 0 }, {\n * actions: (s) => ({ increment: () => s.count.value++ }),\n * });\n * counter.increment();\n * ```\n *\n * @example State + getters (no actions)\n * ```ts\n * const inventory = createStore({ items: [] as Item[] }, {\n * getters: (s) => ({ count: computed(() => s.items.value.length) }),\n * });\n * inventory.count.value;\n * ```\n *\n * @example Full store\n * ```ts\n * const cart = createStore({ items: [] as Item[], discount: 0 }, {\n * name: \"cart\",\n * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),\n * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),\n * plugins: [persistPlugin({ key: \"cart\" })],\n * });\n * ```\n */\nexport function createStore<\n T extends object,\n A extends object = Record<never, never>,\n G extends Record<string, Signal<unknown>> = Record<never, never>,\n>(\n initialState: T,\n options?: CreateStoreOptions<T, A, G>,\n): Store<T, A, G> {\n const { name = \"store\", actions: actionsFactory, getters: gettersFactory, plugins = [], serialize } = options ?? {};\n\n const keys = Object.keys(initialState) as Array<keyof T & string>;\n\n const signals = {} as { [K in keyof T]: Signal<T[K]> };\n for (const key of keys) {\n assertKey(key);\n signals[key] = signal(initialState[key]);\n }\n const typedSignals: StoreSignals<T> = signals;\n\n const _stateComputed = computed<T>(() => {\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].value;\n }\n return snap;\n });\n\n const $stateSignal = makeReadonly(_stateComputed, `store \"${name}\".$stateSignal`);\n\n let _baseline: T;\n try {\n // Use custom serializer if provided, otherwise structuredClone.\n _baseline = serialize ? serialize(initialState) : structuredClone(initialState);\n } catch (e) {\n throw new Error(\n `[elur] Store \"${name}\" initialState contains non-serializable data ` +\n `(functions, DOM nodes, Symbols, or WeakRefs). ` +\n `Provide a custom \\`serialize\\` option or remove these before creating the store. ` +\n `Original error: ${e}`\n );\n }\n\n const _guardFns: GuardFn<T>[] = [];\n\n function $reset(): void {\n const current = $snapshot();\n let next: Partial<T> = _baseline;\n for (const guard of _guardFns) {\n const result = guard(next, current);\n if (result !== undefined) {\n next = { ...next, ...result };\n }\n }\n batch(() => {\n for (const key of keys) {\n signals[key].value = (next as T)[key];\n }\n });\n }\n\n function $patch(partial: Partial<T>): void {\n let payload: Partial<T> = partial;\n const current = $snapshot();\n for (const guard of _guardFns) {\n const result = guard(payload, current);\n if (result !== undefined) {\n payload = result;\n }\n }\n batch(() => {\n for (const key of Object.keys(payload) as Array<keyof T & string>) {\n if (Object.prototype.hasOwnProperty.call(signals, key)) {\n signals[key].value = payload[key] as T[keyof T & string];\n }\n }\n });\n }\n\n function $snapshot(): T {\n // Passive read — uses peek() to avoid creating a reactive subscription.\n const snap = {} as T;\n for (const key of keys) {\n snap[key] = signals[key].peek();\n }\n return snap;\n }\n\n function $watch(\n cb: (next: T, prev: T | undefined) => void,\n opts?: WatchOptions,\n ): () => void {\n return watch(_stateComputed, cb, opts);\n }\n\n const store = Object.assign(\n Object.create(null) as object,\n typedSignals,\n { $reset, $patch, $watch, $snapshot },\n ) as Store<T, A, G>;\n\n Object.defineProperty(store, \"$id\", {\n value: name, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"$state\", {\n get(): T { return _stateComputed.value; },\n enumerable: true, configurable: false,\n });\n\n Object.defineProperty(store, \"$stateSignal\", {\n value: $stateSignal, writable: false, enumerable: false, configurable: false,\n });\n\n Object.defineProperty(store, \"_guardFns\", {\n value: _guardFns, writable: false, enumerable: false, configurable: false,\n });\n\n const occupiedKeys = new Set<string>([...keys, ...Array.from(RESERVED)]);\n\n if (actionsFactory) {\n const raw = actionsFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"action\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": action \"${key}\" collides with an existing ` +\n `signal or getter and will be ignored.`,\n );\n continue;\n }\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n (raw as Record<string, unknown>)[key];\n }\n }\n\n if (gettersFactory) {\n const raw = gettersFactory(typedSignals);\n for (const key of Object.keys(raw)) {\n if (!warnReserved(key, \"getter\")) continue;\n if (occupiedKeys.has(key)) {\n console.warn(\n `[elur] Store \"${name}\": getter \"${key}\" collides with an existing ` +\n `signal or action and will be ignored.`,\n );\n continue;\n }\n\n const sig = (raw as Record<string, Signal<unknown>>)[key];\n\n if (!(sig instanceof Signal)) {\n throw new TypeError(\n `[elur] Store \"${name}\": getter \"${key}\" must return a Signal ` +\n `(wrap it with computed()). Got: ${typeof sig}`\n );\n }\n\n occupiedKeys.add(key);\n (store as Record<string, unknown>)[key] =\n makeReadonly(sig, `getter \"${key}\" in store \"${name}\"`);\n }\n }\n\n const cleanups: Array<() => void> = [() => _stateComputed.dispose()];\n\n for (const plugin of plugins) {\n try {\n const cleanup = plugin(store);\n if (typeof cleanup === \"function\") cleanups.push(cleanup);\n } catch (error) {\n console.error(\n `[elur] Plugin initialization failed for store \"${name}\":`,\n error\n );\n }\n }\n\n (store as Record<string, unknown>)[\"$dispose\"] = () => {\n for (const fn of cleanups) fn();\n };\n\n return store;\n}"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`../../signals.cjs`),t=require(`./dom-write.cjs`),n=require(`./node-binding.cjs`),r=require(`./sanitize.cjs`);function i(e,t){t?e.style.display===`none`&&(e.style.display=``):e.style.display!==`none`&&(e.style.display=`none`)}function a(e){let t=e.lastIndexOf(`>`),n=e.lastIndexOf(`<`);if(n<=t)return{type:`node`};let i=e.slice(n+1),a=i.lastIndexOf(`=`);if(a===-1)return{type:`node`};let o=i.endsWith(`"`)||i.endsWith(`'`)||i[i.length-1]===`"`||i[i.length-1]===`'`,s=a-1;for(;s>=0&&/\S/.test(i[s]);)s--;s++;let c=i.slice(s,a);if(c[0]===`@`){let e=c.slice(1).split(`.`);return{type:`event`,eventName:e[0],modifiers:e.slice(1),hadOpenQuote:o}}return{type:`attr`,attrName:c,hadOpenQuote:o,url:r.isUrlAttrName(c),executable:r.isExecutableAttrName(c)}}var o={enter:`Enter`,escape:`Escape`,space:` `,tab:`Tab`,delete:`Delete`,backspace:`Backspace`,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`},s=new Set([`click`,`dblclick`,`mousedown`,`mouseup`,`keydown`,`keyup`,`input`,`change`,`submit`]),c=new Set;function l(e,t,n){let r=e.target,i=e.stopPropagation,a=!1;for(e.stopPropagation=()=>{a=!0,i.call(e)};r&&r!==document;){let i=r[t];if(i){let t=r[n];if(t){if(t.includes(`prevent`)&&e.preventDefault(),t.includes(`stop`)&&e.stopPropagation(),t.includes(`self`)&&e.target!==r){r=r.parentNode;continue}if(`key`in e){let n=e,i=!0;for(let e of t){let t=o[e];if(t!==void 0&&n.key!==t){i=!1;break}if(!t&&e.length===1&&n.key.toLowerCase()!==e){i=!1;break}}if(!i){r=r.parentNode;continue}}}if(i(e),a)break}r=r.parentNode}e.stopPropagation=i}var u=new Map;function d(e){if(!c.has(e)){let t=`__elur_${e}`,n=`__elur_${e}_mods`,r=e=>l(e,t,n);u.set(e,r),document.addEventListener(e,r),c.add(e)}}function f(e,t,n,r){d(t);let i=`__elur_${t}`,a=`__elur_${t}_mods`;e[i]=r,n.length>0&&(e[a]=n)}function p(e,t,n,r){f(e,t,n,r);let i=`__elur_${t}`,a=`__elur_${t}_mods`;return()=>{e[i]=null,e[a]=null}}function m(e){return s.has(e)}function h(e,t,n,r){let i=Array(t.length),a=-1;for(let e=0;e<t.length;e++)r[e]&&r[e].nodeIndex>a&&(a=r[e].nodeIndex);let o=Array(a+1);if(o[0]=e,a>0){let t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT),n=1,r;for(;n<=a&&(r=t.nextNode());)o[n++]=r}for(let e=0;e<t.length;e++){let t=r[e];i[e]=t?o[t.nodeIndex]:null}return g(e,t,n,r,i)}function g(i,a,o,s,c){let l=[],u=[];for(let i=0;i<a.length;i++){let d=a[i],f=o[i],h=s[i];if(!h)continue;let g=c[i];if(d.type===`event`){let e=h.name,t=f,n=d.modifiers;if(m(e)&&!n.includes(`capture`)&&!n.includes(`once`))l.push(p(g,e,n,t));else{let r={once:n.includes(`once`),capture:n.includes(`capture`),passive:n.includes(`passive`)},i=e=>{n.includes(`prevent`)&&e.preventDefault(),n.includes(`stop`)&&e.stopPropagation(),!(n.includes(`self`)&&e.target!==e.currentTarget)&&t(e)};g.addEventListener(e,i,r),l.push(()=>g.removeEventListener(e,i,r))}continue}if(d.type===`attr`){let n=h.name,i=g;if(n===`ref`){f.el=i,l.push(()=>{f.el=null});continue}if(n===`show`||n===`hide`){let r=i,a=null;if(typeof f==`function`){let i=!1,o=!1,s=!0,c=e.effect(()=>{o=!!f();let e=()=>{i=!1;let e=n===`show`?o:!o;a===null&&(a=r.style.display||``),r.style.display=e?a:`none`};s?(s=!1,e()):i||(i=!0,t.queueDOMWrite(e))});l.push(c)}else (n===`show`?f:!f)||(r.style.display=`none`);continue}(d.executable??r.isExecutableAttrName(n))&&console.warn(`[elur] Dynamic binding on executable attribute "${n}". Use @event for handlers; avoid binding untrusted values here.`);let a=d.url??r.isUrlAttrName(n),o=(n===`value`||n===`checked`||n===`selected`)&&n in i;if(typeof f==`function`){let s=!1,c,u=!0,d=e.effect(()=>{c=f();let e=()=>{s=!1;let e=c;if(o)i[n]=e??``;else if(e==null||e===!1)i.removeAttribute(n);else{let t=String(e);i.setAttribute(n,a?r.sanitizeUrl(t):t)}};u?(u=!1,e()):s||(s=!0,t.queueDOMWrite(e))});l.push(d)}else if(o)i[n]=f??``;else if(f!=null&&f!==!1){let e=String(f);i.setAttribute(n,a?r.sanitizeUrl(e):e)}continue}let _=g;if(!_)continue;let v=document.createTextNode(``);_.parentNode.replaceChild(v,_),n.activateNodeBinding(v,f,l,u)}return{disposes:l,postMountHooks:u}}exports._activateBindingsWithNodes=g,exports._ensureDelegatedEvent=d,exports._setDelegatedEvent=f,exports.activateBindings=h,exports.activateDelegatedEvent=p,exports.detectContext=a,exports.isDelegableEvent=m,exports.showWhen=i;
1
+ const e=require(`../next-2/reactivity.cjs`),t=require(`./types.cjs`),n=require(`../next-2/dom-write.cjs`),r=require(`../next-2/node-binding.cjs`),i=require(`./sanitize.cjs`);function a(e,t){t?e.style.display===`none`&&(e.style.display=``):e.style.display!==`none`&&(e.style.display=`none`)}function o(e){let t=e.lastIndexOf(`>`),n=e.lastIndexOf(`<`);if(n<=t)return{type:`node`};let r=e.slice(n+1),a=r.lastIndexOf(`=`);if(a===-1)return{type:`node`};let o=r.endsWith(`"`)||r.endsWith(`'`)||r[r.length-1]===`"`||r[r.length-1]===`'`,s=a-1;for(;s>=0&&/\S/.test(r[s]);)s--;s++;let c=r.slice(s,a);if(c[0]===`@`){let e=c.slice(1).split(`.`);return{type:`event`,eventName:e[0],modifiers:e.slice(1),hadOpenQuote:o}}return{type:`attr`,attrName:c,hadOpenQuote:o,url:i.isUrlAttrName(c),executable:i.isExecutableAttrName(c)}}var s={enter:`Enter`,escape:`Escape`,space:` `,tab:`Tab`,delete:`Delete`,backspace:`Backspace`,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`},c=new Set([`click`,`dblclick`,`mousedown`,`mouseup`,`keydown`,`keyup`,`input`,`change`,`submit`]),l=new Set;function u(e,t,n){let r=e.target,i=e.stopPropagation,a=!1;for(e.stopPropagation=()=>{a=!0,i.call(e)};r&&r!==document;){let i=r[t];if(i){let t=r[n];if(t){if(t.includes(`prevent`)&&e.preventDefault(),t.includes(`stop`)&&e.stopPropagation(),t.includes(`self`)&&e.target!==r){r=r.parentNode;continue}if(`key`in e){let n=e,i=!0;for(let e of t){let t=s[e];if(t!==void 0&&n.key!==t){i=!1;break}if(!t&&e.length===1&&n.key.toLowerCase()!==e){i=!1;break}}if(!i){r=r.parentNode;continue}}}if(i(e),a)break}r=r.parentNode}e.stopPropagation=i}var d=new Map;function f(e){if(!l.has(e)){let t=`__elur_${e}`,n=`__elur_${e}_mods`,r=e=>u(e,t,n);d.set(e,r),document.addEventListener(e,r),l.add(e)}}function p(e,t,n,r){f(t);let i=`__elur_${t}`,a=`__elur_${t}_mods`;e[i]=r,n.length>0&&(e[a]=n)}function m(e,t,n,r){p(e,t,n,r);let i=`__elur_${t}`,a=`__elur_${t}_mods`;return()=>{e[i]=null,e[a]=null}}function h(e){return c.has(e)}function g(e,t,n,r){let i=Array(t.length),a=-1;for(let e=0;e<t.length;e++)r[e]&&r[e].nodeIndex>a&&(a=r[e].nodeIndex);let o=Array(a+1);if(o[0]=e,a>0){let t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT),n=1,r;for(;n<=a&&(r=t.nextNode());)o[n++]=r}for(let e=0;e<t.length;e++){let t=r[e];i[e]=t?o[t.nodeIndex]:null}return _(e,t,n,r,i)}function _(a,o,s,c,l){let u=[],d=[];for(let a=0;a<o.length;a++){let f=o[a],p=s[a];t.isDerivedBinding(p)&&(p=p.get);let g=c[a];if(!g)continue;let _=l[a];if(f.type===`event`){let e=g.name,t=p,n=f.modifiers;if(h(e)&&!n.includes(`capture`)&&!n.includes(`once`)&&!n.includes(`passive`))u.push(m(_,e,n,t));else{let r={once:n.includes(`once`),capture:n.includes(`capture`),passive:n.includes(`passive`)},i=e=>{n.includes(`prevent`)&&e.preventDefault(),n.includes(`stop`)&&e.stopPropagation(),!(n.includes(`self`)&&e.target!==e.currentTarget)&&t(e)};_.addEventListener(e,i,r),u.push(()=>_.removeEventListener(e,i,r))}continue}if(f.type===`attr`){let t=g.name,r=_;if(t===`ref`){p.el=r,u.push(()=>{p.el=null});continue}if(t===`show`||t===`hide`){let i=r,a=null;if(typeof p==`function`){let r=!1,o=!1,s=!0,c=e.effect(()=>{o=!!p();let e=()=>{r=!1;let e=t===`show`?o:!o;a===null&&(a=i.style.display||``),i.style.display=e?a:`none`};s?(s=!1,e()):r||(r=!0,n.queueDOMWrite(e))});u.push(c)}else (t===`show`?p:!p)||(i.style.display=`none`);continue}(f.executable??i.isExecutableAttrName(t))&&console.warn(`[elur] Dynamic binding on executable attribute "${t}". Use @event for handlers; avoid binding untrusted values here.`);let a=f.url??i.isUrlAttrName(t),o=(t===`value`||t===`checked`||t===`selected`)&&t in r;if(p instanceof e.Signal){u.push(e._bindSignal(p,e=>{if(o)r[t]=e??``;else if(e==null||e===!1)r.removeAttribute(t);else{let n=String(e);r.setAttribute(t,a?i.sanitizeUrl(n):n)}}));continue}if(typeof p==`function`){let s=!1,c,l=!0,d=e.effect(()=>{c=p();let e=()=>{s=!1;let e=c;if(o)r[t]=e??``;else if(e==null||e===!1)r.removeAttribute(t);else{let n=String(e);r.setAttribute(t,a?i.sanitizeUrl(n):n)}};l?(l=!1,e()):s||(s=!0,n.queueDOMWrite(e))});u.push(d)}else if(o)r[t]=p??``;else if(p!=null&&p!==!1){let e=String(p);r.setAttribute(t,a?i.sanitizeUrl(e):e)}continue}let v=_;if(!v)continue;let y=document.createTextNode(``);v.parentNode.replaceChild(y,v),r.activateNodeBinding(y,p,u,d)}return{disposes:u,postMountHooks:d}}exports._activateBindingsWithNodes=_,exports._ensureDelegatedEvent=f,exports._setDelegatedEvent=p,exports.activateBindings=g,exports.activateDelegatedEvent=m,exports.detectContext=o,exports.isDelegableEvent=h,exports.showWhen=a;
2
2
  //# sourceMappingURL=bindings.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.cjs","names":[],"sources":["../../../../src/elur/template/bindings.ts"],"sourcesContent":["import { effect } from \"../reactivity.js\";\nimport type { ElurRef, TemplateBindingContext } from \"./types.js\";\nimport { activateNodeBinding } from \"./node-binding.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\nimport { isUrlAttrName, isExecutableAttrName, sanitizeUrl } from \"./sanitize.js\";\n\n// =============================================================================\n// --- show / hide ---\n// =============================================================================\n\n/** Toggles element visibility via `display: none` without unmounting. */\nexport function showWhen(el: HTMLElement, condition: boolean): void {\n if (!condition) {\n if (el.style.display !== \"none\") el.style.display = \"none\";\n } else {\n if (el.style.display === \"none\") el.style.display = \"\";\n }\n}\n\n// =============================================================================\n// --- Binding context ---\n// =============================================================================\n\nexport type BindingContext = TemplateBindingContext;\n\n/**\n * Determines the binding context (node, event, or attribute) for an interpolated\n * value based on the preceding template string.\n */\nexport function detectContext(prevString: string): BindingContext {\n const lastClose = prevString.lastIndexOf(\">\");\n const lastOpen = prevString.lastIndexOf(\"<\");\n\n if (lastOpen <= lastClose) {\n return { type: \"node\" };\n }\n\n const tagContent = prevString.slice(lastOpen + 1);\n\n const eqIdx = tagContent.lastIndexOf(\"=\");\n if (eqIdx === -1) {\n return { type: \"node\" };\n }\n\n const hadOpenQuote =\n tagContent.endsWith('\"') ||\n tagContent.endsWith(\"'\") ||\n tagContent[tagContent.length - 1] === '\"' ||\n tagContent[tagContent.length - 1] === \"'\";\n\n let startIdx = eqIdx - 1;\n while (startIdx >= 0 && /\\S/.test(tagContent[startIdx])) {\n startIdx--;\n }\n startIdx++;\n\n const fullAttr = tagContent.slice(startIdx, eqIdx);\n\n if (fullAttr[0] === \"@\") {\n const parts = fullAttr.slice(1).split(\".\");\n return {\n type: \"event\",\n eventName: parts[0],\n modifiers: parts.slice(1),\n hadOpenQuote,\n };\n }\n\n return {\n type: \"attr\",\n attrName: fullAttr,\n hadOpenQuote,\n // Precomputed once per template (compile time). Read as a cheap boolean\n // in the render/update hot path.\n url: isUrlAttrName(fullAttr),\n executable: isExecutableAttrName(fullAttr),\n };\n}\n\n// =============================================================================\n// --- Keyboard modifier map ---\n// =============================================================================\n\nconst KEY_MAP: Readonly<Record<string, string>> = {\n enter: \"Enter\",\n escape: \"Escape\",\n space: \" \",\n tab: \"Tab\",\n delete: \"Delete\",\n backspace: \"Backspace\",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n};\n\n// =============================================================================\n// --- Global Event Delegation ---\n// =============================================================================\n\nconst DELEGABLE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\",\n \"keydown\", \"keyup\", \"input\", \"change\", \"submit\"\n]);\nconst _delegatedRegistry = new Set<string>();\n\nfunction _globalEventHandlerCore(e: Event, propName: string, modsName: string): void {\n let target = e.target as Node | null;\n\n const originalStop = e.stopPropagation;\n let stopped = false;\n e.stopPropagation = () => {\n stopped = true;\n originalStop.call(e);\n };\n\n while (target && target !== document) {\n const handler = (target as any)[propName] as EventListener | undefined;\n if (handler) {\n const mods = (target as any)[modsName] as string[] | undefined;\n if (mods) {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== target) {\n target = target.parentNode;\n continue;\n }\n if (\"key\" in e) {\n const ke = e as KeyboardEvent;\n let keyMatch = true;\n for (const mod of mods) {\n const mapped = KEY_MAP[mod];\n if (mapped !== undefined && ke.key !== mapped) { keyMatch = false; break; }\n if (!mapped && mod.length === 1 && ke.key.toLowerCase() !== mod) { keyMatch = false; break; }\n }\n if (!keyMatch) {\n target = target.parentNode;\n continue;\n }\n }\n }\n handler(e);\n if (stopped) break;\n }\n target = target.parentNode;\n }\n\n e.stopPropagation = originalStop;\n}\n\n// NOTE: entries are intentionally permanent — delegated listeners on document\n// live for the application lifetime.\nconst _delegatedHandlers = new Map<string, (e: Event) => void>();\n\n/**\n * Activates a delegated event on an element, using the same global registry\n * as mount-time bindings. Used by both `activateBindings` (mount) and the\n * hydrator to ensure consistent event delegation.\n *\n * @returns A dispose function that removes the handler from the element.\n */\nexport function _ensureDelegatedEvent(eventName: string): void {\n if (!_delegatedRegistry.has(eventName)) {\n const propName = `__elur_${eventName}`;\n const modsName = `__elur_${eventName}_mods`;\n const boundHandler = (e: Event) => _globalEventHandlerCore(e, propName, modsName);\n _delegatedHandlers.set(eventName, boundHandler);\n document.addEventListener(eventName, boundHandler);\n _delegatedRegistry.add(eventName);\n }\n}\n\nexport function _setDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): void {\n _ensureDelegatedEvent(eventName);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n (el as any)[nodePropName] = rawHandler;\n if (modifiers.length > 0) (el as any)[nodeModsName] = modifiers;\n}\n\nexport function activateDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): () => void {\n _setDelegatedEvent(el, eventName, modifiers, rawHandler);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n return () => {\n (el as any)[nodePropName] = null;\n (el as any)[nodeModsName] = null;\n };\n}\n\n/** Returns true if an event name is in the delegable set. */\nexport function isDelegableEvent(eventName: string): boolean {\n return DELEGABLE_EVENTS.has(eventName);\n}\n\n// =============================================================================\n// --- Binding activation ---\n// =============================================================================\n\n/** Activates all bindings on the cloned fragment. Returns dispose/postMount. */\nexport function activateBindings(\n fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n // PHASE 1: READ — single-pass TreeWalker O(N)\n const resolvedNodes = new Array<Node | null>(contexts.length);\n\n let maxNodeIndex = -1;\n for (let i = 0; i < contexts.length; i++) {\n if (pathMap[i] && pathMap[i]!.nodeIndex > maxNodeIndex) {\n maxNodeIndex = pathMap[i]!.nodeIndex;\n }\n }\n\n const flatNodes = new Array<Node>(maxNodeIndex + 1);\n flatNodes[0] = fragment;\n if (maxNodeIndex > 0) {\n const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);\n let fi = 1;\n let currentNode: Node | null;\n while (fi <= maxNodeIndex && (currentNode = walker.nextNode())) {\n flatNodes[fi++] = currentNode;\n }\n }\n\n for (let i = 0; i < contexts.length; i++) {\n const info = pathMap[i];\n resolvedNodes[i] = info ? flatNodes[info.nodeIndex] : null;\n }\n\n // PHASE 2: MUTATE (delegated to _activateBindingsWithNodes)\n return _activateBindingsWithNodes(fragment, contexts, values, pathMap, resolvedNodes);\n}\n\n/**\n * Activates bindings using pre-resolved nodes — skips the TreeWalker phase.\n * Used by the compiler's __elurCompiledTemplate to eliminate the second TreeWalker.\n */\nexport function _activateBindingsWithNodes(\n _fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n resolvedNodes: Array<Node | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n const disposes: Array<() => void> = [];\n const postMountHooks: Array<() => void> = [];\n\n for (let i = 0; i < contexts.length; i++) {\n const ctx = contexts[i];\n const value = values[i];\n const info = pathMap[i];\n if (!info) continue;\n\n const el = resolvedNodes[i]!;\n\n // --- Events ---\n if (ctx.type === \"event\") {\n const eventName = info.name!;\n const rawHandler = value as EventListener;\n const mods = ctx.modifiers;\n\n const canDelegate =\n isDelegableEvent(eventName) &&\n !mods.includes(\"capture\") &&\n !mods.includes(\"once\");\n\n if (canDelegate) {\n disposes.push(activateDelegatedEvent(el as Element, eventName, mods, rawHandler));\n } else {\n const listenerOpts: AddEventListenerOptions = {\n once: mods.includes(\"once\"),\n capture: mods.includes(\"capture\"),\n passive: mods.includes(\"passive\")\n };\n const handler = (e: Event) => {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== e.currentTarget) return;\n rawHandler(e);\n };\n el.addEventListener(eventName, handler, listenerOpts);\n disposes.push(() => el.removeEventListener(eventName, handler, listenerOpts));\n }\n continue;\n }\n\n // --- Attributes ---\n if (ctx.type === \"attr\") {\n const attrName = info.name!;\n const element = el as Element;\n\n if (attrName === \"ref\") {\n (value as ElurRef<Element>).el = element;\n disposes.push(() => { (value as ElurRef<Element>).el = null; });\n continue;\n }\n\n if (attrName === \"show\" || attrName === \"hide\") {\n const htmlEl = element as HTMLElement;\n let originalDisplay: string | null = null;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingVisible = false;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingVisible = Boolean((value as () => unknown)());\n const update = () => {\n queued = false;\n const shouldShow = attrName === \"show\" ? pendingVisible : !pendingVisible;\n if (originalDisplay === null) {\n originalDisplay = htmlEl.style.display || \"\";\n }\n htmlEl.style.display = shouldShow ? originalDisplay : \"none\";\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n const shouldShow = attrName === \"show\" ? Boolean(value) : !Boolean(value);\n if (!shouldShow) htmlEl.style.display = \"none\";\n }\n continue;\n }\n\n // on*/srcdoc bindings are non-idiomatic in Elur (events use @click) and\n // turn an untrusted value into executable code. Warn the developer but\n // do not block — the attribute name is developer-authored.\n if (ctx.executable ?? isExecutableAttrName(attrName)) {\n console.warn(\n `[elur] Dynamic binding on executable attribute \"${attrName}\". Use @event for handlers; avoid binding untrusted values here.`,\n );\n }\n\n // Precomputed at compile time. Only URL attributes pay the sanitizer;\n // class/style/aria-*/data-*/custom attributes skip it entirely.\n const isUrl = ctx.url ?? isUrlAttrName(attrName);\n\n const isDomProp = (attrName === \"value\" || attrName === \"checked\" || attrName === \"selected\") && attrName in element;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingValue: unknown;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingValue = (value as () => unknown)();\n const update = () => {\n queued = false;\n const v = pendingValue;\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n if (isDomProp) {\n (element as any)[attrName] = value ?? \"\";\n } else if (value != null && value !== false) {\n const s = String(value);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }\n continue;\n }\n\n // --- Nodes — delegate to node-binding.ts ---\n const originalAnchor = el as Comment;\n if (!originalAnchor) continue;\n\n const anchor = document.createTextNode(\"\");\n originalAnchor.parentNode!.replaceChild(anchor, originalAnchor);\n\n activateNodeBinding(anchor, value, disposes, postMountHooks);\n }\n\n return { disposes, postMountHooks };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bindings.cjs","names":[],"sources":["../../../../src/elur/template/bindings.ts"],"sourcesContent":["import { effect, Signal, _bindSignal } from \"../reactivity.js\";\nimport type { ElurRef, TemplateBindingContext } from \"./types.js\";\nimport { isDerivedBinding } from \"./types.js\";\nimport { activateNodeBinding } from \"./node-binding.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\nimport { isUrlAttrName, isExecutableAttrName, sanitizeUrl } from \"./sanitize.js\";\n\n// =============================================================================\n// --- show / hide ---\n// =============================================================================\n\n/** Toggles element visibility via `display: none` without unmounting. */\nexport function showWhen(el: HTMLElement, condition: boolean): void {\n if (!condition) {\n if (el.style.display !== \"none\") el.style.display = \"none\";\n } else {\n if (el.style.display === \"none\") el.style.display = \"\";\n }\n}\n\n// =============================================================================\n// --- Binding context ---\n// =============================================================================\n\nexport type BindingContext = TemplateBindingContext;\n\n/**\n * Determines the binding context (node, event, or attribute) for an interpolated\n * value based on the preceding template string.\n */\nexport function detectContext(prevString: string): BindingContext {\n const lastClose = prevString.lastIndexOf(\">\");\n const lastOpen = prevString.lastIndexOf(\"<\");\n\n if (lastOpen <= lastClose) {\n return { type: \"node\" };\n }\n\n const tagContent = prevString.slice(lastOpen + 1);\n\n const eqIdx = tagContent.lastIndexOf(\"=\");\n if (eqIdx === -1) {\n return { type: \"node\" };\n }\n\n const hadOpenQuote =\n tagContent.endsWith('\"') ||\n tagContent.endsWith(\"'\") ||\n tagContent[tagContent.length - 1] === '\"' ||\n tagContent[tagContent.length - 1] === \"'\";\n\n let startIdx = eqIdx - 1;\n while (startIdx >= 0 && /\\S/.test(tagContent[startIdx])) {\n startIdx--;\n }\n startIdx++;\n\n const fullAttr = tagContent.slice(startIdx, eqIdx);\n\n if (fullAttr[0] === \"@\") {\n const parts = fullAttr.slice(1).split(\".\");\n return {\n type: \"event\",\n eventName: parts[0],\n modifiers: parts.slice(1),\n hadOpenQuote,\n };\n }\n\n return {\n type: \"attr\",\n attrName: fullAttr,\n hadOpenQuote,\n // Precomputed once per template (compile time). Read as a cheap boolean\n // in the render/update hot path.\n url: isUrlAttrName(fullAttr),\n executable: isExecutableAttrName(fullAttr),\n };\n}\n\n// =============================================================================\n// --- Keyboard modifier map ---\n// =============================================================================\n\nconst KEY_MAP: Readonly<Record<string, string>> = {\n enter: \"Enter\",\n escape: \"Escape\",\n space: \" \",\n tab: \"Tab\",\n delete: \"Delete\",\n backspace: \"Backspace\",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n};\n\n// =============================================================================\n// --- Global Event Delegation ---\n// =============================================================================\n\nconst DELEGABLE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\",\n \"keydown\", \"keyup\", \"input\", \"change\", \"submit\"\n]);\nconst _delegatedRegistry = new Set<string>();\n\nfunction _globalEventHandlerCore(e: Event, propName: string, modsName: string): void {\n let target = e.target as Node | null;\n\n const originalStop = e.stopPropagation;\n let stopped = false;\n e.stopPropagation = () => {\n stopped = true;\n originalStop.call(e);\n };\n\n while (target && target !== document) {\n const handler = (target as any)[propName] as EventListener | undefined;\n if (handler) {\n const mods = (target as any)[modsName] as string[] | undefined;\n if (mods) {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== target) {\n target = target.parentNode;\n continue;\n }\n if (\"key\" in e) {\n const ke = e as KeyboardEvent;\n let keyMatch = true;\n for (const mod of mods) {\n const mapped = KEY_MAP[mod];\n if (mapped !== undefined && ke.key !== mapped) { keyMatch = false; break; }\n if (!mapped && mod.length === 1 && ke.key.toLowerCase() !== mod) { keyMatch = false; break; }\n }\n if (!keyMatch) {\n target = target.parentNode;\n continue;\n }\n }\n }\n handler(e);\n if (stopped) break;\n }\n target = target.parentNode;\n }\n\n e.stopPropagation = originalStop;\n}\n\n// NOTE: entries are intentionally permanent — delegated listeners on document\n// live for the application lifetime.\nconst _delegatedHandlers = new Map<string, (e: Event) => void>();\n\n/**\n * Activates a delegated event on an element, using the same global registry\n * as mount-time bindings. Used by both `activateBindings` (mount) and the\n * hydrator to ensure consistent event delegation.\n *\n * @returns A dispose function that removes the handler from the element.\n */\nexport function _ensureDelegatedEvent(eventName: string): void {\n if (!_delegatedRegistry.has(eventName)) {\n const propName = `__elur_${eventName}`;\n const modsName = `__elur_${eventName}_mods`;\n const boundHandler = (e: Event) => _globalEventHandlerCore(e, propName, modsName);\n _delegatedHandlers.set(eventName, boundHandler);\n document.addEventListener(eventName, boundHandler);\n _delegatedRegistry.add(eventName);\n }\n}\n\nexport function _setDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): void {\n _ensureDelegatedEvent(eventName);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n (el as any)[nodePropName] = rawHandler;\n if (modifiers.length > 0) (el as any)[nodeModsName] = modifiers;\n}\n\nexport function activateDelegatedEvent(\n el: Element,\n eventName: string,\n modifiers: readonly string[],\n rawHandler: EventListener,\n): () => void {\n _setDelegatedEvent(el, eventName, modifiers, rawHandler);\n const nodePropName = `__elur_${eventName}`;\n const nodeModsName = `__elur_${eventName}_mods`;\n return () => {\n (el as any)[nodePropName] = null;\n (el as any)[nodeModsName] = null;\n };\n}\n\n/** Returns true if an event name is in the delegable set. */\nexport function isDelegableEvent(eventName: string): boolean {\n return DELEGABLE_EVENTS.has(eventName);\n}\n\n// =============================================================================\n// --- Binding activation ---\n// =============================================================================\n\n/** Activates all bindings on the cloned fragment. Returns dispose/postMount. */\nexport function activateBindings(\n fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n // PHASE 1: READ — single-pass TreeWalker O(N)\n const resolvedNodes = new Array<Node | null>(contexts.length);\n\n let maxNodeIndex = -1;\n for (let i = 0; i < contexts.length; i++) {\n if (pathMap[i] && pathMap[i]!.nodeIndex > maxNodeIndex) {\n maxNodeIndex = pathMap[i]!.nodeIndex;\n }\n }\n\n const flatNodes = new Array<Node>(maxNodeIndex + 1);\n flatNodes[0] = fragment;\n if (maxNodeIndex > 0) {\n const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);\n let fi = 1;\n let currentNode: Node | null;\n while (fi <= maxNodeIndex && (currentNode = walker.nextNode())) {\n flatNodes[fi++] = currentNode;\n }\n }\n\n for (let i = 0; i < contexts.length; i++) {\n const info = pathMap[i];\n resolvedNodes[i] = info ? flatNodes[info.nodeIndex] : null;\n }\n\n // PHASE 2: MUTATE (delegated to _activateBindingsWithNodes)\n return _activateBindingsWithNodes(fragment, contexts, values, pathMap, resolvedNodes);\n}\n\n/**\n * Activates bindings using pre-resolved nodes — skips the TreeWalker phase.\n * Used by the compiler's __elurCompiledTemplate to eliminate the second TreeWalker.\n */\nexport function _activateBindingsWithNodes(\n _fragment: DocumentFragment,\n contexts: BindingContext[],\n values: unknown[],\n pathMap: Array<{ nodeIndex: number; name?: string } | null>,\n resolvedNodes: Array<Node | null>,\n): { disposes: Array<() => void>; postMountHooks: Array<() => void> } {\n const disposes: Array<() => void> = [];\n const postMountHooks: Array<() => void> = [];\n\n for (let i = 0; i < contexts.length; i++) {\n const ctx = contexts[i];\n let value = values[i];\n // C.7 T2: desenvolver el pack derivado a getter — cubre todos los\n // paths aguas abajo (attr, show/hide, prop, etc.) con tracking\n // dinámico — semántica idéntica al getter genérico.\n if (isDerivedBinding(value)) value = value.get;\n const info = pathMap[i];\n if (!info) continue;\n\n const el = resolvedNodes[i]!;\n\n // --- Events ---\n if (ctx.type === \"event\") {\n const eventName = info.name!;\n const rawHandler = value as EventListener;\n const mods = ctx.modifiers;\n\n const canDelegate =\n isDelegableEvent(eventName) &&\n !mods.includes(\"capture\") &&\n !mods.includes(\"once\") &&\n !mods.includes(\"passive\");\n\n if (canDelegate) {\n disposes.push(activateDelegatedEvent(el as Element, eventName, mods, rawHandler));\n } else {\n const listenerOpts: AddEventListenerOptions = {\n once: mods.includes(\"once\"),\n capture: mods.includes(\"capture\"),\n passive: mods.includes(\"passive\")\n };\n const handler = (e: Event) => {\n if (mods.includes(\"prevent\")) e.preventDefault();\n if (mods.includes(\"stop\")) e.stopPropagation();\n if (mods.includes(\"self\") && e.target !== e.currentTarget) return;\n rawHandler(e);\n };\n el.addEventListener(eventName, handler, listenerOpts);\n disposes.push(() => el.removeEventListener(eventName, handler, listenerOpts));\n }\n continue;\n }\n\n // --- Attributes ---\n if (ctx.type === \"attr\") {\n const attrName = info.name!;\n const element = el as Element;\n\n if (attrName === \"ref\") {\n (value as ElurRef<Element>).el = element;\n disposes.push(() => { (value as ElurRef<Element>).el = null; });\n continue;\n }\n\n if (attrName === \"show\" || attrName === \"hide\") {\n const htmlEl = element as HTMLElement;\n let originalDisplay: string | null = null;\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingVisible = false;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingVisible = Boolean((value as () => unknown)());\n const update = () => {\n queued = false;\n const shouldShow = attrName === \"show\" ? pendingVisible : !pendingVisible;\n if (originalDisplay === null) {\n originalDisplay = htmlEl.style.display || \"\";\n }\n htmlEl.style.display = shouldShow ? originalDisplay : \"none\";\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n const shouldShow = attrName === \"show\" ? Boolean(value) : !Boolean(value);\n if (!shouldShow) htmlEl.style.display = \"none\";\n }\n continue;\n }\n\n // on*/srcdoc bindings are non-idiomatic in Elur (events use @click) and\n // turn an untrusted value into executable code. Warn the developer but\n // do not block — the attribute name is developer-authored.\n if (ctx.executable ?? isExecutableAttrName(attrName)) {\n console.warn(\n `[elur] Dynamic binding on executable attribute \"${attrName}\". Use @event for handlers; avoid binding untrusted values here.`,\n );\n }\n\n // Precomputed at compile time. Only URL attributes pay the sanitizer;\n // class/style/aria-*/data-*/custom attributes skip it entirely.\n const isUrl = ctx.url ?? isUrlAttrName(attrName);\n\n const isDomProp = (attrName === \"value\" || attrName === \"checked\" || attrName === \"selected\") && attrName in element;\n\n // C.6 T1: la señal viaja como valor — edge directa, sin getter.\n if (value instanceof Signal) {\n disposes.push(_bindSignal(value, (v) => {\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }));\n continue;\n }\n\n if (typeof value === \"function\") {\n let queued = false;\n let pendingValue: unknown;\n let isFirstRun = true;\n\n const dispose = effect(() => {\n pendingValue = (value as () => unknown)();\n const update = () => {\n queued = false;\n const v = pendingValue;\n if (isDomProp) {\n (element as any)[attrName] = v ?? \"\";\n } else if (v == null || v === false) {\n element.removeAttribute(attrName);\n } else {\n const s = String(v);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n };\n\n if (isFirstRun) {\n isFirstRun = false;\n update();\n } else if (!queued) {\n queued = true;\n queueDOMWrite(update);\n }\n });\n disposes.push(dispose);\n } else {\n if (isDomProp) {\n (element as any)[attrName] = value ?? \"\";\n } else if (value != null && value !== false) {\n const s = String(value);\n element.setAttribute(attrName, isUrl ? sanitizeUrl(s) : s);\n }\n }\n continue;\n }\n\n // --- Nodes — delegate to node-binding.ts ---\n const originalAnchor = el as Comment;\n if (!originalAnchor) continue;\n\n const anchor = document.createTextNode(\"\");\n originalAnchor.parentNode!.replaceChild(anchor, originalAnchor);\n\n activateNodeBinding(anchor, value, disposes, postMountHooks);\n }\n\n return { disposes, postMountHooks };\n}\n"],"mappings":"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"}