@limrun/ui 0.17.0-rc.1 → 0.17.0-rc.3

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 (80) hide show
  1. package/README.md +18 -3
  2. package/dist/{GLTFLoader-DW6wpvyR.mjs → GLTFLoader-B4nnixLv.mjs} +1 -1
  3. package/dist/{GLTFLoader-BKnsBFsj.js → GLTFLoader-GATLvS0U.js} +1 -1
  4. package/dist/components/duo-fold-motion.d.ts +1 -1
  5. package/dist/components/duo-view.d.ts +5 -1
  6. package/dist/{duo-frame-DjfA4gG-.js → duo-frame-CfBILL32.js} +2 -2
  7. package/dist/{duo-frame-BzyYljix.mjs → duo-frame-F3hwc09B.mjs} +406 -406
  8. package/dist/duo-view-BsdKpp8d.mjs +662 -0
  9. package/dist/duo-view-Cy8kBdg9.js +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.css +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/lite.cjs +1 -1
  14. package/dist/lite.js +1 -1
  15. package/dist/{remote-control-CQjSEVDF.mjs → remote-control-BAmNHDdv.mjs} +109 -108
  16. package/dist/{remote-control-DVRXfCR2.js → remote-control-F3h8U5pg.js} +1 -1
  17. package/package.json +5 -1
  18. package/dist/demo.d.ts +0 -1
  19. package/dist/duo-view-COZg8ieG.js +0 -1
  20. package/dist/duo-view-o1yxIzxu.mjs +0 -654
  21. package/index.html +0 -199
  22. package/src/assets/Apple_logo_white.svg +0 -4
  23. package/src/assets/android_boot.gif +0 -0
  24. package/src/assets/android_boot.webp +0 -0
  25. package/src/assets/iphone16pro_black.webp +0 -0
  26. package/src/assets/iphone16pro_black_bg.webp +0 -0
  27. package/src/assets/iphone16pro_black_landscape.webp +0 -0
  28. package/src/assets/iphone16pro_black_landscape_bg.webp +0 -0
  29. package/src/assets/pixel9_black.webp +0 -0
  30. package/src/assets/pixel9_black_landscape.webp +0 -0
  31. package/src/assets/pixel_tablet_landscape.webp +0 -0
  32. package/src/assets/pixel_tablet_portrait.webp +0 -0
  33. package/src/components/device-install/index.ts +0 -1
  34. package/src/components/duo-button-hinge.test.tsx +0 -138
  35. package/src/components/duo-button-hinge.ts +0 -75
  36. package/src/components/duo-controls.tsx +0 -142
  37. package/src/components/duo-flat-frame.tsx +0 -145
  38. package/src/components/duo-flat-geometry.test.ts +0 -63
  39. package/src/components/duo-flat-geometry.ts +0 -97
  40. package/src/components/duo-fold-motion.test.tsx +0 -245
  41. package/src/components/duo-fold-motion.tsx +0 -192
  42. package/src/components/duo-fold-renderer.test.ts +0 -29
  43. package/src/components/duo-fold-renderer.ts +0 -397
  44. package/src/components/duo-fold-timing.test.ts +0 -44
  45. package/src/components/duo-fold-timing.ts +0 -9
  46. package/src/components/duo-frame.tsx +0 -579
  47. package/src/components/duo-hardware.test.ts +0 -61
  48. package/src/components/duo-hardware.ts +0 -61
  49. package/src/components/duo-model.test.ts +0 -243
  50. package/src/components/duo-model.ts +0 -607
  51. package/src/components/duo-screen-handoff.ts +0 -52
  52. package/src/components/duo-view.test.tsx +0 -104
  53. package/src/components/duo-view.tsx +0 -315
  54. package/src/components/inspect-overlay.css +0 -223
  55. package/src/components/inspect-overlay.tsx +0 -437
  56. package/src/components/remote-control-framed.tsx +0 -14
  57. package/src/components/remote-control.css +0 -441
  58. package/src/components/remote-control.test.ts +0 -34
  59. package/src/components/remote-control.tsx +0 -3989
  60. package/src/core/ax-fetcher.test.ts +0 -418
  61. package/src/core/ax-fetcher.ts +0 -387
  62. package/src/core/ax-tree.test.ts +0 -491
  63. package/src/core/ax-tree.ts +0 -416
  64. package/src/core/camera.test.ts +0 -155
  65. package/src/core/camera.ts +0 -133
  66. package/src/core/constants.ts +0 -344
  67. package/src/core/duo.test.ts +0 -142
  68. package/src/core/duo.ts +0 -156
  69. package/src/core/webrtc-messages.ts +0 -148
  70. package/src/demo.tsx +0 -283
  71. package/src/frame-assets.ts +0 -22
  72. package/src/hooks/index.ts +0 -1
  73. package/src/index.ts +0 -29
  74. package/src/lite.test.ts +0 -30
  75. package/src/lite.ts +0 -30
  76. package/src/vite-env.d.ts +0 -1
  77. package/tsconfig.json +0 -26
  78. package/tsconfig.node.json +0 -26
  79. package/vite.config.ts +0 -35
  80. package/vitest.config.ts +0 -24
