@pyreon/runtime-dom 0.24.5 → 0.24.6

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 (53) hide show
  1. package/package.json +5 -9
  2. package/src/delegate.ts +0 -98
  3. package/src/devtools.ts +0 -339
  4. package/src/env.d.ts +0 -6
  5. package/src/hydrate.ts +0 -450
  6. package/src/hydration-debug.ts +0 -129
  7. package/src/index.ts +0 -83
  8. package/src/keep-alive-entry.ts +0 -3
  9. package/src/keep-alive.ts +0 -83
  10. package/src/manifest.ts +0 -236
  11. package/src/mount.ts +0 -597
  12. package/src/nodes.ts +0 -896
  13. package/src/props.ts +0 -474
  14. package/src/template.ts +0 -523
  15. package/src/tests/callback-ref-unmount.browser.test.ts +0 -62
  16. package/src/tests/callback-ref-unmount.test.ts +0 -52
  17. package/src/tests/compiler-integration.test.tsx +0 -508
  18. package/src/tests/coverage-gaps.test.ts +0 -3183
  19. package/src/tests/coverage.test.ts +0 -1140
  20. package/src/tests/ctx-stack-growth-repro.test.tsx +0 -158
  21. package/src/tests/dev-gate-pattern.test.ts +0 -46
  22. package/src/tests/dev-gate-treeshake.test.ts +0 -256
  23. package/src/tests/error-boundary-stack-leak-repro.test.tsx +0 -133
  24. package/src/tests/fanout-repro.test.tsx +0 -219
  25. package/src/tests/hydration-integration.test.tsx +0 -540
  26. package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +0 -140
  27. package/src/tests/lifecycle-integration.test.tsx +0 -342
  28. package/src/tests/lis-prepend.browser.test.ts +0 -99
  29. package/src/tests/manifest-snapshot.test.ts +0 -85
  30. package/src/tests/mount.test.ts +0 -3529
  31. package/src/tests/native-markers.test.ts +0 -19
  32. package/src/tests/props.test.ts +0 -581
  33. package/src/tests/reactive-props.test.ts +0 -270
  34. package/src/tests/real-world-integration.test.tsx +0 -714
  35. package/src/tests/rs-collapse-dyn-h.browser.test.ts +0 -303
  36. package/src/tests/rs-collapse-dyn.browser.test.ts +0 -316
  37. package/src/tests/rs-collapse-h.browser.test.ts +0 -152
  38. package/src/tests/rs-collapse-h.test.ts +0 -237
  39. package/src/tests/rs-collapse.browser.test.ts +0 -128
  40. package/src/tests/runtime-dom.browser.test.ts +0 -409
  41. package/src/tests/setup.ts +0 -3
  42. package/src/tests/show-context.test.ts +0 -270
  43. package/src/tests/show-of-for-batched-toggle.browser.test.ts +0 -122
  44. package/src/tests/ssr-xss-round-trip.browser.test.ts +0 -93
  45. package/src/tests/style-key-removal.browser.test.ts +0 -54
  46. package/src/tests/style-key-removal.test.ts +0 -88
  47. package/src/tests/template.test.ts +0 -383
  48. package/src/tests/transition-timeout-leak.test.ts +0 -126
  49. package/src/tests/transition.test.ts +0 -568
  50. package/src/tests/verified-correct-probes.test.ts +0 -56
  51. package/src/transition-entry.ts +0 -7
  52. package/src/transition-group.ts +0 -350
  53. package/src/transition.ts +0 -245
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/runtime-dom",
3
- "version": "0.24.5",
3
+ "version": "0.24.6",
4
4
  "description": "DOM renderer for Pyreon",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/runtime-dom#readme",
