rn-studio 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/LICENSE +21 -0
- package/README.md +116 -0
- package/babel-plugin/index.js +68 -0
- package/bin/rn-studio-server.js +98 -0
- package/dist/StudioProvider.d.ts +25 -0
- package/dist/StudioProvider.d.ts.map +1 -0
- package/dist/StudioProvider.js +131 -0
- package/dist/StudioProvider.js.map +1 -0
- package/dist/ast/AstEngine.d.ts +16 -0
- package/dist/ast/AstEngine.d.ts.map +1 -0
- package/dist/ast/AstEngine.js +153 -0
- package/dist/ast/AstEngine.js.map +1 -0
- package/dist/bridge/WebSocketBridge.d.ts +24 -0
- package/dist/bridge/WebSocketBridge.d.ts.map +1 -0
- package/dist/bridge/WebSocketBridge.js +94 -0
- package/dist/bridge/WebSocketBridge.js.map +1 -0
- package/dist/components/ComponentTree.d.ts +9 -0
- package/dist/components/ComponentTree.d.ts.map +1 -0
- package/dist/components/ComponentTree.js +65 -0
- package/dist/components/ComponentTree.js.map +1 -0
- package/dist/components/FloatingBubble.d.ts +8 -0
- package/dist/components/FloatingBubble.d.ts.map +1 -0
- package/dist/components/FloatingBubble.js +205 -0
- package/dist/components/FloatingBubble.js.map +1 -0
- package/dist/components/InspectorPanel.d.ts +9 -0
- package/dist/components/InspectorPanel.d.ts.map +1 -0
- package/dist/components/InspectorPanel.js +242 -0
- package/dist/components/InspectorPanel.js.map +1 -0
- package/dist/components/SelectionOverlay.d.ts +15 -0
- package/dist/components/SelectionOverlay.d.ts.map +1 -0
- package/dist/components/SelectionOverlay.js +152 -0
- package/dist/components/SelectionOverlay.js.map +1 -0
- package/dist/components/StyleEditor.d.ts +10 -0
- package/dist/components/StyleEditor.d.ts.map +1 -0
- package/dist/components/StyleEditor.js +144 -0
- package/dist/components/StyleEditor.js.map +1 -0
- package/dist/context/StudioContext.d.ts +7 -0
- package/dist/context/StudioContext.d.ts.map +1 -0
- package/dist/context/StudioContext.js +10 -0
- package/dist/context/StudioContext.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/fiberWalker.d.ts +36 -0
- package/dist/utils/fiberWalker.d.ts.map +1 -0
- package/dist/utils/fiberWalker.js +122 -0
- package/dist/utils/fiberWalker.js.map +1 -0
- package/dist/utils/measureComponent.d.ts +15 -0
- package/dist/utils/measureComponent.d.ts.map +1 -0
- package/dist/utils/measureComponent.js +28 -0
- package/dist/utils/measureComponent.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,WAAW,GACZ,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ComponentTree = exports.StyleEditor = exports.InspectorPanel = exports.SelectionOverlay = exports.FloatingBubble = exports.WebSocketBridge = exports.useStudio = exports.StudioProvider = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* rn-studio — public barrel exports
|
|
6
|
+
*
|
|
7
|
+
* Consumer usage:
|
|
8
|
+
*
|
|
9
|
+
* import { StudioProvider, useStudio } from 'rn-studio';
|
|
10
|
+
*
|
|
11
|
+
* export default function App() {
|
|
12
|
+
* return (
|
|
13
|
+
* <StudioProvider enabled={__DEV__} bubblePosition="bottom-right">
|
|
14
|
+
* <YourApp />
|
|
15
|
+
* </StudioProvider>
|
|
16
|
+
* );
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
var StudioProvider_1 = require("./StudioProvider");
|
|
20
|
+
Object.defineProperty(exports, "StudioProvider", { enumerable: true, get: function () { return StudioProvider_1.StudioProvider; } });
|
|
21
|
+
Object.defineProperty(exports, "useStudio", { enumerable: true, get: function () { return StudioProvider_1.useStudio; } });
|
|
22
|
+
var WebSocketBridge_1 = require("./bridge/WebSocketBridge");
|
|
23
|
+
Object.defineProperty(exports, "WebSocketBridge", { enumerable: true, get: function () { return WebSocketBridge_1.WebSocketBridge; } });
|
|
24
|
+
var FloatingBubble_1 = require("./components/FloatingBubble");
|
|
25
|
+
Object.defineProperty(exports, "FloatingBubble", { enumerable: true, get: function () { return FloatingBubble_1.FloatingBubble; } });
|
|
26
|
+
var SelectionOverlay_1 = require("./components/SelectionOverlay");
|
|
27
|
+
Object.defineProperty(exports, "SelectionOverlay", { enumerable: true, get: function () { return SelectionOverlay_1.SelectionOverlay; } });
|
|
28
|
+
var InspectorPanel_1 = require("./components/InspectorPanel");
|
|
29
|
+
Object.defineProperty(exports, "InspectorPanel", { enumerable: true, get: function () { return InspectorPanel_1.InspectorPanel; } });
|
|
30
|
+
var StyleEditor_1 = require("./components/StyleEditor");
|
|
31
|
+
Object.defineProperty(exports, "StyleEditor", { enumerable: true, get: function () { return StyleEditor_1.StyleEditor; } });
|
|
32
|
+
var ComponentTree_1 = require("./components/ComponentTree");
|
|
33
|
+
Object.defineProperty(exports, "ComponentTree", { enumerable: true, get: function () { return ComponentTree_1.ComponentTree; } });
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;GAcG;AACH,mDAA6D;AAApD,gHAAA,cAAc,OAAA;AAAE,2GAAA,SAAS,OAAA;AAClC,4DAA2D;AAAlD,kHAAA,eAAe,OAAA;AACxB,8DAA6D;AAApD,gHAAA,cAAc,OAAA;AACvB,kEAAiE;AAAxD,oHAAA,gBAAgB,OAAA;AACzB,8DAA6D;AAApD,gHAAA,cAAc,OAAA;AACvB,wDAAuD;AAA9C,0GAAA,WAAW,OAAA;AACpB,4DAA2D;AAAlD,8GAAA,aAAa,OAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rn-studio — shared type definitions
|
|
3
|
+
*
|
|
4
|
+
* These types are consumed by both the runtime (React Native) and
|
|
5
|
+
* the Node.js side (CLI server, AST engine). Keep this file free of
|
|
6
|
+
* runtime imports so it can be used from either environment.
|
|
7
|
+
*/
|
|
8
|
+
/** Source location injected by the babel plugin. */
|
|
9
|
+
export interface SourceLocation {
|
|
10
|
+
file: string;
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
componentName: string;
|
|
14
|
+
}
|
|
15
|
+
/** A node in the component tree reconstructed from React fiber. */
|
|
16
|
+
export interface ComponentNode {
|
|
17
|
+
id: string;
|
|
18
|
+
componentName: string;
|
|
19
|
+
source: SourceLocation;
|
|
20
|
+
props: Record<string, unknown>;
|
|
21
|
+
styles: StyleProperty[];
|
|
22
|
+
children: ComponentNode[];
|
|
23
|
+
}
|
|
24
|
+
/** A single editable style property. */
|
|
25
|
+
export interface StyleProperty {
|
|
26
|
+
key: string;
|
|
27
|
+
value: string | number;
|
|
28
|
+
type: 'color' | 'number' | 'string' | 'boolean';
|
|
29
|
+
unit?: 'px' | '%' | 'dp';
|
|
30
|
+
}
|
|
31
|
+
/** Configuration passed to `<StudioProvider>`. */
|
|
32
|
+
export interface StudioConfig {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
serverPort?: number;
|
|
35
|
+
bubblePosition?: BubblePosition;
|
|
36
|
+
theme?: 'dark' | 'light';
|
|
37
|
+
}
|
|
38
|
+
export type BubblePosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
39
|
+
/** Context value exposed via `useStudio()`. */
|
|
40
|
+
export interface StudioContextValue {
|
|
41
|
+
isActive: boolean;
|
|
42
|
+
isSelecting: boolean;
|
|
43
|
+
selectedComponent: ComponentNode | null;
|
|
44
|
+
toggleActive: () => void;
|
|
45
|
+
selectComponent: (node: ComponentNode) => void;
|
|
46
|
+
clearSelection: () => void;
|
|
47
|
+
updateStyle: (key: string, value: string | number) => void;
|
|
48
|
+
}
|
|
49
|
+
/** WebSocket message protocol — discriminated union. */
|
|
50
|
+
export type StudioMessage = {
|
|
51
|
+
type: 'COMPONENT_SELECTED';
|
|
52
|
+
payload: ComponentNode;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'STYLE_CHANGE';
|
|
55
|
+
payload: {
|
|
56
|
+
source: SourceLocation;
|
|
57
|
+
key: string;
|
|
58
|
+
value: string | number;
|
|
59
|
+
};
|
|
60
|
+
} | {
|
|
61
|
+
type: 'PROP_CHANGE';
|
|
62
|
+
payload: {
|
|
63
|
+
source: SourceLocation;
|
|
64
|
+
propName: string;
|
|
65
|
+
value: unknown;
|
|
66
|
+
};
|
|
67
|
+
} | {
|
|
68
|
+
type: 'PING';
|
|
69
|
+
} | {
|
|
70
|
+
type: 'ACK';
|
|
71
|
+
payload: {
|
|
72
|
+
success: boolean;
|
|
73
|
+
message?: string;
|
|
74
|
+
};
|
|
75
|
+
} | {
|
|
76
|
+
type: 'ERROR';
|
|
77
|
+
payload: {
|
|
78
|
+
message: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/** Studio state machine states. */
|
|
82
|
+
export type StudioState = 'IDLE' | 'ACTIVE' | 'SELECTING' | 'SELECTED' | 'EDITING';
|
|
83
|
+
/**
|
|
84
|
+
* Ambient augmentation so TSX files can safely pass `__rnStudioSource`
|
|
85
|
+
* to host components without type errors. The babel plugin injects this
|
|
86
|
+
* prop on every JSX element.
|
|
87
|
+
*/
|
|
88
|
+
declare global {
|
|
89
|
+
namespace React {
|
|
90
|
+
interface DOMAttributes<T> {
|
|
91
|
+
__rnStudioSource?: SourceLocation;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAChD,IAAI,CAAC,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,aAAa,GACb,WAAW,GACX,UAAU,CAAC;AAEf,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,aAAa,GAAG,IAAI,CAAC;IACxC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CAC5D;AAED,wDAAwD;AACxD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GACtD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE;QACP,MAAM,EAAE,cAAc,CAAC;QACvB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;KACxB,CAAC;CACH,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE;QACP,MAAM,EAAE,cAAc,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,OAAO,CAAC;KAChB,CAAC;CACH,GACD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEpD,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEnF;;;;GAIG;AACH,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,KAAK,CAAC;QACd,UAAU,aAAa,CAAC,CAAC;YACvB,gBAAgB,CAAC,EAAE,cAAc,CAAC;SACnC;KACF;CACF"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* rn-studio — shared type definitions
|
|
4
|
+
*
|
|
5
|
+
* These types are consumed by both the runtime (React Native) and
|
|
6
|
+
* the Node.js side (CLI server, AST engine). Keep this file free of
|
|
7
|
+
* runtime imports so it can be used from either environment.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ComponentNode, SourceLocation, StyleProperty } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fiber walker utilities
|
|
4
|
+
*
|
|
5
|
+
* Given a React Native host element (or its measured layout rectangle),
|
|
6
|
+
* walk the React fiber tree to find the nearest owning component that
|
|
7
|
+
* carries a `__rnStudioSource` prop (injected by babel-plugin-rn-studio).
|
|
8
|
+
*
|
|
9
|
+
* Note: React Native does not expose a document-like hit-testing API,
|
|
10
|
+
* so the overlay captures raw touch coordinates and relies on the
|
|
11
|
+
* caller passing a candidate ref — then this walker climbs from there
|
|
12
|
+
* looking for source metadata.
|
|
13
|
+
*/
|
|
14
|
+
type AnyFiber = any;
|
|
15
|
+
/**
|
|
16
|
+
* Walk up a fiber chain, returning the first ancestor that carries
|
|
17
|
+
* `__rnStudioSource` metadata.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findSourceOwner(startFiber: AnyFiber): {
|
|
20
|
+
fiber: AnyFiber;
|
|
21
|
+
source: SourceLocation;
|
|
22
|
+
} | null;
|
|
23
|
+
export declare function fiberFromRef(ref: any): AnyFiber | null;
|
|
24
|
+
/**
|
|
25
|
+
* Derive a list of editable StyleProperty entries from a raw style prop,
|
|
26
|
+
* which may be an object, an array, or a StyleSheet id (number).
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractStyles(rawStyle: unknown): StyleProperty[];
|
|
29
|
+
/**
|
|
30
|
+
* Build a lightweight ComponentNode from a fiber with source metadata.
|
|
31
|
+
* Children are resolved lazily (one level deep) so the tree view can
|
|
32
|
+
* render without traversing the entire app.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildComponentNode(fiber: AnyFiber, source: SourceLocation): ComponentNode;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=fiberWalker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fiberWalker.d.ts","sourceRoot":"","sources":["../../src/utils/fiberWalker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7E;;;;;;;;;;;GAWG;AAMH,KAAK,QAAQ,GAAG,GAAG,CAAC;AAuBpB;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,QAAQ,GACnB;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAAG,IAAI,CAQpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,GAAG,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa,EAAE,CAoBhE;AAqBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,cAAc,GACrB,aAAa,CAkCf"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findSourceOwner = findSourceOwner;
|
|
4
|
+
exports.fiberFromRef = fiberFromRef;
|
|
5
|
+
exports.extractStyles = extractStyles;
|
|
6
|
+
exports.buildComponentNode = buildComponentNode;
|
|
7
|
+
function getFiberFromRef(ref) {
|
|
8
|
+
if (!ref)
|
|
9
|
+
return null;
|
|
10
|
+
const keys = Object.keys(ref);
|
|
11
|
+
const fiberKey = keys.find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
|
|
12
|
+
if (fiberKey)
|
|
13
|
+
return ref[fiberKey];
|
|
14
|
+
if (ref._reactInternalFiber)
|
|
15
|
+
return ref._reactInternalFiber;
|
|
16
|
+
if (ref.stateNode)
|
|
17
|
+
return ref;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function getSourceFromFiber(fiber) {
|
|
21
|
+
if (!fiber)
|
|
22
|
+
return null;
|
|
23
|
+
const props = fiber.memoizedProps || fiber.pendingProps;
|
|
24
|
+
if (props && props.__rnStudioSource) {
|
|
25
|
+
return props.__rnStudioSource;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Walk up a fiber chain, returning the first ancestor that carries
|
|
31
|
+
* `__rnStudioSource` metadata.
|
|
32
|
+
*/
|
|
33
|
+
function findSourceOwner(startFiber) {
|
|
34
|
+
let current = startFiber;
|
|
35
|
+
while (current) {
|
|
36
|
+
const src = getSourceFromFiber(current);
|
|
37
|
+
if (src)
|
|
38
|
+
return { fiber: current, source: src };
|
|
39
|
+
current = current.return;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function fiberFromRef(ref) {
|
|
44
|
+
return getFiberFromRef(ref);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Derive a list of editable StyleProperty entries from a raw style prop,
|
|
48
|
+
* which may be an object, an array, or a StyleSheet id (number).
|
|
49
|
+
*/
|
|
50
|
+
function extractStyles(rawStyle) {
|
|
51
|
+
const flat = {};
|
|
52
|
+
const visit = (s) => {
|
|
53
|
+
if (!s)
|
|
54
|
+
return;
|
|
55
|
+
if (Array.isArray(s)) {
|
|
56
|
+
s.forEach(visit);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (typeof s === 'object') {
|
|
60
|
+
Object.assign(flat, s);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
visit(rawStyle);
|
|
64
|
+
return Object.entries(flat).map(([key, value]) => {
|
|
65
|
+
const type = inferStyleType(key, value);
|
|
66
|
+
return { key, value, type };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function inferStyleType(key, value) {
|
|
70
|
+
if (typeof value === 'number')
|
|
71
|
+
return 'number';
|
|
72
|
+
if (typeof value === 'boolean')
|
|
73
|
+
return 'boolean';
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
// Heuristic: anything that looks like a color becomes 'color'.
|
|
76
|
+
const colorish = /color/i.test(key) ||
|
|
77
|
+
/^#[0-9a-f]{3,8}$/i.test(value) ||
|
|
78
|
+
/^rgba?\(/i.test(value) ||
|
|
79
|
+
/^hsla?\(/i.test(value);
|
|
80
|
+
if (colorish)
|
|
81
|
+
return 'color';
|
|
82
|
+
return 'string';
|
|
83
|
+
}
|
|
84
|
+
return 'string';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build a lightweight ComponentNode from a fiber with source metadata.
|
|
88
|
+
* Children are resolved lazily (one level deep) so the tree view can
|
|
89
|
+
* render without traversing the entire app.
|
|
90
|
+
*/
|
|
91
|
+
function buildComponentNode(fiber, source) {
|
|
92
|
+
const props = (fiber.memoizedProps || {});
|
|
93
|
+
const styles = extractStyles(props.style);
|
|
94
|
+
const id = `${source.file}:${source.line}:${source.column}`;
|
|
95
|
+
const children = [];
|
|
96
|
+
let child = fiber.child;
|
|
97
|
+
let safety = 0;
|
|
98
|
+
while (child && safety < 50) {
|
|
99
|
+
const sub = getSourceFromFiber(child);
|
|
100
|
+
if (sub) {
|
|
101
|
+
children.push({
|
|
102
|
+
id: `${sub.file}:${sub.line}:${sub.column}`,
|
|
103
|
+
componentName: sub.componentName,
|
|
104
|
+
source: sub,
|
|
105
|
+
props: (child.memoizedProps || {}),
|
|
106
|
+
styles: extractStyles((child.memoizedProps || {}).style),
|
|
107
|
+
children: [],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
child = child.sibling;
|
|
111
|
+
safety++;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
id,
|
|
115
|
+
componentName: source.componentName,
|
|
116
|
+
source,
|
|
117
|
+
props,
|
|
118
|
+
styles,
|
|
119
|
+
children,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=fiberWalker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fiberWalker.js","sourceRoot":"","sources":["../../src/utils/fiberWalker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* measureComponent
|
|
3
|
+
*
|
|
4
|
+
* Thin promise wrapper around React Native's `measureInWindow` for use
|
|
5
|
+
* by the SelectionOverlay when drawing the highlight box around a
|
|
6
|
+
* selected component.
|
|
7
|
+
*/
|
|
8
|
+
export interface Rect {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function measureInWindow(ref: any): Promise<Rect | null>;
|
|
15
|
+
//# sourceMappingURL=measureComponent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measureComponent.d.ts","sourceRoot":"","sources":["../../src/utils/measureComponent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAuB9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.measureInWindow = measureInWindow;
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
function measureInWindow(ref) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
if (!ref || typeof ref.measureInWindow !== 'function') {
|
|
8
|
+
resolve(null);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
ref.measureInWindow((x, y, width, height) => {
|
|
13
|
+
if (typeof x !== 'number' ||
|
|
14
|
+
typeof y !== 'number' ||
|
|
15
|
+
typeof width !== 'number' ||
|
|
16
|
+
typeof height !== 'number') {
|
|
17
|
+
resolve(null);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
resolve({ x, y, width, height });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
resolve(null);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=measureComponent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measureComponent.js","sourceRoot":"","sources":["../../src/utils/measureComponent.ts"],"names":[],"mappings":";;AAeA,0CAuBC;AAxBD,uDAAuD;AACvD,SAAgB,eAAe,CAAC,GAAQ;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;YACtD,OAAO,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,eAAe,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,KAAa,EAAE,MAAc,EAAE,EAAE;gBAC1E,IACE,OAAO,CAAC,KAAK,QAAQ;oBACrB,OAAO,CAAC,KAAK,QAAQ;oBACrB,OAAO,KAAK,KAAK,QAAQ;oBACzB,OAAO,MAAM,KAAK,QAAQ,EAC1B,CAAC;oBACD,OAAO,CAAC,IAAI,CAAC,CAAC;oBACd,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rn-studio",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Live UI editor for React Native — inspect and edit components directly in your emulator",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"babel-plugin",
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"rn-studio-server": "./bin/rn-studio-server.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepare": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./babel-plugin": "./babel-plugin/index.js"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=18",
|
|
29
|
+
"react-native": ">=0.70",
|
|
30
|
+
"react-native-gesture-handler": ">=2.0",
|
|
31
|
+
"react-native-reanimated": ">=3.0",
|
|
32
|
+
"react-native-haptic-feedback": ">=2.0",
|
|
33
|
+
"react-native-safe-area-context": ">=4.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"react-native-gesture-handler": { "optional": true },
|
|
37
|
+
"react-native-reanimated": { "optional": true },
|
|
38
|
+
"react-native-haptic-feedback": { "optional": true },
|
|
39
|
+
"react-native-safe-area-context": { "optional": true }
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@babel/parser": "^7.23.0",
|
|
43
|
+
"recast": "^0.23.0",
|
|
44
|
+
"ws": "^8.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.3.0",
|
|
48
|
+
"@types/ws": "^8.5.0",
|
|
49
|
+
"@types/react": "^18.2.0",
|
|
50
|
+
"react": "18.2.0",
|
|
51
|
+
"react-native": "0.73.6"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"react-native",
|
|
55
|
+
"devtools",
|
|
56
|
+
"ui-editor",
|
|
57
|
+
"live-edit",
|
|
58
|
+
"inspector",
|
|
59
|
+
"ast",
|
|
60
|
+
"babel"
|
|
61
|
+
],
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"author": "dgutierrezd",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/dgutierrezd/rn-studio.git"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://github.com/dgutierrezd/rn-studio#readme",
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://github.com/dgutierrezd/rn-studio/issues"
|
|
71
|
+
}
|
|
72
|
+
}
|