@@ -1,148 +0,0 @@
1
- import { CONTROL_MSG_TYPE } from './constants';
2
-
3
- export function createTouchControlMessage(
4
- action: number,
5
- pointerId: number,
6
- videoWidth: number,
7
- videoHeight: number,
8
- x: number,
9
- y: number,
10
- pressure = 1.0,
11
- actionButton = 0,
12
- buttons = 0,
13
- ): ArrayBuffer {
14
- const buffer = new ArrayBuffer(32);
15
- const view = new DataView(buffer);
16
- let offset = 0;
17
-
18
- view.setUint8(offset, CONTROL_MSG_TYPE.INJECT_TOUCH_EVENT);
19
- offset += 1;
20
-
21
- view.setUint8(offset, action);
22
- offset += 1;
23
-
24
- view.setBigInt64(offset, BigInt(pointerId), true);
25
- offset += 8;
26
-
27
- view.setInt32(offset, Math.round(x), true);
28
- offset += 4;
29
- view.setInt32(offset, Math.round(y), true);
30
- offset += 4;
31
- view.setUint16(offset, videoWidth, true);
32
- offset += 2;
33
- view.setUint16(offset, videoHeight, true);
34
- offset += 2;
35
-
36
- view.setInt16(offset, Math.round(pressure * 0xffff), true);
37
- offset += 2;
38
-
39
- view.setInt32(offset, actionButton, true);
40
- offset += 4;
41
-
42
- view.setInt32(offset, buttons, true);
43
- return buffer;
44
- }
45
-
46
- // iOS-specific two-finger touch control message (fixed size = 24 bytes)
47
- // Layout (little-endian):
48
- // 1 byte: type
49
- // 1 byte: action
50
- // 4 bytes: x0 (Int32)
51
- // 4 bytes: y0 (Int32)
52
- // 4 bytes: x1 (Int32)
53
- // 4 bytes: y1 (Int32)
54
- // 2 bytes: videoWidth (UInt16)
55
- // 2 bytes: videoHeight (UInt16)
56
- // 2 bytes: reserved (UInt16, currently 0)
57
- export function createTwoFingerTouchControlMessage(
58
- action: number,
59
- videoWidth: number,
60
- videoHeight: number,
61
- x0: number,
62
- y0: number,
63
- x1: number,
64
- y1: number,
65
- ): ArrayBuffer {
66
- const buffer = new ArrayBuffer(24);
67
- const view = new DataView(buffer);
68
- let offset = 0;
69
-
70
- view.setUint8(offset, CONTROL_MSG_TYPE.INJECT_TWO_FINGER_TOUCH_EVENT);
71
- offset += 1;
72
-
73
- view.setUint8(offset, action);
74
- offset += 1;
75
-
76
- view.setInt32(offset, Math.round(x0), true);
77
- offset += 4;
78
- view.setInt32(offset, Math.round(y0), true);
79
- offset += 4;
80
- view.setInt32(offset, Math.round(x1), true);
81
- offset += 4;
82
- view.setInt32(offset, Math.round(y1), true);
83
- offset += 4;
84
-
85
- view.setUint16(offset, videoWidth, true);
86
- offset += 2;
87
- view.setUint16(offset, videoHeight, true);
88
- offset += 2;
89
-
90
- view.setUint16(offset, 0, true);
91
- return buffer;
92
- }
93
-
94
- export function createSetClipboardMessage(text: string, paste = true): ArrayBuffer {
95
- const encoder = new TextEncoder();
96
- const textBytes = encoder.encode(text);
97
-
98
- // 1 byte for type + 8 bytes for sequence + 1 byte for paste flag + 4 bytes for length + text bytes
99
- const buffer = new ArrayBuffer(14 + textBytes.length);
100
- const view = new DataView(buffer);
101
- let offset = 0;
102
-
103
- view.setUint8(offset, CONTROL_MSG_TYPE.SET_CLIPBOARD);
104
- offset += 1;
105
-
106
- // Use 0 as sequence since we don't need an acknowledgement
107
- view.setBigInt64(offset, BigInt(0), false);
108
- offset += 8;
109
-
110
- // Set paste flag
111
- view.setUint8(offset, paste ? 1 : 0);
112
- offset += 1;
113
-
114
- // Text length
115
- view.setUint32(offset, textBytes.length, false);
116
- offset += 4;
117
-
118
- // Text data
119
- new Uint8Array(buffer, offset).set(textBytes);
120
-
121
- return buffer;
122
- }
123
-
124
- export function createInjectKeycodeMessage(
125
- action: number,
126
- keycode: number,
127
- repeat = 0,
128
- metaState = 0,
129
- ): ArrayBuffer {
130
- const buffer = new ArrayBuffer(14);
131
- const view = new DataView(buffer);
132
- let offset = 0;
133
-
134
- view.setUint8(offset, CONTROL_MSG_TYPE.INJECT_KEYCODE);
135
- offset += 1;
136
-
137
- view.setUint8(offset, action);
138
- offset += 1;
139
-
140
- view.setInt32(offset, keycode, true);
141
- offset += 4;
142
-
143
- view.setInt32(offset, repeat, true);
144
- offset += 4;
145
-
146
- view.setInt32(offset, metaState, true);
147
- return buffer;
148
- }
package/src/demo.tsx DELETED
@@ -1,283 +0,0 @@
1
- import { useState, useRef } from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import { RemoteControl } from './components/remote-control-framed';
4
- import type { RemoteControlHandle } from './components/remote-control';
5
- import { AxSnapshot } from './core/ax-tree';
6
-
7
- type InspectChoice = 'off' | 'hover-only' | 'select';
8
-
9
- // Pre-fill from query string so the developer testing the demo can deeplink
10
- // `?url=...&token=...&inspect=select&autoconnect=1`. Nothing is persisted.
11
- const initialParams = new URLSearchParams(window.location.search);
12
- window.debugRemoteControl = initialParams.get('debug') === '1';
13
- const initialUrl = initialParams.get('url') || 'ws://localhost:8833/signaling';
14
- const initialToken = initialParams.get('token') || 'token';
15
- const initialPlatformParam = initialParams.get('platform');
16
- const initialPlatform: 'ios' | 'android' = initialPlatformParam === 'android' ? 'android' : 'ios';
17
- const initialInspect = (initialParams.get('inspect') as InspectChoice | null) ?? 'off';
18
- const initialAutoconnect =
19
- initialParams.get('autoconnect') === '1' && initialParams.has('url') && initialParams.has('token');
20
-
21
- function Demo() {
22
- const [url, setUrl] = useState(initialUrl);
23
- const [token, setToken] = useState(initialToken);
24
- const [platform, setPlatform] = useState<'ios' | 'android'>(initialPlatform);
25
- const [isConnected, setIsConnected] = useState(initialAutoconnect);
26
- const [key, setKey] = useState(0);
27
- const [showDebugInfo, setShowDebugInfo] = useState(false);
28
- const [inspectChoice, setInspectChoice] = useState<InspectChoice>(initialInspect);
29
- const [latestSnapshot, setLatestSnapshot] = useState<AxSnapshot | null>(null);
30
- const [latestSelection, setLatestSelection] = useState<string | null>(null);
31
-
32
- const remoteControlRef = useRef<RemoteControlHandle>(null);
33
-
34
- const inspectModeProp: boolean | 'hover-only' | undefined =
35
- inspectChoice === 'off' ? undefined
36
- : inspectChoice === 'hover-only' ? 'hover-only'
37
- : true;
38
-
39
- const handleConnect = () => {
40
- if (url) {
41
- setIsConnected(true);
42
- // Force remount by changing key
43
- setKey((prev) => prev + 1);
44
- }
45
- };
46
-
47
- const handleDisconnect = () => {
48
- setIsConnected(false);
49
- setKey((prev) => prev + 1);
50
- };
51
-
52
- const handleScreenshot = async () => {
53
- if (remoteControlRef.current) {
54
- try {
55
- const screenshot = await remoteControlRef.current.screenshot();
56
- // Open screenshot in new window
57
- const win = window.open();
58
- if (win) {
59
- win.document.write(`<img src="${screenshot.dataUri}" style="max-width: 100%;" />`);
60
- }
61
- } catch (error) {
62
- console.error('Screenshot failed:', error);
63
- alert('Screenshot failed: ' + (error as Error).message);
64
- }
65
- }
66
- };
67
-
68
- if (initialParams.get('compact') === '1') {
69
- return (
70
- <div style={{ position: 'fixed', inset: 0, background: '#edf0f4' }}>
71
- <RemoteControl
72
- url={url}
73
- token={token}
74
- deviceModel={initialParams.get('model') === 'iphone-duo' ? 'iphone-duo' : undefined}
75
- />
76
- </div>
77
- );
78
- }
79
-
80
- return (
81
- <>
82
- <div className="header">
83
- <h1>📱 RemoteControl Component Demo</h1>
84
- <p>Test the iOS device frame and remote control features</p>
85
- </div>
86
-
87
- <div className="demo-container">
88
- <div className="info-box">
89
- <h4>ℹ️ How to Use:</h4>
90
- <p>
91
- Enter your WebSocket URL and authentication token below, select iOS or Android platform, then
92
- click Connect to see the remote control in action. The iOS platform will display a realistic
93
- iPhone frame around the stream.
94
- </p>
95
- <p style={{ marginTop: '10px', fontWeight: 600 }}>
96
- ✨ iOS Feature: Touches can start from the bottom bezel area (below the screen, near the home
97
- indicator) to enable authentic iOS swipe-up gestures for going home or switching apps!
98
- </p>
99
- </div>
100
-
101
- <div className="controls">
102
- <div className="control-group">
103
- <label htmlFor="url">WebSocket URL</label>
104
- <input
105
- id="url"
106
- type="text"
107
- value={url}
108
- onChange={(e) => setUrl(e.target.value)}
109
- placeholder="wss://your-instance.limrun.com/control"
110
- disabled={isConnected}
111
- />
112
- </div>
113
-
114
- <div className="control-group">
115
- <label htmlFor="token">Authentication Token</label>
116
- <input
117
- id="token"
118
- type="password"
119
- value={token}
120
- onChange={(e) => setToken(e.target.value)}
121
- placeholder="Enter your token"
122
- disabled={isConnected}
123
- />
124
- </div>
125
-
126
- <div className="control-group">
127
- <label htmlFor="platform">Platform</label>
128
- <select
129
- id="platform"
130
- value={platform}
131
- onChange={(e) => setPlatform(e.target.value as 'ios' | 'android')}
132
- disabled={isConnected}
133
- >
134
- <option value="ios">iOS (with frame)</option>
135
- <option value="android">Android (no frame)</option>
136
- </select>
137
- </div>
138
-
139
- <div className="control-group">
140
- <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
141
- <input
142
- type="checkbox"
143
- checked={showDebugInfo}
144
- onChange={(e) => setShowDebugInfo(e.target.checked)}
145
- style={{ cursor: 'pointer' }}
146
- />
147
- Show iOS Extended Touch Area Info
148
- </label>
149
- </div>
150
-
151
- <div className="control-group">
152
- <label htmlFor="inspect-mode">Inspect Mode</label>
153
- <select
154
- id="inspect-mode"
155
- value={inspectChoice}
156
- onChange={(e) => setInspectChoice(e.target.value as InspectChoice)}
157
- >
158
- <option value="off">Off</option>
159
- <option value="hover-only">Hover-only (input still works)</option>
160
- <option value="select">Select (click to pin, ESC to clear)</option>
161
- </select>
162
- </div>
163
-
164
- <div className="button-group">
165
- {!isConnected ?
166
- <button className="primary" onClick={handleConnect} disabled={!url}>
167
- Connect
168
- </button>
169
- : <>
170
- <button className="secondary" onClick={handleDisconnect}>
171
- Disconnect
172
- </button>
173
- <button className="primary" onClick={handleScreenshot}>
174
- Take Screenshot
175
- </button>
176
- </>
177
- }
178
- </div>
179
- </div>
180
-
181
- {showDebugInfo && platform === 'ios' && (
182
- <div
183
- style={{
184
- background: '#e0f2fe',
185
- border: '2px solid #0284c7',
186
- borderRadius: '8px',
187
- padding: '15px',
188
- marginBottom: '20px',
189
- }}
190
- >
191
- <h4 style={{ color: '#0c4a6e', marginBottom: '8px', fontSize: '0.95rem' }}>
192
- 🔧 iOS Extended Touch Area
193
- </h4>
194
- <p style={{ color: '#075985', fontSize: '0.9rem', lineHeight: '1.5', margin: 0 }}>
195
- The iOS frame includes a <strong>60-pixel extended touch area</strong> below the visible screen.
196
- This area (where the home indicator is located) can receive touch events and sends coordinates
197
- beyond the screen bounds (y &gt; screenHeight), allowing iOS to properly detect gestures that
198
- start from outside the screen - just like on a real iPhone. Try starting a swipe gesture from
199
- the home indicator area!
200
- </p>
201
- </div>
202
- )}
203
-
204
- {isConnected ?
205
- <>
206
- <div className="device-preview">
207
- <div className="preview-item">
208
- <h3>{platform === 'ios' ? '📱 iOS with Frame' : '🤖 Android (No Frame)'}</h3>
209
- <div className="device-wrapper">
210
- <RemoteControl
211
- deviceModel={initialParams.get('model') === 'iphone-duo' ? 'iphone-duo' : undefined}
212
- key={key}
213
- ref={remoteControlRef}
214
- url={url}
215
- token={token}
216
- inspectMode={inspectModeProp}
217
- onAxSnapshotChange={setLatestSnapshot}
218
- onInspectSelectionChange={(sel) =>
219
- setLatestSelection(
220
- sel ?
221
- `${sel.element.role || sel.element.type} · ${sel.element.label || '(no label)'}`
222
- : null,
223
- )
224
- }
225
- />
226
- </div>
227
- </div>
228
- </div>
229
-
230
- {inspectChoice !== 'off' && (
231
- <div
232
- style={{
233
- marginTop: '20px',
234
- background: '#0f172a',
235
- color: '#e2e8f0',
236
- border: '1px solid #1e293b',
237
- borderRadius: '8px',
238
- padding: '12px 14px',
239
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
240
- fontSize: '12px',
241
- }}
242
- >
243
- <div
244
- style={{
245
- fontWeight: 600,
246
- marginBottom: '6px',
247
- display: 'flex',
248
- gap: '12px',
249
- flexWrap: 'wrap',
250
- }}
251
- >
252
- <span>Inspect debug (demo-only)</span>
253
- <span style={{ color: '#94a3b8', fontWeight: 400 }}>
254
- elements: {latestSnapshot?.elements.length ?? 0} · screen:{' '}
255
- {latestSnapshot ? `${latestSnapshot.screen.width}×${latestSnapshot.screen.height}` : '—'}{' '}
256
- · platform: {latestSnapshot?.platform ?? '—'} · selection: {latestSelection ?? '(none)'}
257
- </span>
258
- </div>
259
- </div>
260
- )}
261
- </>
262
- : <div
263
- style={{
264
- textAlign: 'center',
265
- padding: '60px 20px',
266
- color: '#9ca3af',
267
- fontSize: '1.1rem',
268
- }}
269
- >
270
- Enter your connection details above and click Connect to start
271
- </div>
272
- }
273
- </div>
274
- </>
275
- );
276
- }
277
-
278
- // Mount the demo app
279
- const container = document.getElementById('root');
280
- if (container) {
281
- const root = createRoot(container);
282
- root.render(<Demo />);
283
- }
@@ -1,22 +0,0 @@
1
- // Limrun's bundled device frames and boot logos. Imported only by the default
2
- // entry so `@limrun/ui/lite` ships without ~600 KB of inlined artwork.
3
- import type { RemoteControlAssets } from './components/remote-control';
4
- import iphoneFrame from './assets/iphone16pro_black_bg.webp';
5
- import iphoneFrameLandscape from './assets/iphone16pro_black_landscape_bg.webp';
6
- import pixelFrame from './assets/pixel9_black.webp';
7
- import pixelFrameLandscape from './assets/pixel9_black_landscape.webp';
8
- import pixelTabletFrame from './assets/pixel_tablet_portrait.webp';
9
- import pixelTabletFrameLandscape from './assets/pixel_tablet_landscape.webp';
10
- import appleLogo from './assets/Apple_logo_white.svg';
11
- import androidBootLogo from './assets/android_boot.webp';
12
-
13
- export const defaultRemoteControlAssets: RemoteControlAssets = {
14
- ios: { frame: iphoneFrame, frameLandscape: iphoneFrameLandscape, loadingLogo: appleLogo },
15
- android: {
16
- frame: pixelFrame,
17
- frameLandscape: pixelFrameLandscape,
18
- tabletFrame: pixelTabletFrame,
19
- tabletFrameLandscape: pixelTabletFrameLandscape,
20
- loadingLogo: androidBootLogo,
21
- },
22
- };
@@ -1 +0,0 @@
1
- export {};
package/src/index.ts DELETED
@@ -1,29 +0,0 @@
1
- export { RemoteControl } from './components/remote-control-framed';
2
- export { defaultRemoteControlAssets } from './frame-assets';
3
- export type {
4
- RemoteControlProps,
5
- RemoteControlHandle,
6
- RemoteControlAssets,
7
- RemoteControlPlatformAssets,
8
- RemoteControlConnectionState,
9
- MicrophoneState,
10
- CameraStreamStats,
11
- } from './components/remote-control';
12
- export type { CameraCaptureInfo, CameraFacingMode, CameraRequest, CameraResult } from './core/camera';
13
-
14
- // Accessibility / inspect-mode types and helpers. Exported so customers can
15
- // build their own side panels, search UIs, or agent-driven inspectors on top
16
- // of the snapshots delivered via `onAxSnapshotChange`.
17
- export type { AxSnapshot, AxElement, AxRect, AxSelectors, AxPlatform } from './core/ax-tree';
18
- export type { AndroidElementTreeOptions, AxStatus } from './core/ax-fetcher';
19
- export {
20
- axElementAtPoint,
21
- axElementSelectorExpression,
22
- axElementSummary,
23
- axElementsEqual,
24
- axSnapshotsEqual,
25
- clampAxFrameForScreen,
26
- normalizeAndroidTree,
27
- normalizeIosTree,
28
- AX_UNAVAILABLE_ERROR,
29
- } from './core/ax-tree';
package/src/lite.test.ts DELETED
@@ -1,30 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
- import { join } from 'node:path';
4
-
5
- // Guards the reason the lite entry exists: an embed that imports it must not
6
- // pull the bundled device artwork. Runs against dist, so build first.
7
- const dist = join(__dirname, '..', 'dist');
8
- const describeDist = existsSync(join(dist, 'lite.js')) ? describe : describe.skip;
9
-
10
- describeDist('@limrun/ui/lite bundle', () => {
11
- const chunksOf = (entry: string): string[] => {
12
- const src = readFileSync(join(dist, entry), 'utf8');
13
- const chunks = [...src.matchAll(/from "\.\/([^"]+)"/g)].map((m) => m[1]);
14
- return [src, ...chunks.map((c) => readFileSync(join(dist, c), 'utf8'))];
15
- };
16
-
17
- test('lite entry and its chunks carry no inlined images', () => {
18
- for (const code of chunksOf('lite.js')) {
19
- expect(code).not.toMatch(/data:image\//);
20
- }
21
- });
22
-
23
- test('default entry still bundles the device frames (positive control)', () => {
24
- expect(chunksOf('index.js').some((code) => /data:image\/webp/.test(code))).toBe(true);
25
- });
26
-
27
- test('one stylesheet keeps its historical name', () => {
28
- expect(readdirSync(dist).filter((f) => f.endsWith('.css'))).toEqual(['index.css']);
29
- });
30
- });
package/src/lite.ts DELETED
@@ -1,30 +0,0 @@
1
- // Frameless entry: identical API, but no bundled device artwork. Pass `assets`
2
- // to add frames or boot logos.
3
- export { RemoteControl } from './components/remote-control';
4
- export type {
5
- RemoteControlProps,
6
- RemoteControlHandle,
7
- RemoteControlAssets,
8
- RemoteControlPlatformAssets,
9
- RemoteControlConnectionState,
10
- MicrophoneState,
11
- CameraStreamStats,
12
- } from './components/remote-control';
13
- export type { CameraCaptureInfo, CameraFacingMode, CameraRequest, CameraResult } from './core/camera';
14
-
15
- // Accessibility / inspect-mode types and helpers. Exported so customers can
16
- // build their own side panels, search UIs, or agent-driven inspectors on top
17
- // of the snapshots delivered via `onAxSnapshotChange`.
18
- export type { AxSnapshot, AxElement, AxRect, AxSelectors, AxPlatform } from './core/ax-tree';
19
- export type { AndroidElementTreeOptions, AxStatus } from './core/ax-fetcher';
20
- export {
21
- axElementAtPoint,
22
- axElementSelectorExpression,
23
- axElementSummary,
24
- axElementsEqual,
25
- axSnapshotsEqual,
26
- clampAxFrameForScreen,
27
- normalizeAndroidTree,
28
- normalizeIosTree,
29
- AX_UNAVAILABLE_ERROR,
30
- } from './core/ax-tree';
package/src/vite-env.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
- "moduleResolution": "bundler",
9
- "allowImportingTsExtensions": false,
10
- "resolveJsonModule": true,
11
- "isolatedModules": true,
12
- "noEmit": false,
13
- "emitDeclarationOnly": true,
14
- "declaration": true,
15
- "declarationDir": "dist",
16
- "jsx": "react-jsx",
17
- "strict": true,
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "outDir": "dist"
22
- },
23
- "include": ["src"],
24
- "exclude": ["**/*.test.ts", "**/*.test.tsx", "node_modules", "dist"],
25
- "references": [{ "path": "./tsconfig.node.json" }]
26
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "composite": true,
4
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5
- "target": "ES2022",
6
- "lib": ["ES2023"],
7
- "module": "ESNext",
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "isolatedModules": true,
14
- "moduleDetection": "force",
15
- "noEmit": false,
16
- "emitDeclarationOnly": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
24
- },
25
- "include": ["vite.config.ts"]
26
- }
package/vite.config.ts DELETED
@@ -1,35 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react-swc';
3
- import { resolve } from 'path';
4
- import dts from 'vite-plugin-dts';
5
- import { libInjectCss } from 'vite-plugin-lib-inject-css';
6
-
7
- // https://vite.dev/config/
8
- export default defineConfig({
9
- plugins: [react(), libInjectCss(), dts({ include: ['src'] })],
10
- build: {
11
- lib: {
12
- entry: {
13
- index: resolve(__dirname, 'src/index.ts'),
14
- lite: resolve(__dirname, 'src/lite.ts'),
15
- },
16
- name: 'LimrunUI',
17
- formats: ['es', 'cjs'],
18
- fileName: (format, entryName) => `${entryName}.${format === 'es' ? 'js' : 'cjs'}`,
19
- },
20
- rollupOptions: {
21
- external: ['react', 'react-dom', 'react/jsx-runtime'],
22
- output: {
23
- // Both entries share one stylesheet; keep its historical name so
24
- // consumers that reference dist/index.css directly keep working.
25
- assetFileNames: (info) =>
26
- info.names?.some((n) => n.endsWith('.css')) ? 'index.css' : '[name][extname]',
27
- globals: {
28
- react: 'React',
29
- 'react-dom': 'ReactDOM',
30
- 'react/jsx-runtime': 'jsxRuntime',
31
- },
32
- },
33
- },
34
- },
35
- });
package/vitest.config.ts DELETED
@@ -1,24 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- // Vitest config for @limrun/ui unit tests.
4
- //
5
- // We use jsdom as the default environment because the runtime code uses
6
- // browser globals (window.setTimeout, window.requestAnimationFrame, etc.).
7
- // Pure modules can opt back into the node env per-file via:
8
- //
9
- // // @vitest-environment node
10
- //
11
- // at the top of the test file.
12
- //
13
- // The actual <RemoteControl> component is intentionally NOT under unit
14
- // test here — its WebRTC plumbing is integration-tested via the demo +
15
- // staging instance. These tests cover the smaller, pure-logic pieces:
16
- // `core/ax-tree.ts`, `core/ax-fetcher.ts` and the URL helper exported from
17
- // `components/remote-control.tsx`.
18
- export default defineConfig({
19
- test: {
20
- environment: 'jsdom',
21
- include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
22
- globals: false,
23
- },
24
- });