6
6
  "bugs": {
@@ -15,7 +15,6 @@
15
15
  "files": [
16
16
  "lib",
17
17
  "!lib/**/*.map",
18
- "src",
19
18
  "README.md",
20
19
  "LICENSE"
21
20
  ],
@@ -26,17 +25,14 @@
26
25
  "types": "./lib/types/index.d.ts",
27
26
  "exports": {
28
27
  ".": {
29
- "bun": "./src/index.ts",
30
28
  "import": "./lib/index.js",
31
29
  "types": "./lib/types/index.d.ts"
32
30
  },
33
31
  "./transition": {
34
- "bun": "./src/transition-entry.ts",
35
32
  "import": "./lib/transition-entry.js",
36
33
  "types": "./lib/types/transition-entry.d.ts"
37
34
  },
38
35
  "./keep-alive": {
39
- "bun": "./src/keep-alive-entry.ts",
40
36
  "import": "./lib/keep-alive-entry.js",
41
37
  "types": "./lib/types/keep-alive-entry.d.ts"
42
38
  }
@@ -54,14 +50,14 @@
54
50
  "prepublishOnly": "bun run build"
55
51
  },
56
52
  "dependencies": {
57
- "@pyreon/core": "^0.24.5",
58
- "@pyreon/reactivity": "^0.24.5"
53
+ "@pyreon/core": "^0.24.6",
54
+ "@pyreon/reactivity": "^0.24.6"
59
55
  },
60
56
  "devDependencies": {
61
57
  "@happy-dom/global-registrator": "^20.8.9",
62
- "@pyreon/compiler": "^0.24.5",
58
+ "@pyreon/compiler": "^0.24.6",
63
59
  "@pyreon/manifest": "0.13.1",
64
- "@pyreon/runtime-server": "^0.24.5",
60
+ "@pyreon/runtime-server": "^0.24.6",
65
61
  "@pyreon/test-utils": "^0.13.11",
66
62
  "@vitest/browser-playwright": "^4.1.4",
67
63
  "esbuild": "^0.28.0",
package/src/delegate.ts DELETED
@@ -1,98 +0,0 @@
1
- /**
2
- * Event delegation — single listener per event type on the mount container.
3
- *
4
- * Instead of calling addEventListener on every element, the compiler emits
5
- * `el.__click = handler` (expando property). A single delegated listener on the
6
- * container walks event.target up the DOM tree, checking for expandos.
7
- *
8
- * Benefits:
9
- * - Saves ~2000 addEventListener calls for 1000 rows with 2 handlers each
10
- * - Reduces memory per row (no per-element listener closure)
11
- * - Faster initial mount (~0.4-0.8ms savings on 1000-row benchmarks)
12
- */
13
-
14
- import { batch } from '@pyreon/reactivity'
15
-
16
- /**
17
- * Events that are delegated (common bubbling events).
18
- * Non-bubbling events (focus, blur, mouseenter, mouseleave, load, error, scroll)
19
- * are NOT delegated — they must use addEventListener.
20
- */
21
- export const DELEGATED_EVENTS = new Set([
22
- 'click',
23
- 'dblclick',
24
- 'contextmenu',
25
- 'focusin',
26
- 'focusout',
27
- 'input',
28
- 'change',
29
- 'keydown',
30
- 'keyup',
31
- 'mousedown',
32
- 'mouseup',
33
- 'mousemove',
34
- 'mouseover',
35
- 'mouseout',
36
- 'pointerdown',
37
- 'pointerup',
38
- 'pointermove',
39
- 'pointerover',
40
- 'pointerout',
41
- 'touchstart',
42
- 'touchend',
43
- 'touchmove',
44
- 'submit',
45
- ])
46
-
47
- /**
48
- * Property name used on DOM elements to store delegated event handlers.
49
- * Format: `__ev_{eventName}` e.g. `__ev_click`, `__ev_input`
50
- */
51
- export function delegatedPropName(eventName: string): string {
52
- return `__ev_${eventName}`
53
- }
54
-
55
- // Track which containers already have delegation installed
56
- const _delegated = new WeakSet<Element>()
57
-
58
- /**
59
- * Install delegation listeners on a container element.
60
- * Called once from mount(). Idempotent — safe to call multiple times.
61
- */
62
- export function setupDelegation(container: Element): void {
63
- if (_delegated.has(container)) return
64
- _delegated.add(container)
65
-
66
- for (const eventName of DELEGATED_EVENTS) {
67
- const prop = delegatedPropName(eventName)
68
- container.addEventListener(eventName, (e: Event) => {
69
- let el = e.target as (HTMLElement & Record<string, unknown>) | null
70
- while (el && el !== container) {
71
- const handler = el[prop]
72
- if (typeof handler === 'function') {
73
- // Per-handler `currentTarget` patch: native event delegation leaves
74
- // `e.currentTarget` as the container (the listener root). Without
75
- // this override, `ev.currentTarget.value` in user code reads from
76
- // the container — silently `undefined` for inputs, the wrong tag
77
- // type, etc. Pyreon's `TargetedEvent<E>` type *promises* the
78
- // matched element; this override makes the runtime keep that
79
- // promise, matching what React, Vue, and Solid all do for
80
- // delegated events.
81
- //
82
- // `currentTarget` is a read-only accessor on native Event types,
83
- // so direct assignment is silently ignored — `Object.defineProperty`
84
- // with `configurable: true` is the only portable override.
85
- Object.defineProperty(e, 'currentTarget', {
86
- value: el,
87
- configurable: true,
88
- })
89
- batch(() => handler(e))
90
- // Don't break — allow ancestor handlers too (consistent with addEventListener)
91
- // But if stopPropagation was called, stop walking
92
- if (e.cancelBubble) break
93
- }
94
- el = el.parentElement as (HTMLElement & Record<string, unknown>) | null
95
- }
96
- })
97
- }
98
- }
package/src/devtools.ts DELETED
@@ -1,339 +0,0 @@
1
- /**
2
- * Pyreon DevTools — exposes a `__PYREON_DEVTOOLS__` global hook for browser devtools extensions
3
- * and in-app debugging utilities.
4
- *
5
- * Installed automatically on first `mount()` call in the browser.
6
- * No-op on the server (typeof window === "undefined").
7
- *
8
- * Usage:
9
- * window.__PYREON_DEVTOOLS__.getComponentTree() // root component entries
10
- * window.__PYREON_DEVTOOLS__.getAllComponents() // flat list of all live components
11
- * window.__PYREON_DEVTOOLS__.highlight("comp-id") // outline a component's DOM node
12
- * window.__PYREON_DEVTOOLS__.onComponentMount(cb) // subscribe to mount events
13
- * window.__PYREON_DEVTOOLS__.onComponentUnmount(cb)// subscribe to unmount events
14
- * window.__PYREON_DEVTOOLS__.enableOverlay() // Ctrl+Shift+P: hover to inspect components
15
- * window.__PYREON_DEVTOOLS__.reactive.activate() // opt-in: track the live signal/effect graph
16
- * window.__PYREON_DEVTOOLS__.reactive.getGraph() // snapshot of signals/derived/effects + edges
17
- */
18
-
19
- import {
20
- activateReactiveDevtools,
21
- deactivateReactiveDevtools,
22
- getReactiveFires,
23
- getReactiveGraph,
24
- type ReactiveFire,
25
- type ReactiveGraph,
26
- } from '@pyreon/reactivity'
27
-
28
- export interface DevtoolsComponentEntry {
29
- id: string
30
- name: string
31
- /** First DOM element produced by this component, if any */
32
- el: Element | null
33
- parentId: string | null
34
- childIds: string[]
35
- }
36
-
37
- export interface PyreonDevtools {
38
- readonly version: string
39
- getComponentTree(): DevtoolsComponentEntry[]
40
- getAllComponents(): DevtoolsComponentEntry[]
41
- highlight(id: string): void
42
- onComponentMount(cb: (entry: DevtoolsComponentEntry) => void): () => void
43
- onComponentUnmount(cb: (id: string) => void): () => void
44
- /** Toggle the component inspector overlay (also: Ctrl+Shift+P) */
45
- enableOverlay(): void
46
- disableOverlay(): void
47
- /**
48
- * Reactive-graph bridge — powers the devtools Signals / Graph / Effects
49
- * surfaces. Opt-in and zero-cost until `activate()` is called: nothing
50
- * is tracked while a devtools client is not attached.
51
- */
52
- reactive: PyreonReactiveDevtools
53
- }
54
-
55
- export interface PyreonReactiveDevtools {
56
- /** Start tracking the live signal/computed/effect graph. Idempotent. */
57
- activate(): void
58
- /** Stop tracking + drop all retained registry/timeline state. */
59
- deactivate(): void
60
- /** Fresh snapshot of the reactive graph (nodes + edges). */
61
- getGraph(): ReactiveGraph
62
- /** Bounded recent-fire timeline (oldest → newest). */
63
- getFires(): ReactiveFire[]
64
- }
65
-
66
- // ─── Internal registry ────────────────────────────────────────────────────────
67
-
68
- const _components = new Map<string, DevtoolsComponentEntry>()
69
- const _mountListeners: ((entry: DevtoolsComponentEntry) => void)[] = []
70
- const _unmountListeners: ((id: string) => void)[] = []
71
-
72
- export function registerComponent(
73
- id: string,
74
- name: string,
75
- el: Element | null,
76
- parentId: string | null,
77
- ): void {
78
- const entry: DevtoolsComponentEntry = { id, name, el, parentId, childIds: [] }
79
- _components.set(id, entry)
80
- if (parentId) {
81
- const parent = _components.get(parentId)
82
- if (parent) parent.childIds.push(id)
83
- }
84
- for (const cb of _mountListeners) cb(entry)
85
- }
86
-
87
- export function unregisterComponent(id: string): void {
88
- const entry = _components.get(id)
89
- if (!entry) return
90
- if (entry.parentId) {
91
- const parent = _components.get(entry.parentId)
92
- if (parent) parent.childIds = parent.childIds.filter((c) => c !== id)
93
- }
94
- _components.delete(id)
95
- for (const cb of _unmountListeners) cb(id)
96
- }
97
-
98
- // ─── Component Inspector Overlay ─────────────────────────────────────────────
99
-
100
- let _overlayActive = false
101
- let _overlayEl: HTMLDivElement | null = null
102
- let _tooltipEl: HTMLDivElement | null = null
103
- let _currentHighlight: Element | null = null
104
-
105
- function findComponentForElement(el: Element): DevtoolsComponentEntry | null {
106
- // Walk up from the hovered element to find the nearest registered component
107
- let node: Element | null = el
108
- while (node) {
109
- for (const entry of _components.values()) {
110
- if (entry.el === node) return entry
111
- }
112
- node = node.parentElement
113
- }
114
- return null
115
- }
116
-
117
- function createOverlayElements(): void {
118
- if (_overlayEl) return
119
-
120
- _overlayEl = document.createElement('div')
121
- _overlayEl.id = '__pyreon-overlay'
122
- _overlayEl.style.cssText =
123
- 'position:fixed;pointer-events:none;border:2px solid #00b4d8;border-radius:3px;z-index:999999;display:none;transition:all 0.08s ease-out;'
124
-
125
- _tooltipEl = document.createElement('div')
126
- _tooltipEl.style.cssText =
127
- 'position:fixed;pointer-events:none;background:#1a1a2e;color:#e0e0e0;font:12px/1.4 ui-monospace,monospace;padding:6px 10px;border-radius:4px;z-index:999999;display:none;box-shadow:0 2px 8px rgba(0,0,0,0.3);max-width:400px;white-space:pre-wrap;'
128
-
129
- document.body.appendChild(_overlayEl)
130
- document.body.appendChild(_tooltipEl)
131
- }
132
-
133
- function positionOverlay(rect: DOMRect): void {
134
- if (!_overlayEl) return
135
- _overlayEl.style.display = 'block'
136
- _overlayEl.style.top = `${rect.top}px`
137
- _overlayEl.style.left = `${rect.left}px`
138
- _overlayEl.style.width = `${rect.width}px`
139
- _overlayEl.style.height = `${rect.height}px`
140
- }
141
-
142
- function positionTooltip(entry: DevtoolsComponentEntry, rect: DOMRect): void {
143
- if (!_tooltipEl) return
144
- const childCount = entry.childIds.length
145
- let info = `<${entry.name}>`
146
- if (childCount > 0) info += `\n ${childCount} child component${childCount === 1 ? '' : 's'}`
147
- _tooltipEl.textContent = info
148
- _tooltipEl.style.display = 'block'
149
- _tooltipEl.style.top = `${rect.top - 30}px`
150
- _tooltipEl.style.left = `${rect.left}px`
151
- if (rect.top < 35) {
152
- _tooltipEl.style.top = `${rect.bottom + 4}px`
153
- }
154
- }
155
-
156
- function hideOverlayElements(): void {
157
- if (_overlayEl) _overlayEl.style.display = 'none'
158
- if (_tooltipEl) _tooltipEl.style.display = 'none'
159
- _currentHighlight = null
160
- }
161
-
162
- /** @internal — exported for testing only */
163
- export function onOverlayMouseMove(e: MouseEvent): void {
164
- const target = document.elementFromPoint(e.clientX, e.clientY)
165
- if (!target || target === _overlayEl || target === _tooltipEl) return
166
-
167
- const entry = findComponentForElement(target)
168
- if (!entry?.el) {
169
- hideOverlayElements()
170
- return
171
- }
172
-
173
- if (entry.el === _currentHighlight) return
174
- _currentHighlight = entry.el
175
-
176
- const rect = entry.el.getBoundingClientRect()
177
- positionOverlay(rect)
178
- positionTooltip(entry, rect)
179
- }
180
-
181
- /** @internal — exported for testing only */
182
- export function onOverlayClick(e: MouseEvent): void {
183
- e.preventDefault()
184
- e.stopPropagation()
185
- const target = document.elementFromPoint(e.clientX, e.clientY)
186
- if (!target) return
187
- const entry = findComponentForElement(target)
188
- if (entry) {
189
- console.group(`[Pyreon] <${entry.name}>`)
190
- console.log('element:', entry.el)
191
- console.log('children:', entry.childIds.length)
192
- if (entry.parentId) {
193
- const parent = _components.get(entry.parentId)
194
- if (parent) {
195
- console.log('parent:', `<${parent.name}>`)
196
- }
197
- }
198
- console.groupEnd()
199
- }
200
- disableOverlay()
201
- }
202
-
203
- function onOverlayKeydown(e: KeyboardEvent): void {
204
- if (e.key === 'Escape') {
205
- disableOverlay()
206
- }
207
- }
208
-
209
- function enableOverlay(): void {
210
- if (_overlayActive) return
211
- _overlayActive = true
212
- createOverlayElements()
213
- document.addEventListener('mousemove', onOverlayMouseMove, true)
214
- document.addEventListener('click', onOverlayClick, true)
215
- document.addEventListener('keydown', onOverlayKeydown, true)
216
- document.body.style.cursor = 'crosshair'
217
- }
218
-
219
- function disableOverlay(): void {
220
- if (!_overlayActive) return
221
- _overlayActive = false
222
- document.removeEventListener('mousemove', onOverlayMouseMove, true)
223
- document.removeEventListener('click', onOverlayClick, true)
224
- document.removeEventListener('keydown', onOverlayKeydown, true)
225
- document.body.style.cursor = ''
226
- if (_overlayEl) _overlayEl.style.display = 'none'
227
- if (_tooltipEl) _tooltipEl.style.display = 'none'
228
- _currentHighlight = null
229
- }
230
-
231
- // ─── Installation ─────────────────────────────────────────────────────────────
232
-
233
- let _installed = false
234
- // Resolved once at module load — avoids per-call typeof branch in coverage
235
- const _hasWindow = typeof window !== 'undefined'
236
-
237
- export function installDevTools(): void {
238
- if (!_hasWindow || _installed) return
239
- _installed = true
240
-
241
- const devtools: PyreonDevtools = {
242
- version: '0.1.0',
243
-
244
- getComponentTree() {
245
- return Array.from(_components.values()).filter((e) => e.parentId === null)
246
- },
247
-
248
- getAllComponents() {
249
- return Array.from(_components.values())
250
- },
251
-
252
- highlight(id: string) {
253
- const entry = _components.get(id)
254
- if (!entry?.el) return
255
- const el = entry.el as HTMLElement
256
- const prev = el.style.outline
257
- el.style.outline = '2px solid #00b4d8'
258
- setTimeout(() => {
259
- el.style.outline = prev
260
- }, 1500)
261
- },
262
-
263
- onComponentMount(cb: (entry: DevtoolsComponentEntry) => void): () => void {
264
- _mountListeners.push(cb)
265
- return () => {
266
- const i = _mountListeners.indexOf(cb)
267
- if (i >= 0) _mountListeners.splice(i, 1)
268
- }
269
- },
270
-
271
- onComponentUnmount(cb: (id: string) => void): () => void {
272
- _unmountListeners.push(cb)
273
- return () => {
274
- const i = _unmountListeners.indexOf(cb)
275
- if (i >= 0) _unmountListeners.splice(i, 1)
276
- }
277
- },
278
-
279
- enableOverlay,
280
- disableOverlay,
281
-
282
- reactive: {
283
- activate: activateReactiveDevtools,
284
- deactivate: deactivateReactiveDevtools,
285
- getGraph: getReactiveGraph,
286
- getFires: getReactiveFires,
287
- },
288
- }
289
-
290
- // Attach to window — compatible with browser devtools extensions
291
- ;(window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ = devtools
292
-
293
- // Ctrl+Shift+P toggles the component inspector overlay
294
- window.addEventListener('keydown', (e) => {
295
- if (e.ctrlKey && e.shiftKey && e.key === 'P') {
296
- e.preventDefault()
297
- if (_overlayActive) disableOverlay()
298
- else enableOverlay()
299
- }
300
- })
301
-
302
- // ── $p console helper ────────────────────────────────────────────────────
303
- // Type `$p` in the browser console for quick access to Pyreon debug tools.
304
- const win = window as unknown as Record<string, unknown>
305
- win.$p = {
306
- /** List all mounted components */
307
- components: () => devtools.getAllComponents(),
308
- /** Component tree (roots only) */
309
- tree: () => devtools.getComponentTree(),
310
- /** Highlight a component by id */
311
- highlight: (id: string) => devtools.highlight(id),
312
- /** Toggle component inspector overlay */
313
- inspect: () => {
314
- if (_overlayActive) disableOverlay()
315
- else enableOverlay()
316
- },
317
- /** Print component count */
318
- stats: () => {
319
- const all = devtools.getAllComponents()
320
- const roots = devtools.getComponentTree()
321
- console.log(
322
- `[Pyreon] ${all.length} component${all.length === 1 ? '' : 's'}, ${roots.length} root${roots.length === 1 ? '' : 's'}`,
323
- )
324
- return { total: all.length, roots: roots.length }
325
- },
326
- /** Quick help */
327
- help: () => {
328
- console.log(
329
- '[Pyreon] $p commands:\n' +
330
- ' $p.components() — list all mounted components\n' +
331
- ' $p.tree() — component tree (roots only)\n' +
332
- ' $p.highlight(id)— outline a component\n' +
333
- ' $p.inspect() — toggle component inspector\n' +
334
- ' $p.stats() — print component count\n' +
335
- ' $p.help() — this message',
336
- )
337
- },
338
- }
339
- }
package/src/env.d.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Minimal process type — just enough for `process.env.NODE_ENV` checks.
3
- * Avoids requiring @types/node in consumers that import pyreon source
4
- * via the `"bun"` export condition.
5
- */
6
- declare var process: { env: { NODE_ENV?: string } }