@alivelabs/expo-orchestrator-react-client 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"SessionViewer.d.ts","sourceRoot":"","sources":["../../src/components/SessionViewer.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyHD,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAgJ5F"}
1
+ {"version":3,"file":"SessionViewer.d.ts","sourceRoot":"","sources":["../../src/components/SessionViewer.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyHD,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA4J5F"}
@@ -153,11 +153,14 @@ export function SessionViewer({ sessionId, apiToken, baseUrl, className }) {
153
153
  const handleSwipe = useCallback((input) => {
154
154
  sendInput(input).catch(() => { });
155
155
  }, [sendInput]);
156
+ const handleKeyInput = useCallback((input) => {
157
+ sendInput(input).catch(() => { });
158
+ }, [sendInput]);
156
159
  return (_jsxs("div", { style: rootStyle, className: className, children: [_jsxs("div", { style: headerStyle, children: [_jsxs("h2", { style: titleStyle, children: ["Session ", sessionId] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }, children: [_jsx(StatusBadge, { status: status }), _jsx("span", { style: {
157
160
  fontSize: "11px",
158
161
  color: connected ? "#4ade80" : "#6b7280",
159
162
  fontWeight: 500,
160
- }, children: connected ? "● live" : "○ disconnected" }), !connected && (_jsx("button", { type: "button", style: btnStyle("secondary"), onClick: reconnect, children: "Reconnect" }))] })] }), error && _jsxs("div", { style: errorBannerStyle, children: ["\u26A0 ", error] }), _jsxs("div", { style: bodyStyle, children: [_jsx("div", { style: simulatorColStyle, children: _jsx(SimulatorScreen, { frame: frame, onTap: handleTap, onSwipe: handleSwipe }) }), _jsxs("div", { style: rightColStyle, children: [_jsxs("div", { style: controlsStyle, children: [_jsx("div", { style: {
163
+ }, children: connected ? "● live" : "○ disconnected" }), !connected && (_jsx("button", { type: "button", style: btnStyle("secondary"), onClick: reconnect, children: "Reconnect" }))] })] }), error && _jsxs("div", { style: errorBannerStyle, children: ["\u26A0 ", error] }), _jsxs("div", { style: bodyStyle, children: [_jsx("div", { style: simulatorColStyle, children: _jsx(SimulatorScreen, { frame: frame, onTap: handleTap, onSwipe: handleSwipe, onKeyInput: handleKeyInput }) }), _jsxs("div", { style: rightColStyle, children: [_jsxs("div", { style: controlsStyle, children: [_jsx("div", { style: {
161
164
  fontSize: "11px",
162
165
  fontWeight: 600,
163
166
  color: "#8b949e",
@@ -7,11 +7,17 @@ interface SimulatorScreenProps {
7
7
  onSwipe?: (input: Extract<SimulatorInput, {
8
8
  type: "swipe";
9
9
  }>) => void;
10
+ /** Forward the focused viewer's keystrokes to the simulator (text + key events). */
11
+ onKeyInput?: (input: Extract<SimulatorInput, {
12
+ type: "type";
13
+ } | {
14
+ type: "key";
15
+ }>) => void;
10
16
  /** Cap on the rendered simulator height; the image scales down proportionally
11
17
  * to fit. Accepts any CSS length. Defaults to `80vh` so the device never
12
18
  * exceeds the viewport on a tall phone like iPhone 17 Pro Max. */
13
19
  maxHeight?: number | string;
14
20
  }
15
- export declare function SimulatorScreen({ frame, onTap, onSwipe, maxHeight, }: SimulatorScreenProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function SimulatorScreen({ frame, onTap, onSwipe, onKeyInput, maxHeight, }: SimulatorScreenProps): import("react/jsx-runtime").JSX.Element;
16
22
  export {};
17
23
  //# sourceMappingURL=SimulatorScreen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SimulatorScreen.d.ts","sourceRoot":"","sources":["../../src/components/SimulatorScreen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IACtE;;uEAEmE;IACnE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAgFD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,KAAK,EACL,OAAO,EACP,SAAkB,GACnB,EAAE,oBAAoB,2CA2GtB"}
1
+ {"version":3,"file":"SimulatorScreen.d.ts","sourceRoot":"","sources":["../../src/components/SimulatorScreen.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAA0C,MAAM,aAAa,CAAC;AAE1F,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IACtE,oFAAoF;IACpF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IAC1F;;uEAEmE;IACnE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAoJD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,KAAK,EACL,OAAO,EACP,UAAU,EACV,SAAkB,GACnB,EAAE,oBAAoB,2CA+HtB"}
@@ -1,6 +1,66 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useRef, useState } from "react";
2
+ // biome-ignore-all lint/a11y/noNoninteractiveTabindex: the simulator screen is an application-role surface that forwards keyboard + pointer input and must be focusable.
3
+ import { useCallback, useRef, useState, } from "react";
3
4
  const SWIPE_THRESHOLD_PX = 5;
5
+ // Browser KeyboardEvent.code values baguette accepts (mirrors KeyCodeSchema in
6
+ // @alivelabs/expo-orchestrator-schemas). Built at module load to stay compact.
7
+ const SUPPORTED_KEY_CODES = (() => {
8
+ const set = new Set([
9
+ "Enter",
10
+ "Escape",
11
+ "Backspace",
12
+ "Tab",
13
+ "Space",
14
+ "ArrowUp",
15
+ "ArrowDown",
16
+ "ArrowLeft",
17
+ "ArrowRight",
18
+ "Minus",
19
+ "Equal",
20
+ "BracketLeft",
21
+ "BracketRight",
22
+ "Backslash",
23
+ "Semicolon",
24
+ "Quote",
25
+ "Backquote",
26
+ "Comma",
27
+ "Period",
28
+ "Slash",
29
+ ]);
30
+ for (let i = 0; i < 26; i += 1)
31
+ set.add(`Key${String.fromCharCode(65 + i)}`);
32
+ for (let i = 0; i < 10; i += 1)
33
+ set.add(`Digit${i}`);
34
+ return set;
35
+ })();
36
+ function isSupportedKeyCode(code) {
37
+ return SUPPORTED_KEY_CODES.has(code);
38
+ }
39
+ /**
40
+ * Translate a browser keydown into a simulator input. A printable character
41
+ * (no Ctrl/Cmd) is sent as `type` so layout/shift resolve correctly; everything
42
+ * else (Enter, Backspace, arrows, shortcuts like Cmd+A) goes as a `key` event
43
+ * with modifiers. Returns null for keys baguette can't take (e.g. F-keys).
44
+ */
45
+ function mapKeyboardEvent(e) {
46
+ const usesCommand = e.ctrlKey || e.metaKey;
47
+ if (e.key.length === 1 && !usesCommand) {
48
+ return { type: "type", text: e.key };
49
+ }
50
+ if (isSupportedKeyCode(e.code)) {
51
+ const modifiers = [];
52
+ if (e.shiftKey)
53
+ modifiers.push("shift");
54
+ if (e.ctrlKey)
55
+ modifiers.push("control");
56
+ if (e.altKey)
57
+ modifiers.push("option");
58
+ if (e.metaKey)
59
+ modifiers.push("command");
60
+ return { type: "key", code: e.code, modifiers: modifiers.length > 0 ? modifiers : undefined };
61
+ }
62
+ return null;
63
+ }
4
64
  // The outer titanium-style bezel that makes the screen read as a physical
5
65
  // device. The screen (wrapper) sits inside it with its own rounded corners.
6
66
  const deviceFrameStyle = {
@@ -11,6 +71,12 @@ const deviceFrameStyle = {
11
71
  boxShadow: "0 24px 60px -18px rgba(0,0,0,0.75), 0 4px 14px rgba(0,0,0,0.4)," +
12
72
  " inset 0 0 0 1px rgba(255,255,255,0.05), inset 0 1.5px 1px rgba(255,255,255,0.12)",
13
73
  lineHeight: 0,
74
+ transition: "box-shadow 0.18s ease",
75
+ };
76
+ // Extra ring + soft glow layered onto the bezel shadow while the screen is
77
+ // focused, so it's clear keystrokes are being forwarded to the simulator.
78
+ const focusedFrameStyle = {
79
+ boxShadow: `${deviceFrameStyle.boxShadow}, 0 0 0 2px rgba(59,130,246,0.7), 0 0 18px -4px rgba(59,130,246,0.5)`,
14
80
  };
15
81
  const wrapperStyle = {
16
82
  position: "relative",
@@ -22,6 +88,9 @@ const wrapperStyle = {
22
88
  userSelect: "none",
23
89
  lineHeight: 0,
24
90
  boxShadow: "inset 0 0 0 2px rgba(0,0,0,0.85)",
91
+ // The bezel renders the focus ring; suppress the default outline on the
92
+ // focusable screen itself.
93
+ outline: "none",
25
94
  };
26
95
  const basePlaceholderStyle = {
27
96
  // iPhone 17 logical resolution (402×874 pt). Using aspect-ratio keeps the
@@ -60,9 +129,10 @@ function toNatural(el, clientX, clientY) {
60
129
  y: Math.round((clientY - rect.top) * scaleY),
61
130
  };
62
131
  }
63
- export function SimulatorScreen({ frame, onTap, onSwipe, maxHeight = "80vh", }) {
132
+ export function SimulatorScreen({ frame, onTap, onSwipe, onKeyInput, maxHeight = "80vh", }) {
64
133
  const imgRef = useRef(null);
65
134
  const dragRef = useRef(null);
135
+ const [focused, setFocused] = useState(false);
66
136
  const imgStyle = { ...baseImgStyle, maxHeight };
67
137
  // Drive the placeholder by height so its width derives from the iPhone 17
68
138
  // aspect ratio (a block div would otherwise fill the column width and distort).
@@ -110,7 +180,19 @@ export function SimulatorScreen({ frame, onTap, onSwipe, maxHeight = "80vh", })
110
180
  });
111
181
  }
112
182
  }, [onTap, onSwipe]);
113
- return (_jsx("div", { style: deviceFrameStyle, children: _jsxs("div", { style: wrapperStyle, role: "application", "aria-label": "iOS simulator \u2014 click to tap, drag to swipe", onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, children: [frame ? (_jsx("img", { ref: imgRef, src: frame, alt: "iOS Simulator", style: imgStyle, draggable: false })) : (_jsxs("div", { style: placeholderStyle, children: [_jsx("span", { style: { fontWeight: 500 }, children: "Waiting for the simulator" }), _jsx("div", { style: { display: "flex", gap: "6px" }, children: [0, 1, 2].map((i) => (_jsx("span", { style: {
183
+ const handleKeyDown = useCallback((e) => {
184
+ if (!onKeyInput)
185
+ return;
186
+ const input = mapKeyboardEvent(e);
187
+ if (!input)
188
+ return;
189
+ // Keep the keystroke off the page (no scrolling on space/arrows, no focus
190
+ // loss on Tab) — it goes to the simulator instead.
191
+ e.preventDefault();
192
+ onKeyInput(input);
193
+ }, [onKeyInput]);
194
+ const frameStyle = focused ? { ...deviceFrameStyle, ...focusedFrameStyle } : deviceFrameStyle;
195
+ return (_jsx("div", { style: frameStyle, children: _jsxs("div", { style: wrapperStyle, role: "application", "aria-label": "iOS simulator \u2014 click to focus; tap, drag to swipe, type to send keys", tabIndex: 0, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onKeyDown: handleKeyDown, onFocus: () => setFocused(true), onBlur: () => setFocused(false), children: [frame ? (_jsx("img", { ref: imgRef, src: frame, alt: "iOS Simulator", style: imgStyle, draggable: false })) : (_jsxs("div", { style: placeholderStyle, children: [_jsx("span", { style: { fontWeight: 500 }, children: "Waiting for the simulator" }), _jsx("div", { style: { display: "flex", gap: "6px" }, children: [0, 1, 2].map((i) => (_jsx("span", { style: {
114
196
  width: 6,
115
197
  height: 6,
116
198
  borderRadius: "50%",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alivelabs/expo-orchestrator-react-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "React client for Expo CI Orchestrator — streaming logs, live simulator video, and interactive controls.",
5
5
  "type": "module",
6
6
  "license": "MIT",