@np-dev/ui-ai-anotation 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.
Files changed (52) hide show
  1. package/README.md +245 -0
  2. package/dist/cjs/index.cjs +1550 -0
  3. package/dist/cjs/index.cjs.map +7 -0
  4. package/dist/cjs/index.native.cjs +1004 -0
  5. package/dist/cjs/index.native.cjs.map +7 -0
  6. package/dist/cjs/index.web.cjs +83 -0
  7. package/dist/cjs/index.web.cjs.map +7 -0
  8. package/dist/esm/index.js +1524 -0
  9. package/dist/esm/index.js.map +7 -0
  10. package/dist/esm/index.native.js +1012 -0
  11. package/dist/esm/index.native.js.map +7 -0
  12. package/dist/esm/index.web.js +62 -0
  13. package/dist/esm/index.web.js.map +7 -0
  14. package/dist/types/components/AnnotationInput.d.ts +8 -0
  15. package/dist/types/components/AnnotationList.d.ts +1 -0
  16. package/dist/types/components/Draggable.d.ts +10 -0
  17. package/dist/types/components/Highlighter.d.ts +1 -0
  18. package/dist/types/components/Toolbar.d.ts +1 -0
  19. package/dist/types/index.d.ts +20 -0
  20. package/dist/types/index.web.d.ts +69 -0
  21. package/dist/types/store.d.ts +66 -0
  22. package/dist/types/utils/fiber.d.ts +51 -0
  23. package/dist/types/utils/platform.d.ts +8 -0
  24. package/dist/types/utils/screenshot.d.ts +28 -0
  25. package/package.json +115 -0
  26. package/src/components/AnnotationInput.tsx +269 -0
  27. package/src/components/AnnotationList.tsx +248 -0
  28. package/src/components/Draggable.tsx +73 -0
  29. package/src/components/Highlighter.tsx +497 -0
  30. package/src/components/Toolbar.tsx +213 -0
  31. package/src/components/native/AnnotationInput.tsx +227 -0
  32. package/src/components/native/AnnotationList.tsx +157 -0
  33. package/src/components/native/Draggable.tsx +65 -0
  34. package/src/components/native/Highlighter.tsx +239 -0
  35. package/src/components/native/Toolbar.tsx +192 -0
  36. package/src/components/native/index.ts +6 -0
  37. package/src/components/web/AnnotationInput.tsx +150 -0
  38. package/src/components/web/AnnotationList.tsx +117 -0
  39. package/src/components/web/Draggable.tsx +74 -0
  40. package/src/components/web/Highlighter.tsx +329 -0
  41. package/src/components/web/Toolbar.tsx +198 -0
  42. package/src/components/web/index.ts +6 -0
  43. package/src/extension.tsx +15 -0
  44. package/src/index.native.tsx +50 -0
  45. package/src/index.tsx +41 -0
  46. package/src/index.web.tsx +124 -0
  47. package/src/store.tsx +120 -0
  48. package/src/utils/fiber.native.ts +90 -0
  49. package/src/utils/fiber.ts +255 -0
  50. package/src/utils/platform.ts +33 -0
  51. package/src/utils/screenshot.native.ts +139 -0
  52. package/src/utils/screenshot.ts +162 -0
