rn-studio 0.2.1 → 0.3.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 (45) hide show
  1. package/bin/rn-studio-init.js +173 -0
  2. package/bin/rn-studio-server.js +77 -14
  3. package/dist/StudioProvider.d.ts +5 -18
  4. package/dist/StudioProvider.d.ts.map +1 -1
  5. package/dist/StudioProvider.js +118 -41
  6. package/dist/StudioProvider.js.map +1 -1
  7. package/dist/ast/AstEngine.d.ts.map +1 -1
  8. package/dist/ast/AstEngine.js +10 -0
  9. package/dist/ast/AstEngine.js.map +1 -1
  10. package/dist/ast/UndoStack.d.ts +18 -0
  11. package/dist/ast/UndoStack.d.ts.map +1 -0
  12. package/dist/ast/UndoStack.js +105 -0
  13. package/dist/ast/UndoStack.js.map +1 -0
  14. package/dist/components/AddPropertyModal.d.ts +19 -0
  15. package/dist/components/AddPropertyModal.d.ts.map +1 -0
  16. package/dist/components/AddPropertyModal.js +174 -0
  17. package/dist/components/AddPropertyModal.js.map +1 -0
  18. package/dist/components/InspectorPanel.js +35 -5
  19. package/dist/components/InspectorPanel.js.map +1 -1
  20. package/dist/components/SelectionOverlay.d.ts.map +1 -1
  21. package/dist/components/SelectionOverlay.js +5 -0
  22. package/dist/components/SelectionOverlay.js.map +1 -1
  23. package/dist/components/StyleEditor.d.ts +5 -3
  24. package/dist/components/StyleEditor.d.ts.map +1 -1
  25. package/dist/components/StyleEditor.js +45 -11
  26. package/dist/components/StyleEditor.js.map +1 -1
  27. package/dist/data/styleProperties.d.ts +26 -0
  28. package/dist/data/styleProperties.d.ts.map +1 -0
  29. package/dist/data/styleProperties.js +142 -0
  30. package/dist/data/styleProperties.js.map +1 -0
  31. package/dist/types.d.ts +19 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/utils/autoScroll.d.ts +15 -0
  34. package/dist/utils/autoScroll.d.ts.map +1 -0
  35. package/dist/utils/autoScroll.js +132 -0
  36. package/dist/utils/autoScroll.js.map +1 -0
  37. package/dist/utils/findFiberBySource.d.ts +17 -0
  38. package/dist/utils/findFiberBySource.d.ts.map +1 -0
  39. package/dist/utils/findFiberBySource.js +76 -0
  40. package/dist/utils/findFiberBySource.js.map +1 -0
  41. package/dist/utils/persistence.d.ts +12 -0
  42. package/dist/utils/persistence.d.ts.map +1 -0
  43. package/dist/utils/persistence.js +44 -0
  44. package/dist/utils/persistence.js.map +1 -0
  45. package/package.json +3 -2
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * rn-studio init
5
+ *
6
+ * Zero-config bootstrap for consumer apps. Run:
7
+ *
8
+ * npx rn-studio init
9
+ *
10
+ * This command:
11
+ * 1. Adds `rn-studio/babel-plugin` to the project's babel.config.js
12
+ * (gated on `process.env.NODE_ENV !== 'production'`)
13
+ * 2. Adds a `"studio": "rn-studio-server"` script to package.json
14
+ * 3. Prints the exact snippet to paste into App.tsx
15
+ */
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const cwd = process.cwd();
20
+ const INDIGO = '\x1b[38;5;111m';
21
+ const GREEN = '\x1b[32m';
22
+ const GREY = '\x1b[90m';
23
+ const BOLD = '\x1b[1m';
24
+ const RESET = '\x1b[0m';
25
+
26
+ function log(msg) { console.log(msg); }
27
+ function ok(msg) { console.log(`${GREEN}✓${RESET} ${msg}`); }
28
+ function info(msg) { console.log(`${INDIGO}→${RESET} ${msg}`); }
29
+ function warn(msg) { console.log(`${GREY}! ${msg}${RESET}`); }
30
+
31
+ function banner() {
32
+ console.log('');
33
+ console.log(` ${INDIGO}${BOLD}rn-studio init${RESET}`);
34
+ console.log(` ${GREY}Live UI editor for React Native${RESET}`);
35
+ console.log('');
36
+ }
37
+
38
+ /* ───────────────── package.json ───────────────── */
39
+ function patchPackageJson() {
40
+ const pkgPath = path.join(cwd, 'package.json');
41
+ if (!fs.existsSync(pkgPath)) {
42
+ warn('No package.json found — skipping script injection.');
43
+ return;
44
+ }
45
+ const raw = fs.readFileSync(pkgPath, 'utf-8');
46
+ const pkg = JSON.parse(raw);
47
+ pkg.scripts = pkg.scripts || {};
48
+
49
+ if (pkg.scripts.studio === 'rn-studio-server') {
50
+ ok('package.json already has a "studio" script.');
51
+ return;
52
+ }
53
+ if (pkg.scripts.studio && pkg.scripts.studio !== 'rn-studio-server') {
54
+ warn(`package.json has a different "studio" script (${pkg.scripts.studio}). Leaving untouched.`);
55
+ return;
56
+ }
57
+ pkg.scripts.studio = 'rn-studio-server';
58
+ // Preserve two-space indentation convention.
59
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
60
+ ok('Added `"studio": "rn-studio-server"` to package.json scripts.');
61
+ }
62
+
63
+ /* ───────────────── babel.config.js ───────────────── */
64
+ function patchBabelConfig() {
65
+ const candidates = [
66
+ 'babel.config.js',
67
+ 'babel.config.cjs',
68
+ 'babel.config.mjs',
69
+ '.babelrc.js',
70
+ '.babelrc',
71
+ ];
72
+ const found = candidates
73
+ .map((c) => path.join(cwd, c))
74
+ .find((p) => fs.existsSync(p));
75
+
76
+ if (!found) {
77
+ warn('No babel.config.js found — creating one.');
78
+ const fresh = `module.exports = {
79
+ presets: ['module:@react-native/babel-preset'],
80
+ plugins: [
81
+ ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : []),
82
+ ],
83
+ };
84
+ `;
85
+ fs.writeFileSync(path.join(cwd, 'babel.config.js'), fresh, 'utf-8');
86
+ ok('Created babel.config.js with rn-studio plugin registered.');
87
+ return;
88
+ }
89
+
90
+ const content = fs.readFileSync(found, 'utf-8');
91
+ if (content.indexOf('rn-studio/babel-plugin') !== -1) {
92
+ ok(`${path.basename(found)} already references rn-studio/babel-plugin.`);
93
+ return;
94
+ }
95
+
96
+ // Attempt a light, string-based injection. If the file has a
97
+ // `plugins: [ ... ]` array, append the spread expression; otherwise
98
+ // add a new plugins property after the presets line.
99
+ let patched;
100
+ if (/plugins\s*:\s*\[/.test(content)) {
101
+ patched = content.replace(
102
+ /plugins\s*:\s*\[/,
103
+ `plugins: [\n ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : []),`,
104
+ );
105
+ } else {
106
+ patched = content.replace(
107
+ /presets\s*:\s*\[[^\]]*\]\s*,?/,
108
+ (m) => `${m.replace(/,?\s*$/, ',')}\n plugins: [\n ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : []),\n ],`,
109
+ );
110
+ }
111
+
112
+ if (patched === content) {
113
+ warn(`Could not auto-patch ${path.basename(found)} — add this manually:`);
114
+ console.log(
115
+ ` plugins: [\n ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : []),\n ],`,
116
+ );
117
+ return;
118
+ }
119
+
120
+ fs.writeFileSync(found, patched, 'utf-8');
121
+ ok(`Patched ${path.basename(found)} to include rn-studio/babel-plugin.`);
122
+ }
123
+
124
+ /* ───────────────── App.tsx snippet ───────────────── */
125
+ function printAppSnippet() {
126
+ console.log('');
127
+ info('Add this to your App.tsx (or wherever you mount the root):');
128
+ console.log('');
129
+ console.log(`${GREY}────────────────────────────────────────────────${RESET}`);
130
+ console.log(`${INDIGO}import${RESET} { StudioProvider } ${INDIGO}from${RESET} 'rn-studio';`);
131
+ console.log('');
132
+ console.log(`${INDIGO}export default function${RESET} App() {`);
133
+ console.log(' return (');
134
+ console.log(` <StudioProvider enabled={__DEV__} bubblePosition="bottom-right">`);
135
+ console.log(' <YourApp />');
136
+ console.log(' </StudioProvider>');
137
+ console.log(' );');
138
+ console.log('}');
139
+ console.log(`${GREY}────────────────────────────────────────────────${RESET}`);
140
+ console.log('');
141
+ }
142
+
143
+ /* ───────────────── run instructions ───────────────── */
144
+ function printRunInstructions() {
145
+ console.log(`${BOLD}Next steps:${RESET}`);
146
+ console.log('');
147
+ console.log(` ${GREY}#${RESET} Terminal 1 — Metro`);
148
+ console.log(` ${INDIGO}npx${RESET} react-native start`);
149
+ console.log('');
150
+ console.log(` ${GREY}#${RESET} Terminal 2 — rn-studio server`);
151
+ console.log(` ${INDIGO}npm run${RESET} studio`);
152
+ console.log('');
153
+ console.log(
154
+ `${GREEN}✓ Setup complete.${RESET} Launch your app, tap the floating bubble,\n then tap any component to edit its styles live.`,
155
+ );
156
+ console.log('');
157
+ }
158
+
159
+ /* ───────────────── main ───────────────── */
160
+ function main() {
161
+ banner();
162
+ try {
163
+ patchPackageJson();
164
+ patchBabelConfig();
165
+ printAppSnippet();
166
+ printRunInstructions();
167
+ } catch (err) {
168
+ console.error(`\n${GREY}!${RESET} rn-studio init failed:`, err && err.message);
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ main();
@@ -5,19 +5,21 @@
5
5
  *
6
6
  * Run alongside Metro: npm run studio
7
7
  *
8
- * Responsibilities:
9
- * 1. Listen on ws://localhost:7878 for messages from the rn-studio runtime.
10
- * 2. On STYLE_CHANGE, dispatch to the AST engine which rewrites the
11
- * source file. Metro's Fast Refresh instantly propagates the edit.
8
+ * Handles:
9
+ * - STYLE_CHANGE AST engine rewrites the source file
10
+ * - UNDO / REDO → pops/pushes the in-memory edit stack
11
+ * - STACK_STATE broadcast so clients can enable/disable buttons
12
12
  */
13
13
  const { WebSocketServer } = require('ws');
14
14
 
15
- let rewriteStyle;
15
+ let AstEngine;
16
+ let UndoStack;
16
17
  try {
17
- ({ rewriteStyle } = require('../dist/ast/AstEngine'));
18
+ AstEngine = require('../dist/ast/AstEngine');
19
+ UndoStack = require('../dist/ast/UndoStack');
18
20
  } catch (err) {
19
21
  console.error(
20
- '[rn-studio] Unable to load dist/ast/AstEngine. Did you run `npm run build`?'
22
+ '[rn-studio] Unable to load dist modules. Did you run `npm run build`?',
21
23
  );
22
24
  console.error(err.message);
23
25
  process.exit(1);
@@ -29,8 +31,22 @@ const wss = new WebSocketServer({ port: PORT });
29
31
  console.log(`[rn-studio] Server running on ws://localhost:${PORT}`);
30
32
  console.log('[rn-studio] Waiting for React Native runtime to connect...');
31
33
 
34
+ function broadcastStackState(ws) {
35
+ const state = UndoStack.getStackState();
36
+ const payload = JSON.stringify({ type: 'STACK_STATE', payload: state });
37
+ if (ws) {
38
+ ws.send(payload);
39
+ } else {
40
+ wss.clients.forEach((c) => {
41
+ if (c.readyState === 1) c.send(payload);
42
+ });
43
+ }
44
+ }
45
+
32
46
  wss.on('connection', (ws) => {
33
47
  console.log('[rn-studio] Client connected');
48
+ // Sync new clients with the current stack depths.
49
+ broadcastStackState(ws);
34
50
 
35
51
  ws.on('message', async (raw) => {
36
52
  let msg;
@@ -41,7 +57,7 @@ wss.on('connection', (ws) => {
41
57
  JSON.stringify({
42
58
  type: 'ERROR',
43
59
  payload: { message: 'Invalid JSON payload' },
44
- })
60
+ }),
45
61
  );
46
62
  return;
47
63
  }
@@ -54,7 +70,7 @@ wss.on('connection', (ws) => {
54
70
 
55
71
  if (msg.type === 'STYLE_CHANGE') {
56
72
  const { source, key, value } = msg.payload;
57
- await rewriteStyle({
73
+ await AstEngine.rewriteStyle({
58
74
  file: source.file,
59
75
  line: source.line,
60
76
  column: source.column,
@@ -63,18 +79,65 @@ wss.on('connection', (ws) => {
63
79
  });
64
80
  ws.send(JSON.stringify({ type: 'ACK', payload: { success: true } }));
65
81
  console.log(
66
- `[rn-studio] ✓ ${source.componentName} → ${key}: ${value}`
82
+ `[rn-studio] ✓ ${source.componentName} → ${key}: ${value}`,
67
83
  );
84
+ broadcastStackState();
85
+ return;
86
+ }
87
+
88
+ if (msg.type === 'UNDO') {
89
+ const entry = UndoStack.undo();
90
+ if (entry) {
91
+ console.log(`[rn-studio] ↶ undo: ${entry.label} (${entry.file})`);
92
+ ws.send(
93
+ JSON.stringify({
94
+ type: 'ACK',
95
+ payload: { success: true, message: 'undo' },
96
+ }),
97
+ );
98
+ } else {
99
+ ws.send(
100
+ JSON.stringify({
101
+ type: 'ACK',
102
+ payload: { success: false, message: 'Nothing to undo' },
103
+ }),
104
+ );
105
+ }
106
+ broadcastStackState();
107
+ return;
108
+ }
109
+
110
+ if (msg.type === 'REDO') {
111
+ const entry = UndoStack.redo();
112
+ if (entry) {
113
+ console.log(`[rn-studio] ↷ redo: ${entry.label} (${entry.file})`);
114
+ ws.send(
115
+ JSON.stringify({
116
+ type: 'ACK',
117
+ payload: { success: true, message: 'redo' },
118
+ }),
119
+ );
120
+ } else {
121
+ ws.send(
122
+ JSON.stringify({
123
+ type: 'ACK',
124
+ payload: { success: false, message: 'Nothing to redo' },
125
+ }),
126
+ );
127
+ }
128
+ broadcastStackState();
68
129
  return;
69
130
  }
70
131
 
71
132
  if (msg.type === 'PROP_CHANGE') {
72
- // Reserved for future prop editing. Ack for now.
73
133
  ws.send(
74
134
  JSON.stringify({
75
135
  type: 'ACK',
76
- payload: { success: true, message: 'PROP_CHANGE not yet implemented' },
77
- })
136
+ payload: {
137
+ success: true,
138
+ message: 'PROP_CHANGE not yet implemented',
139
+ },
140
+ }),
78
141
  );
79
142
  return;
80
143
  }
@@ -84,7 +147,7 @@ wss.on('connection', (ws) => {
84
147
  JSON.stringify({
85
148
  type: 'ERROR',
86
149
  payload: { message: err && err.message ? err.message : String(err) },
87
- })
150
+ }),
88
151
  );
89
152
  }
90
153
  });
@@ -1,5 +1,5 @@
1
1
  import React, { MutableRefObject } from 'react';
2
- import type { BubblePosition, StudioConfig, StudioContextValue } from './types';
2
+ import type { BubblePosition, SourceLocation, StudioConfig, StudioContextValue } from './types';
3
3
  interface Props extends Partial<StudioConfig> {
4
4
  enabled?: boolean;
5
5
  serverPort?: number;
@@ -8,26 +8,13 @@ interface Props extends Partial<StudioConfig> {
8
8
  children: React.ReactNode;
9
9
  }
10
10
  /**
11
- * Shared mutable ref to the app root view. Populated by `<StudioProvider>`
12
- * and consumed by `<SelectionOverlay>` for hit-testing via the React
13
- * DevTools `getInspectorDataForViewAtPoint` API. Exposing it at module
14
- * scope avoids having to thread it through context (and keeps the
15
- * context value shape unchanged for consumers).
11
+ * Shared mutable ref to the app root view. Populated by
12
+ * `<StudioProvider>` and consumed by `<SelectionOverlay>` for
13
+ * hit-testing via `getInspectorDataForViewAtPoint`.
16
14
  */
17
15
  export declare const appRootRef: MutableRefObject<any>;
18
- /**
19
- * <StudioProvider>
20
- *
21
- * Wrap your App.tsx with this provider. When `enabled` is false (the
22
- * default, intended for production), it renders children verbatim and
23
- * introduces zero overhead — no context, no bridge, no overlay.
24
- *
25
- * When enabled, it manages the studio state machine, opens a WebSocket
26
- * connection to the CLI server, and renders the floating bubble,
27
- * selection overlay, and inspector panel above your app.
28
- */
29
16
  export declare function StudioProvider({ children, enabled, serverPort, bubblePosition, }: Props): React.JSX.Element;
17
+ export type { SourceLocation };
30
18
  /** Hook for any descendant of `<StudioProvider>` to read studio state. */
31
19
  export declare function useStudio(): StudioContextValue;
32
- export {};
33
20
  //# sourceMappingURL=StudioProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StudioProvider.d.ts","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EACZ,gBAAgB,EAKjB,MAAM,OAAO,CAAC;AAOf,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EACZ,kBAAkB,EAEnB,MAAM,SAAS,CAAC;AAEjB,UAAU,KAAM,SAAQ,OAAO,CAAC,YAAY,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,gBAAgB,CAAC,GAAG,CAAqB,CAAC;AAEnE;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,OAAe,EACf,UAAiB,EACjB,cAA+B,GAChC,EAAE,KAAK,qBAUP;AAyFD,0EAA0E;AAC1E,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
1
+ {"version":3,"file":"StudioProvider.d.ts","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EACZ,gBAAgB,EAMjB,MAAM,OAAO,CAAC;AAYf,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EACd,YAAY,EACZ,kBAAkB,EAEnB,MAAM,SAAS,CAAC;AAEjB,UAAU,KAAM,SAAQ,OAAO,CAAC,YAAY,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;GAIG;AAEH,eAAO,MAAM,UAAU,EAAE,gBAAgB,CAAC,GAAG,CAAqB,CAAC;AAEnE,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,OAAe,EACf,UAAiB,EACjB,cAA+B,GAChC,EAAE,KAAK,qBAYP;AA4LD,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B,0EAA0E;AAC1E,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
@@ -43,25 +43,15 @@ const WebSocketBridge_1 = require("./bridge/WebSocketBridge");
43
43
  const FloatingBubble_1 = require("./components/FloatingBubble");
44
44
  const SelectionOverlay_1 = require("./components/SelectionOverlay");
45
45
  const InspectorPanel_1 = require("./components/InspectorPanel");
46
+ const persistence_1 = require("./utils/persistence");
47
+ const findFiberBySource_1 = require("./utils/findFiberBySource");
46
48
  /**
47
- * Shared mutable ref to the app root view. Populated by `<StudioProvider>`
48
- * and consumed by `<SelectionOverlay>` for hit-testing via the React
49
- * DevTools `getInspectorDataForViewAtPoint` API. Exposing it at module
50
- * scope avoids having to thread it through context (and keeps the
51
- * context value shape unchanged for consumers).
49
+ * Shared mutable ref to the app root view. Populated by
50
+ * `<StudioProvider>` and consumed by `<SelectionOverlay>` for
51
+ * hit-testing via `getInspectorDataForViewAtPoint`.
52
52
  */
53
+ /* eslint-disable @typescript-eslint/no-explicit-any */
53
54
  exports.appRootRef = { current: null };
54
- /**
55
- * <StudioProvider>
56
- *
57
- * Wrap your App.tsx with this provider. When `enabled` is false (the
58
- * default, intended for production), it renders children verbatim and
59
- * introduces zero overhead — no context, no bridge, no overlay.
60
- *
61
- * When enabled, it manages the studio state machine, opens a WebSocket
62
- * connection to the CLI server, and renders the floating bubble,
63
- * selection overlay, and inspector panel above your app.
64
- */
65
55
  function StudioProvider({ children, enabled = false, serverPort = 7878, bubblePosition = 'bottom-right', }) {
66
56
  if (!enabled) {
67
57
  return react_1.default.createElement(react_1.default.Fragment, null, children);
@@ -71,50 +61,124 @@ function StudioProvider({ children, enabled = false, serverPort = 7878, bubblePo
71
61
  const StudioProviderInner = ({ serverPort, bubblePosition, children }) => {
72
62
  const [state, setState] = (0, react_1.useState)('IDLE');
73
63
  const [selectedComponent, setSelectedComponent] = (0, react_1.useState)(null);
64
+ const [canUndo, setCanUndo] = (0, react_1.useState)(false);
65
+ const [canRedo, setCanRedo] = (0, react_1.useState)(false);
74
66
  const bridgeRef = (0, react_1.useRef)(null);
75
67
  if (!bridgeRef.current) {
76
68
  bridgeRef.current = new WebSocketBridge_1.WebSocketBridge(serverPort);
77
69
  }
70
+ // Connect + subscribe to server messages.
78
71
  (0, react_1.useEffect)(() => {
79
72
  const bridge = bridgeRef.current;
80
73
  bridge.connect();
81
- const off = bridge.on('ACK', () => {
82
- // Per-editor success feedback is handled via the debounce timers
83
- // in StyleEditor rows.
74
+ const offStack = bridge.on('STACK_STATE', (msg) => {
75
+ if (msg.payload) {
76
+ setCanUndo(msg.payload.undo > 0);
77
+ setCanRedo(msg.payload.redo > 0);
78
+ }
84
79
  });
85
80
  return () => {
86
- off();
81
+ offStack();
87
82
  bridge.disconnect();
88
83
  };
89
84
  }, []);
90
- const toggleActive = () => {
85
+ // Persist the last-selected source whenever it changes.
86
+ (0, react_1.useEffect)(() => {
87
+ if (selectedComponent) {
88
+ (0, persistence_1.saveLastSelection)(selectedComponent.source).catch(() => { });
89
+ }
90
+ }, [selectedComponent]);
91
+ // On initial mount, attempt to re-select the previously selected
92
+ // component (survives Cmd+R and Fast Refresh). Best-effort only.
93
+ (0, react_1.useEffect)(() => {
94
+ let cancelled = false;
95
+ (async () => {
96
+ const saved = await (0, persistence_1.loadLastSelection)();
97
+ if (!saved || cancelled)
98
+ return;
99
+ // Wait a frame for the fiber tree to commit.
100
+ setTimeout(() => {
101
+ if (cancelled)
102
+ return;
103
+ const fiber = (0, findFiberBySource_1.findFiberBySource)(saved);
104
+ if (!fiber)
105
+ return;
106
+ const props = (fiber.memoizedProps || {});
107
+ const node = {
108
+ id: `${saved.file}:${saved.line}:${saved.column}`,
109
+ componentName: saved.componentName,
110
+ source: saved,
111
+ props,
112
+ styles: [],
113
+ children: [],
114
+ };
115
+ // Silently restore in ACTIVE state so the user can tap the
116
+ // bubble to resume editing, without jarring re-opening the
117
+ // inspector automatically.
118
+ setSelectedComponent(node);
119
+ setState('ACTIVE');
120
+ }, 500);
121
+ })();
122
+ return () => {
123
+ cancelled = true;
124
+ };
125
+ }, []);
126
+ const toggleActive = (0, react_1.useCallback)(() => {
91
127
  setState((s) => {
92
128
  if (s === 'IDLE')
93
129
  return 'ACTIVE';
94
130
  setSelectedComponent(null);
95
131
  return 'IDLE';
96
132
  });
97
- };
98
- const selectComponent = (node) => {
133
+ }, []);
134
+ const selectComponent = (0, react_1.useCallback)((node) => {
99
135
  setSelectedComponent(node);
100
136
  setState('SELECTED');
101
- };
102
- const clearSelection = () => {
137
+ }, []);
138
+ const clearSelection = (0, react_1.useCallback)(() => {
103
139
  setSelectedComponent(null);
104
140
  setState('ACTIVE');
105
- };
106
- const updateStyle = (key, value) => {
107
- if (!selectedComponent)
141
+ }, []);
142
+ const updateStyle = (0, react_1.useCallback)((key, value) => {
143
+ const current = selectedComponent;
144
+ if (!current)
108
145
  return;
109
146
  bridgeRef.current?.send({
110
147
  type: 'STYLE_CHANGE',
111
- payload: {
112
- source: selectedComponent.source,
113
- key,
114
- value,
115
- },
148
+ payload: { source: current.source, key, value },
116
149
  });
117
- };
150
+ // Optimistically update the local styles list so the editor
151
+ // reflects the new value without a round-trip.
152
+ setSelectedComponent({
153
+ ...current,
154
+ styles: upsertLocalStyle(current.styles, key, value),
155
+ });
156
+ }, [selectedComponent]);
157
+ const addStyleProperty = (0, react_1.useCallback)((key, value) => {
158
+ const current = selectedComponent;
159
+ if (!current)
160
+ return;
161
+ // Booleans aren't writable via AST here yet; skip.
162
+ if (typeof value === 'boolean')
163
+ return;
164
+ bridgeRef.current?.send({
165
+ type: 'STYLE_CHANGE',
166
+ payload: { source: current.source, key, value },
167
+ });
168
+ setSelectedComponent({
169
+ ...current,
170
+ styles: upsertLocalStyle(current.styles, key, value),
171
+ });
172
+ }, [selectedComponent]);
173
+ const undo = (0, react_1.useCallback)(() => {
174
+ bridgeRef.current?.send({ type: 'UNDO' });
175
+ }, []);
176
+ const redo = (0, react_1.useCallback)(() => {
177
+ bridgeRef.current?.send({ type: 'REDO' });
178
+ }, []);
179
+ const setAppRootRef = (0, react_1.useCallback)((r) => {
180
+ exports.appRootRef.current = r;
181
+ }, []);
118
182
  const ctx = {
119
183
  isActive: state !== 'IDLE',
120
184
  isSelecting: state === 'SELECTING' || state === 'ACTIVE',
@@ -123,13 +187,11 @@ const StudioProviderInner = ({ serverPort, bubblePosition, children }) => {
123
187
  selectComponent,
124
188
  clearSelection,
125
189
  updateStyle,
126
- };
127
- // Wrap children in a ref'd host View so the SelectionOverlay can
128
- // hit-test against the user's UI via Fabric's inspector data API.
129
- // `collapsable={false}` guarantees the View remains a real native
130
- // node that can be targeted by `getInspectorDataForViewAtPoint`.
131
- const setAppRootRef = (r) => {
132
- exports.appRootRef.current = r;
190
+ addStyleProperty,
191
+ undo,
192
+ redo,
193
+ canUndo,
194
+ canRedo,
133
195
  };
134
196
  return (react_1.default.createElement(StudioContext_1.StudioContext.Provider, { value: ctx },
135
197
  react_1.default.createElement(react_native_1.View, { ref: setAppRootRef, collapsable: false, style: { flex: 1 } }, children),
@@ -137,6 +199,21 @@ const StudioProviderInner = ({ serverPort, bubblePosition, children }) => {
137
199
  react_1.default.createElement(InspectorPanel_1.InspectorPanel, null),
138
200
  react_1.default.createElement(FloatingBubble_1.FloatingBubble, { position: bubblePosition })));
139
201
  };
202
+ function upsertLocalStyle(list, key, value) {
203
+ const existing = list.findIndex((s) => s.key === key);
204
+ const type = typeof value === 'number'
205
+ ? 'number'
206
+ : /color/i.test(key) || /^#[0-9a-f]{3,8}$/i.test(String(value))
207
+ ? 'color'
208
+ : 'string';
209
+ const entry = { key, value, type };
210
+ if (existing >= 0) {
211
+ const next = list.slice();
212
+ next[existing] = entry;
213
+ return next;
214
+ }
215
+ return [...list, entry];
216
+ }
140
217
  /** Hook for any descendant of `<StudioProvider>` to read studio state. */
141
218
  function useStudio() {
142
219
  const ctx = (0, react_1.useContext)(StudioContext_1.StudioContext);
@@ -1 +1 @@
1
- {"version":3,"file":"StudioProvider.js","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StudioProvider.js","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AstEngine.d.ts","sourceRoot":"","sources":["../../src/ast/AstEngine.ts"],"names":[],"mappings":"AAgCA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAgHD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmJtE"}
1
+ {"version":3,"file":"AstEngine.d.ts","sourceRoot":"","sources":["../../src/ast/AstEngine.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAgHD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA6JtE"}
@@ -65,6 +65,7 @@ exports.rewriteStyle = rewriteStyle;
65
65
  const parser = __importStar(require("@babel/parser"));
66
66
  const recast = __importStar(require("recast"));
67
67
  const fs = __importStar(require("fs"));
68
+ const UndoStack_1 = require("./UndoStack");
68
69
  /* eslint-disable @typescript-eslint/no-explicit-any */
69
70
  const b = recast.types.builders;
70
71
  function buildLiteral(value) {
@@ -276,6 +277,15 @@ async function rewriteStyle(opts) {
276
277
  throw new Error(`Matched JSX at ${file}:${line}:${column} but could not write style "${key}" — unsupported style expression form.`);
277
278
  }
278
279
  const output = recast.print(ast).code;
280
+ // No-op guard: don't touch the file if nothing actually changed.
281
+ if (output === source)
282
+ return;
279
283
  fs.writeFileSync(file, output, 'utf-8');
284
+ (0, UndoStack_1.pushEdit)({
285
+ file,
286
+ before: source,
287
+ after: output,
288
+ label: `${key} = ${value}`,
289
+ });
280
290
  }
281
291
  //# sourceMappingURL=AstEngine.js.map