@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.
- package/package.json +5 -9
- package/src/delegate.ts +0 -98
- package/src/devtools.ts +0 -339
- package/src/env.d.ts +0 -6
- package/src/hydrate.ts +0 -450
- package/src/hydration-debug.ts +0 -129
- package/src/index.ts +0 -83
- package/src/keep-alive-entry.ts +0 -3
- package/src/keep-alive.ts +0 -83
- package/src/manifest.ts +0 -236
- package/src/mount.ts +0 -597
- package/src/nodes.ts +0 -896
- package/src/props.ts +0 -474
- package/src/template.ts +0 -523
- package/src/tests/callback-ref-unmount.browser.test.ts +0 -62
- package/src/tests/callback-ref-unmount.test.ts +0 -52
- package/src/tests/compiler-integration.test.tsx +0 -508
- package/src/tests/coverage-gaps.test.ts +0 -3183
- package/src/tests/coverage.test.ts +0 -1140
- package/src/tests/ctx-stack-growth-repro.test.tsx +0 -158
- package/src/tests/dev-gate-pattern.test.ts +0 -46
- package/src/tests/dev-gate-treeshake.test.ts +0 -256
- package/src/tests/error-boundary-stack-leak-repro.test.tsx +0 -133
- package/src/tests/fanout-repro.test.tsx +0 -219
- package/src/tests/hydration-integration.test.tsx +0 -540
- package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +0 -140
- package/src/tests/lifecycle-integration.test.tsx +0 -342
- package/src/tests/lis-prepend.browser.test.ts +0 -99
- package/src/tests/manifest-snapshot.test.ts +0 -85
- package/src/tests/mount.test.ts +0 -3529
- package/src/tests/native-markers.test.ts +0 -19
- package/src/tests/props.test.ts +0 -581
- package/src/tests/reactive-props.test.ts +0 -270
- package/src/tests/real-world-integration.test.tsx +0 -714
- package/src/tests/rs-collapse-dyn-h.browser.test.ts +0 -303
- package/src/tests/rs-collapse-dyn.browser.test.ts +0 -316
- package/src/tests/rs-collapse-h.browser.test.ts +0 -152
- package/src/tests/rs-collapse-h.test.ts +0 -237
- package/src/tests/rs-collapse.browser.test.ts +0 -128
- package/src/tests/runtime-dom.browser.test.ts +0 -409
- package/src/tests/setup.ts +0 -3
- package/src/tests/show-context.test.ts +0 -270
- package/src/tests/show-of-for-batched-toggle.browser.test.ts +0 -122
- package/src/tests/ssr-xss-round-trip.browser.test.ts +0 -93
- package/src/tests/style-key-removal.browser.test.ts +0 -54
- package/src/tests/style-key-removal.test.ts +0 -88
- package/src/tests/template.test.ts +0 -383
- package/src/tests/transition-timeout-leak.test.ts +0 -126
- package/src/tests/transition.test.ts +0 -568
- package/src/tests/verified-correct-probes.test.ts +0 -56
- package/src/transition-entry.ts +0 -7
- package/src/transition-group.ts +0 -350
- 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.
|
|
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.
|
|
58
|
-
"@pyreon/reactivity": "^0.24.
|
|
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.
|
|
58
|
+
"@pyreon/compiler": "^0.24.6",
|
|
63
59
|
"@pyreon/manifest": "0.13.1",
|
|
64
|
-
"@pyreon/runtime-server": "^0.24.
|
|
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