@eyeglass/inspector 0.1.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.
- package/dist/fiber-walker.d.ts +15 -0
- package/dist/fiber-walker.js +223 -0
- package/dist/fiber-walker.test.d.ts +1 -0
- package/dist/fiber-walker.test.js +252 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +33 -0
- package/dist/inspector.d.ts +83 -0
- package/dist/inspector.js +1932 -0
- package/dist/snapshot.d.ts +8 -0
- package/dist/snapshot.js +192 -0
- package/dist/snapshot.test.d.ts +1 -0
- package/dist/snapshot.test.js +283 -0
- package/package.json +38 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Fiber Walker - extracts component info from React DevTools internals
|
|
3
|
+
*/
|
|
4
|
+
export interface FrameworkInfo {
|
|
5
|
+
name: 'react' | 'vue' | 'svelte' | 'vanilla';
|
|
6
|
+
componentName?: string;
|
|
7
|
+
filePath?: string;
|
|
8
|
+
lineNumber?: number;
|
|
9
|
+
props?: Record<string, unknown>;
|
|
10
|
+
ancestry?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract framework information from a DOM element
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractFrameworkInfo(element: Element): FrameworkInfo;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Fiber Walker - extracts component info from React DevTools internals
|
|
3
|
+
*/
|
|
4
|
+
// Fiber tag constants
|
|
5
|
+
const FunctionComponent = 0;
|
|
6
|
+
const ClassComponent = 1;
|
|
7
|
+
const ForwardRef = 11;
|
|
8
|
+
const MemoComponent = 14;
|
|
9
|
+
const SimpleMemoComponent = 15;
|
|
10
|
+
const COMPONENT_TAGS = new Set([
|
|
11
|
+
FunctionComponent,
|
|
12
|
+
ClassComponent,
|
|
13
|
+
ForwardRef,
|
|
14
|
+
MemoComponent,
|
|
15
|
+
SimpleMemoComponent,
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Find the React Fiber attached to a DOM element
|
|
19
|
+
*/
|
|
20
|
+
function getFiberFromElement(element) {
|
|
21
|
+
const keys = Object.keys(element);
|
|
22
|
+
const fiberKey = keys.find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
|
|
23
|
+
if (!fiberKey)
|
|
24
|
+
return null;
|
|
25
|
+
return element[fiberKey];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a fiber is a user-defined component (not built-in React internals)
|
|
29
|
+
*/
|
|
30
|
+
function isUserComponent(fiber) {
|
|
31
|
+
if (!COMPONENT_TAGS.has(fiber.tag) || typeof fiber.type !== 'function') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const name = fiber.type.displayName || fiber.type.name || '';
|
|
35
|
+
// Skip built-in React components (Context.Provider, StrictMode, etc.)
|
|
36
|
+
if (!name || name.startsWith('Context') || name.endsWith('Provider') || name === 'StrictMode') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get component name from fiber
|
|
43
|
+
*/
|
|
44
|
+
function getComponentName(fiber) {
|
|
45
|
+
return fiber.type.displayName || fiber.type.name || undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Walk up the fiber tree to find the nearest user-defined component
|
|
49
|
+
*/
|
|
50
|
+
function findComponentFiber(fiber) {
|
|
51
|
+
let current = fiber;
|
|
52
|
+
while (current) {
|
|
53
|
+
if (isUserComponent(current)) {
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
56
|
+
current = current.return;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Collect all parent component names by walking up the fiber tree
|
|
62
|
+
*/
|
|
63
|
+
function collectAncestry(fiber) {
|
|
64
|
+
const ancestry = [];
|
|
65
|
+
let current = fiber;
|
|
66
|
+
while (current) {
|
|
67
|
+
if (isUserComponent(current)) {
|
|
68
|
+
const name = getComponentName(current);
|
|
69
|
+
if (name) {
|
|
70
|
+
ancestry.push(name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
current = current.return;
|
|
74
|
+
}
|
|
75
|
+
return ancestry;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get safe props (primitives only, no functions/objects)
|
|
79
|
+
*/
|
|
80
|
+
function getSafeProps(props) {
|
|
81
|
+
if (!props)
|
|
82
|
+
return undefined;
|
|
83
|
+
const safe = {};
|
|
84
|
+
for (const [key, value] of Object.entries(props)) {
|
|
85
|
+
if (key === 'children')
|
|
86
|
+
continue;
|
|
87
|
+
const type = typeof value;
|
|
88
|
+
if (type === 'string' || type === 'number' || type === 'boolean' || value === null) {
|
|
89
|
+
safe[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Object.keys(safe).length > 0 ? safe : undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect Vue component
|
|
96
|
+
*/
|
|
97
|
+
function detectVue(element) {
|
|
98
|
+
// Vue 2
|
|
99
|
+
const vueInstance = element.__vue__;
|
|
100
|
+
if (vueInstance) {
|
|
101
|
+
const componentName = vueInstance.$options?.name || vueInstance.$options?._componentTag;
|
|
102
|
+
return {
|
|
103
|
+
name: 'vue',
|
|
104
|
+
componentName,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Vue 3 - check for any __vue* property
|
|
108
|
+
const keys = Object.keys(element);
|
|
109
|
+
const vueKey = keys.find((k) => k.startsWith('__vue'));
|
|
110
|
+
if (vueKey) {
|
|
111
|
+
const vnode = element[vueKey];
|
|
112
|
+
// Try to get component info from vnode
|
|
113
|
+
if (vnode) {
|
|
114
|
+
// Walk up to find component instance
|
|
115
|
+
let instance = vnode;
|
|
116
|
+
let componentName;
|
|
117
|
+
let props;
|
|
118
|
+
// Check various Vue 3 internal structures
|
|
119
|
+
if (instance?.type?.name) {
|
|
120
|
+
componentName = instance.type.name;
|
|
121
|
+
}
|
|
122
|
+
else if (instance?.type?.__name) {
|
|
123
|
+
componentName = instance.type.__name;
|
|
124
|
+
}
|
|
125
|
+
else if (instance?.component?.type?.name) {
|
|
126
|
+
componentName = instance.component.type.name;
|
|
127
|
+
}
|
|
128
|
+
else if (instance?.component?.type?.__name) {
|
|
129
|
+
componentName = instance.component.type.__name;
|
|
130
|
+
}
|
|
131
|
+
// Try to get props
|
|
132
|
+
const rawProps = instance?.props || instance?.component?.props;
|
|
133
|
+
if (rawProps) {
|
|
134
|
+
props = getSafeProps(rawProps);
|
|
135
|
+
}
|
|
136
|
+
// Try to get file info from __file
|
|
137
|
+
let filePath;
|
|
138
|
+
if (instance?.type?.__file) {
|
|
139
|
+
filePath = instance.type.__file;
|
|
140
|
+
}
|
|
141
|
+
else if (instance?.component?.type?.__file) {
|
|
142
|
+
filePath = instance.component.type.__file;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
name: 'vue',
|
|
146
|
+
componentName,
|
|
147
|
+
filePath,
|
|
148
|
+
props,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { name: 'vue' };
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Detect Svelte component
|
|
157
|
+
*/
|
|
158
|
+
function detectSvelte(element) {
|
|
159
|
+
const keys = Object.keys(element);
|
|
160
|
+
const svelteKey = keys.find((k) => k.startsWith('__svelte'));
|
|
161
|
+
if (svelteKey) {
|
|
162
|
+
const svelteData = element[svelteKey];
|
|
163
|
+
// Try to extract component info from Svelte internals
|
|
164
|
+
let componentName;
|
|
165
|
+
// Svelte 5 uses different structure
|
|
166
|
+
if (svelteData?.constructor?.name && svelteData.constructor.name !== 'Object') {
|
|
167
|
+
componentName = svelteData.constructor.name;
|
|
168
|
+
}
|
|
169
|
+
// Check for component context
|
|
170
|
+
if (!componentName && svelteData?.$$?.ctx) {
|
|
171
|
+
// Svelte 4 structure
|
|
172
|
+
const ctx = svelteData.$$.ctx;
|
|
173
|
+
if (ctx?.constructor?.name && ctx.constructor.name !== 'Object') {
|
|
174
|
+
componentName = ctx.constructor.name;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
name: 'svelte',
|
|
179
|
+
componentName,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Extract framework information from a DOM element
|
|
186
|
+
*/
|
|
187
|
+
export function extractFrameworkInfo(element) {
|
|
188
|
+
// Try React first
|
|
189
|
+
const fiber = getFiberFromElement(element);
|
|
190
|
+
if (fiber) {
|
|
191
|
+
const componentFiber = findComponentFiber(fiber);
|
|
192
|
+
if (componentFiber) {
|
|
193
|
+
const componentName = getComponentName(componentFiber);
|
|
194
|
+
const debugSource = componentFiber._debugSource;
|
|
195
|
+
const ancestry = collectAncestry(componentFiber);
|
|
196
|
+
return {
|
|
197
|
+
name: 'react',
|
|
198
|
+
componentName,
|
|
199
|
+
filePath: debugSource?.fileName,
|
|
200
|
+
lineNumber: debugSource?.lineNumber,
|
|
201
|
+
props: getSafeProps(componentFiber.memoizedProps),
|
|
202
|
+
ancestry: ancestry.length > 0 ? ancestry : undefined,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// React element but no user component found (just DOM nodes)
|
|
206
|
+
// Still try to get ancestry from the fiber
|
|
207
|
+
const ancestry = collectAncestry(fiber);
|
|
208
|
+
return {
|
|
209
|
+
name: 'react',
|
|
210
|
+
ancestry: ancestry.length > 0 ? ancestry : undefined,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// Try Vue
|
|
214
|
+
const vueInfo = detectVue(element);
|
|
215
|
+
if (vueInfo)
|
|
216
|
+
return vueInfo;
|
|
217
|
+
// Try Svelte
|
|
218
|
+
const svelteInfo = detectSvelte(element);
|
|
219
|
+
if (svelteInfo)
|
|
220
|
+
return svelteInfo;
|
|
221
|
+
// Vanilla fallback
|
|
222
|
+
return { name: 'vanilla' };
|
|
223
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { extractFrameworkInfo } from './fiber-walker.js';
|
|
3
|
+
// Helper to create a mock function with a displayName
|
|
4
|
+
function createMockComponent(name) {
|
|
5
|
+
const fn = function () { };
|
|
6
|
+
fn.displayName = name;
|
|
7
|
+
return fn;
|
|
8
|
+
}
|
|
9
|
+
describe('extractFrameworkInfo', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
document.body.innerHTML = '';
|
|
12
|
+
});
|
|
13
|
+
describe('vanilla detection', () => {
|
|
14
|
+
it('should return vanilla for plain DOM elements', () => {
|
|
15
|
+
const div = document.createElement('div');
|
|
16
|
+
document.body.appendChild(div);
|
|
17
|
+
const info = extractFrameworkInfo(div);
|
|
18
|
+
expect(info.name).toBe('vanilla');
|
|
19
|
+
expect(info.componentName).toBeUndefined();
|
|
20
|
+
expect(info.filePath).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('React detection', () => {
|
|
24
|
+
it('should detect React from __reactFiber$ key', () => {
|
|
25
|
+
const div = document.createElement('div');
|
|
26
|
+
document.body.appendChild(div);
|
|
27
|
+
// Mock React fiber structure
|
|
28
|
+
const mockFiber = {
|
|
29
|
+
tag: 0, // FunctionComponent
|
|
30
|
+
type: createMockComponent('MyComponent'),
|
|
31
|
+
return: null,
|
|
32
|
+
_debugSource: {
|
|
33
|
+
fileName: 'src/components/MyComponent.tsx',
|
|
34
|
+
lineNumber: 42,
|
|
35
|
+
},
|
|
36
|
+
memoizedProps: {
|
|
37
|
+
className: 'test',
|
|
38
|
+
onClick: () => { },
|
|
39
|
+
isActive: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
div['__reactFiber$abc123'] = mockFiber;
|
|
43
|
+
const info = extractFrameworkInfo(div);
|
|
44
|
+
expect(info.name).toBe('react');
|
|
45
|
+
expect(info.componentName).toBe('MyComponent');
|
|
46
|
+
expect(info.filePath).toBe('src/components/MyComponent.tsx');
|
|
47
|
+
expect(info.lineNumber).toBe(42);
|
|
48
|
+
});
|
|
49
|
+
it('should detect React from __reactInternalInstance$ key', () => {
|
|
50
|
+
const div = document.createElement('div');
|
|
51
|
+
document.body.appendChild(div);
|
|
52
|
+
const mockFiber = {
|
|
53
|
+
tag: 0,
|
|
54
|
+
type: createMockComponent('Button'),
|
|
55
|
+
return: null,
|
|
56
|
+
};
|
|
57
|
+
div['__reactInternalInstance$xyz789'] = mockFiber;
|
|
58
|
+
const info = extractFrameworkInfo(div);
|
|
59
|
+
expect(info.name).toBe('react');
|
|
60
|
+
expect(info.componentName).toBe('Button');
|
|
61
|
+
});
|
|
62
|
+
it('should extract safe props (primitives only)', () => {
|
|
63
|
+
const div = document.createElement('div');
|
|
64
|
+
document.body.appendChild(div);
|
|
65
|
+
const mockFiber = {
|
|
66
|
+
tag: 0,
|
|
67
|
+
type: createMockComponent('Card'),
|
|
68
|
+
return: null,
|
|
69
|
+
memoizedProps: {
|
|
70
|
+
title: 'Hello',
|
|
71
|
+
count: 42,
|
|
72
|
+
isOpen: true,
|
|
73
|
+
data: null,
|
|
74
|
+
children: ['some', 'children'],
|
|
75
|
+
onClick: () => { },
|
|
76
|
+
config: { nested: 'object' },
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
div['__reactFiber$test'] = mockFiber;
|
|
80
|
+
const info = extractFrameworkInfo(div);
|
|
81
|
+
expect(info.props).toEqual({
|
|
82
|
+
title: 'Hello',
|
|
83
|
+
count: 42,
|
|
84
|
+
isOpen: true,
|
|
85
|
+
data: null,
|
|
86
|
+
});
|
|
87
|
+
// Should not include children, functions, or objects
|
|
88
|
+
expect(info.props?.children).toBeUndefined();
|
|
89
|
+
expect(info.props?.onClick).toBeUndefined();
|
|
90
|
+
expect(info.props?.config).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
it('should walk up fiber tree to find component', () => {
|
|
93
|
+
const div = document.createElement('div');
|
|
94
|
+
document.body.appendChild(div);
|
|
95
|
+
// Simulate a DOM fiber with component parents
|
|
96
|
+
const parentFiber = {
|
|
97
|
+
tag: 0,
|
|
98
|
+
type: createMockComponent('ParentComponent'),
|
|
99
|
+
return: null,
|
|
100
|
+
};
|
|
101
|
+
const domFiber = {
|
|
102
|
+
tag: 5, // HostComponent (div)
|
|
103
|
+
type: 'div',
|
|
104
|
+
return: parentFiber,
|
|
105
|
+
};
|
|
106
|
+
div['__reactFiber$test'] = domFiber;
|
|
107
|
+
const info = extractFrameworkInfo(div);
|
|
108
|
+
expect(info.name).toBe('react');
|
|
109
|
+
expect(info.componentName).toBe('ParentComponent');
|
|
110
|
+
});
|
|
111
|
+
it('should collect ancestry chain', () => {
|
|
112
|
+
const div = document.createElement('div');
|
|
113
|
+
document.body.appendChild(div);
|
|
114
|
+
const appFiber = {
|
|
115
|
+
tag: 0,
|
|
116
|
+
type: createMockComponent('App'),
|
|
117
|
+
return: null,
|
|
118
|
+
};
|
|
119
|
+
const cardFiber = {
|
|
120
|
+
tag: 0,
|
|
121
|
+
type: createMockComponent('Card'),
|
|
122
|
+
return: appFiber,
|
|
123
|
+
};
|
|
124
|
+
const buttonFiber = {
|
|
125
|
+
tag: 0,
|
|
126
|
+
type: createMockComponent('Button'),
|
|
127
|
+
return: cardFiber,
|
|
128
|
+
};
|
|
129
|
+
div['__reactFiber$test'] = buttonFiber;
|
|
130
|
+
const info = extractFrameworkInfo(div);
|
|
131
|
+
expect(info.ancestry).toEqual(['Button', 'Card', 'App']);
|
|
132
|
+
});
|
|
133
|
+
it('should skip Context.Provider and similar internal components', () => {
|
|
134
|
+
const div = document.createElement('div');
|
|
135
|
+
document.body.appendChild(div);
|
|
136
|
+
const appFiber = {
|
|
137
|
+
tag: 0,
|
|
138
|
+
type: createMockComponent('App'),
|
|
139
|
+
return: null,
|
|
140
|
+
};
|
|
141
|
+
const providerFiber = {
|
|
142
|
+
tag: 0,
|
|
143
|
+
type: createMockComponent('ThemeProvider'),
|
|
144
|
+
return: appFiber,
|
|
145
|
+
};
|
|
146
|
+
const contextFiber = {
|
|
147
|
+
tag: 0,
|
|
148
|
+
type: createMockComponent('Context.Consumer'),
|
|
149
|
+
return: providerFiber,
|
|
150
|
+
};
|
|
151
|
+
const buttonFiber = {
|
|
152
|
+
tag: 0,
|
|
153
|
+
type: createMockComponent('Button'),
|
|
154
|
+
return: contextFiber,
|
|
155
|
+
};
|
|
156
|
+
div['__reactFiber$test'] = buttonFiber;
|
|
157
|
+
const info = extractFrameworkInfo(div);
|
|
158
|
+
// The nearest user component should be Button
|
|
159
|
+
expect(info.componentName).toBe('Button');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('Vue detection', () => {
|
|
163
|
+
it('should detect Vue 2 from __vue__', () => {
|
|
164
|
+
const div = document.createElement('div');
|
|
165
|
+
document.body.appendChild(div);
|
|
166
|
+
div.__vue__ = {
|
|
167
|
+
$options: {
|
|
168
|
+
name: 'MyVueComponent',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
const info = extractFrameworkInfo(div);
|
|
172
|
+
expect(info.name).toBe('vue');
|
|
173
|
+
expect(info.componentName).toBe('MyVueComponent');
|
|
174
|
+
});
|
|
175
|
+
it('should detect Vue 3 from __vueParentComponent', () => {
|
|
176
|
+
const div = document.createElement('div');
|
|
177
|
+
document.body.appendChild(div);
|
|
178
|
+
div.__vueParentComponent = {
|
|
179
|
+
type: {
|
|
180
|
+
name: 'VueThreeComponent',
|
|
181
|
+
__file: 'src/components/VueThreeComponent.vue',
|
|
182
|
+
},
|
|
183
|
+
props: {
|
|
184
|
+
message: 'Hello',
|
|
185
|
+
count: 5,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const info = extractFrameworkInfo(div);
|
|
189
|
+
expect(info.name).toBe('vue');
|
|
190
|
+
expect(info.componentName).toBe('VueThreeComponent');
|
|
191
|
+
expect(info.filePath).toBe('src/components/VueThreeComponent.vue');
|
|
192
|
+
expect(info.props).toEqual({
|
|
193
|
+
message: 'Hello',
|
|
194
|
+
count: 5,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
it('should detect Vue 3 with __name', () => {
|
|
198
|
+
const div = document.createElement('div');
|
|
199
|
+
document.body.appendChild(div);
|
|
200
|
+
div.__vueParentComponent = {
|
|
201
|
+
type: {
|
|
202
|
+
__name: 'SetupScriptComponent',
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
const info = extractFrameworkInfo(div);
|
|
206
|
+
expect(info.name).toBe('vue');
|
|
207
|
+
expect(info.componentName).toBe('SetupScriptComponent');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
describe('Svelte detection', () => {
|
|
211
|
+
it('should detect Svelte from __svelte key', () => {
|
|
212
|
+
const div = document.createElement('div');
|
|
213
|
+
document.body.appendChild(div);
|
|
214
|
+
div.__svelte_component = {
|
|
215
|
+
constructor: {
|
|
216
|
+
name: 'SvelteButton',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
const info = extractFrameworkInfo(div);
|
|
220
|
+
expect(info.name).toBe('svelte');
|
|
221
|
+
});
|
|
222
|
+
it('should extract Svelte component name', () => {
|
|
223
|
+
const div = document.createElement('div');
|
|
224
|
+
document.body.appendChild(div);
|
|
225
|
+
class MySvelteComponent {
|
|
226
|
+
}
|
|
227
|
+
div.__svelte_component = new MySvelteComponent();
|
|
228
|
+
const info = extractFrameworkInfo(div);
|
|
229
|
+
expect(info.name).toBe('svelte');
|
|
230
|
+
expect(info.componentName).toBe('MySvelteComponent');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe('framework priority', () => {
|
|
234
|
+
it('should prefer React over Vue if both are present', () => {
|
|
235
|
+
const div = document.createElement('div');
|
|
236
|
+
document.body.appendChild(div);
|
|
237
|
+
// Add both React and Vue markers
|
|
238
|
+
const mockFiber = {
|
|
239
|
+
tag: 0,
|
|
240
|
+
type: createMockComponent('ReactComp'),
|
|
241
|
+
return: null,
|
|
242
|
+
};
|
|
243
|
+
div['__reactFiber$test'] = mockFiber;
|
|
244
|
+
div.__vue__ = {
|
|
245
|
+
$options: { name: 'VueComp' },
|
|
246
|
+
};
|
|
247
|
+
const info = extractFrameworkInfo(div);
|
|
248
|
+
expect(info.name).toBe('react');
|
|
249
|
+
expect(info.componentName).toBe('ReactComp');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @eyeglass/inspector - Browser-side inspection web component
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import '@eyeglass/inspector';
|
|
6
|
+
* // Or inject via script tag
|
|
7
|
+
*
|
|
8
|
+
* This automatically registers the <eyeglass-inspector> custom element.
|
|
9
|
+
*/
|
|
10
|
+
export { EyeglassInspector } from './inspector.js';
|
|
11
|
+
export { captureSnapshot } from './snapshot.js';
|
|
12
|
+
export { extractFrameworkInfo } from './fiber-walker.js';
|
|
13
|
+
export type { FrameworkInfo } from './fiber-walker.js';
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the inspector by appending it to the document
|
|
16
|
+
*/
|
|
17
|
+
export declare function initInspector(): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @eyeglass/inspector - Browser-side inspection web component
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import '@eyeglass/inspector';
|
|
6
|
+
* // Or inject via script tag
|
|
7
|
+
*
|
|
8
|
+
* This automatically registers the <eyeglass-inspector> custom element.
|
|
9
|
+
*/
|
|
10
|
+
export { EyeglassInspector } from './inspector.js';
|
|
11
|
+
export { captureSnapshot } from './snapshot.js';
|
|
12
|
+
export { extractFrameworkInfo } from './fiber-walker.js';
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the inspector by appending it to the document
|
|
15
|
+
*/
|
|
16
|
+
export function initInspector() {
|
|
17
|
+
if (document.querySelector('eyeglass-inspector')) {
|
|
18
|
+
console.warn('[eyeglass] Inspector already initialized');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const inspector = document.createElement('eyeglass-inspector');
|
|
22
|
+
document.body.appendChild(inspector);
|
|
23
|
+
console.log('[eyeglass] Inspector initialized. Hover over elements and click to annotate.');
|
|
24
|
+
}
|
|
25
|
+
// Auto-initialize when imported in a browser context
|
|
26
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
27
|
+
if (document.readyState === 'loading') {
|
|
28
|
+
document.addEventListener('DOMContentLoaded', initInspector);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
initInspector();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eyeglass Inspector - Glass UI for visual element inspection
|
|
3
|
+
*/
|
|
4
|
+
export declare class EyeglassInspector extends HTMLElement {
|
|
5
|
+
private shadow;
|
|
6
|
+
private highlight;
|
|
7
|
+
private panel;
|
|
8
|
+
private toast;
|
|
9
|
+
private hub;
|
|
10
|
+
private currentElement;
|
|
11
|
+
private currentSnapshot;
|
|
12
|
+
private interactionId;
|
|
13
|
+
private frozen;
|
|
14
|
+
private eventSource;
|
|
15
|
+
private throttleTimeout;
|
|
16
|
+
private mode;
|
|
17
|
+
private activityEvents;
|
|
18
|
+
private currentStatus;
|
|
19
|
+
private hubExpanded;
|
|
20
|
+
private inspectorEnabled;
|
|
21
|
+
private history;
|
|
22
|
+
private isDragging;
|
|
23
|
+
private dragOffset;
|
|
24
|
+
private customPanelPosition;
|
|
25
|
+
private multiSelectMode;
|
|
26
|
+
private selectedElements;
|
|
27
|
+
private selectedSnapshots;
|
|
28
|
+
private multiSelectHighlights;
|
|
29
|
+
private submittedSnapshots;
|
|
30
|
+
private static readonly MAX_SELECTION;
|
|
31
|
+
private cursorStyleElement;
|
|
32
|
+
private scrollTimeout;
|
|
33
|
+
constructor();
|
|
34
|
+
connectedCallback(): void;
|
|
35
|
+
private saveSession;
|
|
36
|
+
private restoreSession;
|
|
37
|
+
private showResultToast;
|
|
38
|
+
private hideToast;
|
|
39
|
+
private loadHistory;
|
|
40
|
+
private saveHistory;
|
|
41
|
+
private addToHistory;
|
|
42
|
+
private updateHistoryStatus;
|
|
43
|
+
private renderHub;
|
|
44
|
+
private requestUndo;
|
|
45
|
+
disconnectedCallback(): void;
|
|
46
|
+
private connectSSE;
|
|
47
|
+
private handleActivityEvent;
|
|
48
|
+
private handleMouseMove;
|
|
49
|
+
private handleClick;
|
|
50
|
+
private handleKeyDown;
|
|
51
|
+
private handleScroll;
|
|
52
|
+
private disableHighlightTransitions;
|
|
53
|
+
private enableHighlightTransitions;
|
|
54
|
+
private updateMultiSelectHighlightPositions;
|
|
55
|
+
private handlePanelDragStart;
|
|
56
|
+
private handlePanelDrag;
|
|
57
|
+
private handlePanelDragEnd;
|
|
58
|
+
private showHighlight;
|
|
59
|
+
private hideHighlight;
|
|
60
|
+
private freeze;
|
|
61
|
+
private enterMultiSelectMode;
|
|
62
|
+
private toggleInSelection;
|
|
63
|
+
private removeFromSelection;
|
|
64
|
+
private exitMultiSelectMode;
|
|
65
|
+
private renderMultiSelectHighlights;
|
|
66
|
+
private clearMultiSelectHighlights;
|
|
67
|
+
private unfreeze;
|
|
68
|
+
private renderPanel;
|
|
69
|
+
private renderInputMode;
|
|
70
|
+
private renderActivityMode;
|
|
71
|
+
private renderActivityFeed;
|
|
72
|
+
private renderStatusItem;
|
|
73
|
+
private renderThoughtItem;
|
|
74
|
+
private renderActionItem;
|
|
75
|
+
private renderQuestionItem;
|
|
76
|
+
private getUserNote;
|
|
77
|
+
private getStatusText;
|
|
78
|
+
private hidePanel;
|
|
79
|
+
private submit;
|
|
80
|
+
private submitAnswer;
|
|
81
|
+
private escapeHtml;
|
|
82
|
+
private updateCursor;
|
|
83
|
+
}
|