package/src/index.tsx ADDED
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { AiAnnotationProvider as Provider } from './store';
3
+ import { Toolbar } from './components/Toolbar';
4
+
5
+ export interface AiAnnotationProviderProps {
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * Web AI Annotation Provider
11
+ * Wraps your app to provide annotation functionality
12
+ */
13
+ export function AiAnnotationProvider({ children }: AiAnnotationProviderProps) {
14
+ return (
15
+ <Provider>
16
+ {children}
17
+ <Toolbar />
18
+ </Provider>
19
+ );
20
+ }
21
+
22
+ // Re-export store
23
+ export * from './store';
24
+
25
+ // Export web components
26
+ export { Toolbar } from './components/Toolbar';
27
+ export { Highlighter } from './components/Highlighter';
28
+ export { AnnotationInput } from './components/AnnotationInput';
29
+ export { AnnotationList } from './components/AnnotationList';
30
+ export { Draggable } from './components/Draggable';
31
+
32
+ // Export screenshot utility
33
+ export { captureScreenshot } from './utils/screenshot';
34
+ export type { ScreenshotOptions, ScreenshotResult } from './utils/screenshot';
35
+
36
+ // Export fiber utilities
37
+ export { getReactFiber, getComponentDisplayName, getElementFromFiber } from './utils/fiber';
38
+
39
+ // Export platform utilities
40
+ export { isWeb, isNative, getPlatform, isTauriEnv } from './utils/platform';
41
+ export type { PlatformType } from './utils/platform';
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Web-only entry point for Chrome Extension
3
+ * This avoids any React Native imports
4
+ */
5
+
6
+ import React, { createContext, useContext, useReducer, ReactNode } from 'react';
7
+ import type { ComponentDetails, ChildComponentInfo, ElementInfo } from './utils/fiber';
8
+
9
+ // Re-export types for external use
10
+ export type { ComponentDetails, ChildComponentInfo, ElementInfo };
11
+
12
+ export type Annotation = {
13
+ id: string;
14
+ componentName: string;
15
+ note: string;
16
+ timestamp: number;
17
+ /** Enhanced component details (optional for backward compatibility) */
18
+ details?: ComponentDetails;
19
+ };
20
+
21
+ export type Mode = 'disabled' | 'inspecting';
22
+
23
+ /**
24
+ * Platform-agnostic element type
25
+ * - On web: HTMLElement
26
+ */
27
+ export type HoveredElement = HTMLElement | null;
28
+
29
+ /**
30
+ * Enhanced component info with full details
31
+ */
32
+ export interface HoveredComponentInfo {
33
+ name: string;
34
+ /** Full component details including hierarchy, children, and element info */
35
+ details?: ComponentDetails;
36
+ }
37
+
38
+ interface State {
39
+ mode: Mode;
40
+ annotations: Annotation[];
41
+ hoveredElement: HoveredElement;
42
+ hoveredComponentInfo: HoveredComponentInfo | null;
43
+ isMinimized: boolean;
44
+ showList: boolean;
45
+ }
46
+
47
+ type Action =
48
+ | { type: 'SET_MODE'; payload: Mode }
49
+ | { type: 'ADD_ANNOTATION'; payload: Annotation }
50
+ | { type: 'REMOVE_ANNOTATION'; payload: string }
51
+ | { type: 'CLEAR_ALL_ANNOTATIONS' }
52
+ | { type: 'SET_HOVERED'; payload: { element: HoveredElement; name: string | null; details?: ComponentDetails } }
53
+ | { type: 'TOGGLE_MINIMIZED' }
54
+ | { type: 'TOGGLE_LIST' }
55
+ | { type: 'RESET_HOVER' };
56
+
57
+ const initialState: State = {
58
+ mode: 'inspecting', // Start enabled for extension
59
+ annotations: [],
60
+ hoveredElement: null,
61
+ hoveredComponentInfo: null,
62
+ isMinimized: false,
63
+ showList: false,
64
+ };
65
+
66
+ const AnnotationContext = createContext<{
67
+ state: State;
68
+ dispatch: React.Dispatch<Action>;
69
+ } | undefined>(undefined);
70
+
71
+ function reducer(state: State, action: Action): State {
72
+ switch (action.type) {
73
+ case 'SET_MODE':
74
+ return { ...state, mode: action.payload };
75
+ case 'ADD_ANNOTATION':
76
+ return { ...state, annotations: [...state.annotations, action.payload] };
77
+ case 'REMOVE_ANNOTATION':
78
+ return {
79
+ ...state,
80
+ annotations: state.annotations.filter((a) => a.id !== action.payload),
81
+ };
82
+ case 'CLEAR_ALL_ANNOTATIONS':
83
+ return {
84
+ ...state,
85
+ annotations: [],
86
+ };
87
+ case 'SET_HOVERED':
88
+ // Avoid updates if same
89
+ if (state.hoveredElement === action.payload.element) return state;
90
+ return {
91
+ ...state,
92
+ hoveredElement: action.payload.element,
93
+ hoveredComponentInfo: action.payload.name
94
+ ? { name: action.payload.name, details: action.payload.details }
95
+ : null
96
+ };
97
+ case 'RESET_HOVER':
98
+ return { ...state, hoveredElement: null, hoveredComponentInfo: null };
99
+ case 'TOGGLE_MINIMIZED':
100
+ return { ...state, isMinimized: !state.isMinimized };
101
+ case 'TOGGLE_LIST':
102
+ return { ...state, showList: !state.showList };
103
+ default:
104
+ return state;
105
+ }
106
+ }
107
+
108
+ export function AiAnnotationProvider({ children }: { children: ReactNode }) {
109
+ const [state, dispatch] = useReducer(reducer, initialState);
110
+
111
+ return (
112
+ <AnnotationContext.Provider value={{ state, dispatch }}>
113
+ {children}
114
+ </AnnotationContext.Provider>
115
+ );
116
+ }
117
+
118
+ export function useAiAnnotation() {
119
+ const context = useContext(AnnotationContext);
120
+ if (!context) {
121
+ throw new Error('useAiAnnotation must be used within an AiAnnotationProvider');
122
+ }
123
+ return context;
124
+ }
package/src/store.tsx ADDED
@@ -0,0 +1,120 @@
1
+ import React, { createContext, useContext, useReducer, ReactNode } from 'react';
2
+ import type { ComponentDetails, ChildComponentInfo, ElementInfo } from './utils/fiber';
3
+
4
+ // Re-export types for external use
5
+ export type { ComponentDetails, ChildComponentInfo, ElementInfo };
6
+
7
+ export type Annotation = {
8
+ id: string;
9
+ componentName: string;
10
+ note: string;
11
+ timestamp: number;
12
+ /** Enhanced component details (optional for backward compatibility) */
13
+ details?: ComponentDetails;
14
+ };
15
+
16
+ export type Mode = 'disabled' | 'inspecting';
17
+
18
+ /**
19
+ * Platform-agnostic element type
20
+ * - On web: HTMLElement
21
+ * - On native: View ref or null
22
+ */
23
+ export type HoveredElement = any;
24
+
25
+ /**
26
+ * Enhanced component info with full details
27
+ */
28
+ export interface HoveredComponentInfo {
29
+ name: string;
30
+ /** Full component details including hierarchy, children, and element info */
31
+ details?: ComponentDetails;
32
+ }
33
+
34
+ interface State {
35
+ mode: Mode;
36
+ annotations: Annotation[];
37
+ hoveredElement: HoveredElement;
38
+ hoveredComponentInfo: HoveredComponentInfo | null;
39
+ isMinimized: boolean;
40
+ showList: boolean;
41
+ }
42
+
43
+ type Action =
44
+ | { type: 'SET_MODE'; payload: Mode }
45
+ | { type: 'ADD_ANNOTATION'; payload: Annotation }
46
+ | { type: 'REMOVE_ANNOTATION'; payload: string }
47
+ | { type: 'CLEAR_ALL_ANNOTATIONS' }
48
+ | { type: 'SET_HOVERED'; payload: { element: HoveredElement; name: string | null; details?: ComponentDetails } }
49
+ | { type: 'TOGGLE_MINIMIZED' }
50
+ | { type: 'TOGGLE_LIST' }
51
+ | { type: 'RESET_HOVER' };
52
+
53
+ const initialState: State = {
54
+ mode: 'disabled',
55
+ annotations: [],
56
+ hoveredElement: null,
57
+ hoveredComponentInfo: null,
58
+ isMinimized: false,
59
+ showList: false,
60
+ };
61
+
62
+ const AnnotationContext = createContext<{
63
+ state: State;
64
+ dispatch: React.Dispatch<Action>;
65
+ } | undefined>(undefined);
66
+
67
+ function reducer(state: State, action: Action): State {
68
+ switch (action.type) {
69
+ case 'SET_MODE':
70
+ return { ...state, mode: action.payload };
71
+ case 'ADD_ANNOTATION':
72
+ return { ...state, annotations: [...state.annotations, action.payload] };
73
+ case 'REMOVE_ANNOTATION':
74
+ return {
75
+ ...state,
76
+ annotations: state.annotations.filter((a) => a.id !== action.payload),
77
+ };
78
+ case 'CLEAR_ALL_ANNOTATIONS':
79
+ return {
80
+ ...state,
81
+ annotations: [],
82
+ };
83
+ case 'SET_HOVERED':
84
+ // Avoid updates if same
85
+ if (state.hoveredElement === action.payload.element) return state;
86
+ return {
87
+ ...state,
88
+ hoveredElement: action.payload.element,
89
+ hoveredComponentInfo: action.payload.name
90
+ ? { name: action.payload.name, details: action.payload.details }
91
+ : null
92
+ };
93
+ case 'RESET_HOVER':
94
+ return { ...state, hoveredElement: null, hoveredComponentInfo: null };
95
+ case 'TOGGLE_MINIMIZED':
96
+ return { ...state, isMinimized: !state.isMinimized };
97
+ case 'TOGGLE_LIST':
98
+ return { ...state, showList: !state.showList };
99
+ default:
100
+ return state;
101
+ }
102
+ }
103
+
104
+ export function AiAnnotationProvider({ children }: { children: ReactNode }) {
105
+ const [state, dispatch] = useReducer(reducer, initialState);
106
+
107
+ return (
108
+ <AnnotationContext.Provider value={{ state, dispatch }}>
109
+ {children}
110
+ </AnnotationContext.Provider>
111
+ );
112
+ }
113
+
114
+ export function useAiAnnotation() {
115
+ const context = useContext(AnnotationContext);
116
+ if (!context) {
117
+ throw new Error('useAiAnnotation must be used within an AiAnnotationProvider');
118
+ }
119
+ return context;
120
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * React Fiber utilities for React Native
3
+ *
4
+ * Note: React Native has different fiber access compared to web.
5
+ * These utilities provide a similar interface but with native-specific logic.
6
+ */
7
+
8
+ /**
9
+ * Attempts to get the React Fiber from a native view instance
10
+ * This is less reliable in React Native than on web
11
+ */
12
+ export function getReactFiber(instance: any): any {
13
+ if (!instance) return null;
14
+
15
+ // React Native stores fiber differently
16
+ // Try to find the fiber via internal props
17
+ const key = Object.keys(instance).find((k) =>
18
+ k.startsWith('__reactFiber$') ||
19
+ k.startsWith('__reactInternalInstance$') ||
20
+ k.startsWith('_reactInternals')
21
+ );
22
+
23
+ return key ? instance[key] : null;
24
+ }
25
+
26
+ /**
27
+ * Gets the display name of a React component from its fiber
28
+ */
29
+ export function getComponentDisplayName(fiber: any): string {
30
+ if (!fiber) return 'Unknown';
31
+
32
+ let curr = fiber;
33
+ while (curr) {
34
+ const type = curr.type;
35
+ if (type) {
36
+ // Check for displayName or name
37
+ const name = type.displayName || type.name;
38
+ if (name && typeof name === 'string') {
39
+ // Skip internal React Native components
40
+ if (!name.startsWith('RCT') && !name.startsWith('_')) {
41
+ return name;
42
+ }
43
+ }
44
+ }
45
+ curr = curr.return;
46
+ }
47
+ return 'Unknown';
48
+ }
49
+
50
+ /**
51
+ * For React Native, we can't easily get DOM elements from fibers
52
+ * This is a no-op that returns null
53
+ */
54
+ export function getElementFromFiber(_fiber: any): null {
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Interface for component inspection result
60
+ */
61
+ export interface ComponentInfo {
62
+ name: string;
63
+ props?: Record<string, any>;
64
+ }
65
+
66
+ /**
67
+ * Inspects a React Native component instance to get info
68
+ * This works with refs passed to components
69
+ */
70
+ export function inspectComponent(ref: any): ComponentInfo | null {
71
+ if (!ref) return null;
72
+
73
+ // Try to get fiber from the ref
74
+ const fiber = getReactFiber(ref);
75
+ if (fiber) {
76
+ return {
77
+ name: getComponentDisplayName(fiber),
78
+ props: fiber.memoizedProps,
79
+ };
80
+ }
81
+
82
+ // Fallback: try to infer from the ref itself
83
+ if (ref.constructor && ref.constructor.name) {
84
+ return {
85
+ name: ref.constructor.name,
86
+ };
87
+ }
88
+
89
+ return null;
90
+ }
@@ -0,0 +1,255 @@
1
+
2
+ export function getReactFiber(dom: HTMLElement): any {
3
+ const key = Object.keys(dom).find((key) =>
4
+ key.startsWith("__reactFiber$")
5
+ );
6
+ // @ts-ignore
7
+ return key ? dom[key] : null;
8
+ }
9
+
10
+ export function getComponentDisplayName(fiber: any): string {
11
+ let curr = fiber;
12
+ while (curr) {
13
+ const name = curr.type?.displayName || curr.type?.name;
14
+ // Filter out internal components if needed, but for now take the first named one
15
+ // Also skip styled-components or basic html tags if we want "Component" names
16
+ if (name && typeof curr.type === 'function') return name;
17
+ curr = curr.return;
18
+ }
19
+ return "Unknown";
20
+ }
21
+
22
+ export function getElementFromFiber(fiber: any): HTMLElement | null {
23
+ let curr = fiber;
24
+ while (curr) {
25
+ if (curr.stateNode instanceof HTMLElement) {
26
+ return curr.stateNode;
27
+ }
28
+ curr = curr.child;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Component info with hierarchy details
35
+ */
36
+ export interface ComponentDetails {
37
+ name: string;
38
+ parentHierarchy: string[];
39
+ childComponents: ChildComponentInfo[];
40
+ elementInfo: ElementInfo;
41
+ props?: Record<string, any>;
42
+ }
43
+
44
+ export interface ChildComponentInfo {
45
+ name: string;
46
+ depth: number;
47
+ count: number;
48
+ hasChildren: boolean;
49
+ }
50
+
51
+ export interface ElementInfo {
52
+ tagName: string;
53
+ id?: string;
54
+ className?: string;
55
+ attributes: Record<string, string>;
56
+ childElementCount: number;
57
+ textContent?: string;
58
+ }
59
+
60
+ /**
61
+ * Get parent component hierarchy (from current component up to root)
62
+ */
63
+ export function getParentHierarchy(fiber: any, maxDepth: number = 10): string[] {
64
+ const hierarchy: string[] = [];
65
+ let curr = fiber?.return;
66
+ let depth = 0;
67
+
68
+ while (curr && depth < maxDepth) {
69
+ const name = curr.type?.displayName || curr.type?.name;
70
+ if (name && typeof curr.type === 'function') {
71
+ // Skip internal React components (starting with uppercase but containing $)
72
+ if (!name.includes('$') && !name.startsWith('_')) {
73
+ hierarchy.push(name);
74
+ }
75
+ }
76
+ curr = curr.return;
77
+ depth++;
78
+ }
79
+
80
+ return hierarchy;
81
+ }
82
+
83
+ /**
84
+ * Get child components from a fiber node
85
+ */
86
+ export function getChildComponents(fiber: any, maxDepth: number = 5): ChildComponentInfo[] {
87
+ const children: Map<string, { count: number; depth: number; hasChildren: boolean }> = new Map();
88
+
89
+ function traverse(node: any, depth: number) {
90
+ if (!node || depth > maxDepth) return;
91
+
92
+ const name = node.type?.displayName || node.type?.name;
93
+ if (name && typeof node.type === 'function' && !name.includes('$') && !name.startsWith('_')) {
94
+ const existing = children.get(name);
95
+ const hasChildComponents = checkHasChildComponents(node.child);
96
+
97
+ if (existing) {
98
+ existing.count++;
99
+ // Keep the shallowest depth
100
+ if (depth < existing.depth) {
101
+ existing.depth = depth;
102
+ }
103
+ existing.hasChildren = existing.hasChildren || hasChildComponents;
104
+ } else {
105
+ children.set(name, { count: 1, depth, hasChildren: hasChildComponents });
106
+ }
107
+ }
108
+
109
+ // Traverse siblings and children
110
+ if (node.child) traverse(node.child, depth + 1);
111
+ if (node.sibling) traverse(node.sibling, depth);
112
+ }
113
+
114
+ // Start from the fiber's child
115
+ if (fiber?.child) {
116
+ traverse(fiber.child, 1);
117
+ }
118
+
119
+ return Array.from(children.entries()).map(([name, info]) => ({
120
+ name,
121
+ depth: info.depth,
122
+ count: info.count,
123
+ hasChildren: info.hasChildren,
124
+ })).sort((a, b) => a.depth - b.depth);
125
+ }
126
+
127
+ /**
128
+ * Check if a fiber node has child components
129
+ */
130
+ function checkHasChildComponents(fiber: any, maxDepth: number = 3): boolean {
131
+ if (!fiber || maxDepth <= 0) return false;
132
+
133
+ const name = fiber.type?.displayName || fiber.type?.name;
134
+ if (name && typeof fiber.type === 'function' && !name.includes('$')) {
135
+ return true;
136
+ }
137
+
138
+ if (fiber.child && checkHasChildComponents(fiber.child, maxDepth - 1)) return true;
139
+ if (fiber.sibling && checkHasChildComponents(fiber.sibling, maxDepth - 1)) return true;
140
+
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Get DOM element information
146
+ */
147
+ export function getElementInfo(element: HTMLElement): ElementInfo {
148
+ const importantAttributes = ['data-testid', 'role', 'aria-label', 'type', 'name', 'href', 'src'];
149
+ const attributes: Record<string, string> = {};
150
+
151
+ importantAttributes.forEach(attr => {
152
+ const value = element.getAttribute(attr);
153
+ if (value) {
154
+ attributes[attr] = value;
155
+ }
156
+ });
157
+
158
+ // Get text content (truncated)
159
+ let textContent: string | undefined;
160
+ const directText = Array.from(element.childNodes)
161
+ .filter(node => node.nodeType === Node.TEXT_NODE)
162
+ .map(node => node.textContent?.trim())
163
+ .filter(Boolean)
164
+ .join(' ');
165
+
166
+ if (directText) {
167
+ textContent = directText.length > 100 ? directText.substring(0, 100) + '...' : directText;
168
+ }
169
+
170
+ return {
171
+ tagName: element.tagName.toLowerCase(),
172
+ id: element.id || undefined,
173
+ className: element.className && typeof element.className === 'string'
174
+ ? element.className.split(' ').slice(0, 5).join(' ') + (element.className.split(' ').length > 5 ? '...' : '')
175
+ : undefined,
176
+ attributes,
177
+ childElementCount: element.childElementCount,
178
+ textContent,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Get component props (filtered for safety - removes functions and large objects)
184
+ */
185
+ export function getComponentProps(fiber: any, maxProps: number = 10): Record<string, any> | undefined {
186
+ if (!fiber?.memoizedProps) return undefined;
187
+
188
+ const props = fiber.memoizedProps;
189
+ const safeProps: Record<string, any> = {};
190
+ let count = 0;
191
+
192
+ for (const key of Object.keys(props)) {
193
+ if (count >= maxProps) {
194
+ safeProps['...'] = `${Object.keys(props).length - maxProps} more props`;
195
+ break;
196
+ }
197
+
198
+ // Skip children and function props
199
+ if (key === 'children') continue;
200
+
201
+ const value = props[key];
202
+ const type = typeof value;
203
+
204
+ if (type === 'function') {
205
+ safeProps[key] = '[Function]';
206
+ } else if (type === 'object' && value !== null) {
207
+ if (Array.isArray(value)) {
208
+ safeProps[key] = `[Array(${value.length})]`;
209
+ } else if (value.$$typeof) {
210
+ // React element
211
+ safeProps[key] = '[ReactElement]';
212
+ } else {
213
+ try {
214
+ const json = JSON.stringify(value);
215
+ safeProps[key] = json.length > 100 ? '[Object]' : value;
216
+ } catch {
217
+ safeProps[key] = '[Object]';
218
+ }
219
+ }
220
+ } else {
221
+ safeProps[key] = value;
222
+ }
223
+ count++;
224
+ }
225
+
226
+ return Object.keys(safeProps).length > 0 ? safeProps : undefined;
227
+ }
228
+
229
+ /**
230
+ * Get comprehensive component details for the selected element
231
+ */
232
+ export function getComponentDetails(element: HTMLElement, options?: {
233
+ includeProps?: boolean;
234
+ maxParentDepth?: number;
235
+ maxChildDepth?: number;
236
+ }): ComponentDetails {
237
+ const { includeProps = false, maxParentDepth = 10, maxChildDepth = 5 } = options || {};
238
+
239
+ const fiber = getReactFiber(element);
240
+ const name = fiber ? getComponentDisplayName(fiber) : 'Unknown';
241
+
242
+ // Find the actual component fiber (not DOM node fiber)
243
+ let componentFiber = fiber;
244
+ while (componentFiber && typeof componentFiber.type !== 'function') {
245
+ componentFiber = componentFiber.return;
246
+ }
247
+
248
+ return {
249
+ name,
250
+ parentHierarchy: getParentHierarchy(componentFiber, maxParentDepth),
251
+ childComponents: getChildComponents(componentFiber, maxChildDepth),
252
+ elementInfo: getElementInfo(element),
253
+ props: includeProps ? getComponentProps(componentFiber) : undefined,
254
+ };
255
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Platform detection utilities for cross-platform support
3
+ */
4
+
5
+ // Check if we're running in a web environment
6
+ export const isWeb = typeof document !== 'undefined' && typeof window !== 'undefined';
7
+
8
+ // Check if we're running in React Native
9
+ // We check for the absence of DOM and presence of native-specific globals
10
+ export const isNative = !isWeb && typeof global !== 'undefined';
11
+
12
+ // Get the current platform
13
+ export type PlatformType = 'web' | 'ios' | 'android' | 'native';
14
+
15
+ export function getPlatform(): PlatformType {
16
+ if (isWeb) return 'web';
17
+
18
+ // In React Native, we need to dynamically check Platform
19
+ try {
20
+ // This will only work in React Native environment
21
+ const { Platform } = require('react-native');
22
+ if (Platform.OS === 'ios') return 'ios';
23
+ if (Platform.OS === 'android') return 'android';
24
+ return 'native';
25
+ } catch {
26
+ return 'native';
27
+ }
28
+ }
29
+
30
+ // Check if running in Tauri environment (web only)
31
+ export function isTauriEnv(): boolean {
32
+ return isWeb && typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
33
+ }