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

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 (83) hide show
  1. package/README.md +18 -3
  2. package/dist/{GLTFLoader-DW6wpvyR.mjs → GLTFLoader-Cami_vza.mjs} +1 -1
  3. package/dist/{GLTFLoader-BKnsBFsj.js → GLTFLoader-DeCLxW_q.js} +1 -1
  4. package/dist/components/duo-fold-motion.d.ts +1 -1
  5. package/dist/components/duo-loading.d.ts +8 -0
  6. package/dist/components/duo-view.d.ts +6 -1
  7. package/dist/{duo-frame-BzyYljix.mjs → duo-frame-Cmw4p2uN.mjs} +414 -414
  8. package/dist/{duo-frame-DjfA4gG-.js → duo-frame-iMtrvx_a.js} +18 -18
  9. package/dist/duo-view-BAh-w2Kz.js +1 -0
  10. package/dist/duo-view-D9Ae00rj.mjs +512 -0
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.css +1 -1
  13. package/dist/index.js +2 -2
  14. package/dist/lite.cjs +1 -1
  15. package/dist/lite.js +1 -1
  16. package/dist/remote-control-B2jZpf3c.js +1 -0
  17. package/dist/remote-control-DiddxS8l.mjs +2883 -0
  18. package/package.json +5 -1
  19. package/dist/demo.d.ts +0 -1
  20. package/dist/duo-view-COZg8ieG.js +0 -1
  21. package/dist/duo-view-o1yxIzxu.mjs +0 -654
  22. package/dist/remote-control-CQjSEVDF.mjs +0 -2673
  23. package/dist/remote-control-DVRXfCR2.js +0 -1
  24. package/index.html +0 -199
  25. package/src/assets/Apple_logo_white.svg +0 -4
  26. package/src/assets/android_boot.gif +0 -0
  27. package/src/assets/android_boot.webp +0 -0
  28. package/src/assets/iphone16pro_black.webp +0 -0
  29. package/src/assets/iphone16pro_black_bg.webp +0 -0
  30. package/src/assets/iphone16pro_black_landscape.webp +0 -0
  31. package/src/assets/iphone16pro_black_landscape_bg.webp +0 -0
  32. package/src/assets/pixel9_black.webp +0 -0
  33. package/src/assets/pixel9_black_landscape.webp +0 -0
  34. package/src/assets/pixel_tablet_landscape.webp +0 -0
  35. package/src/assets/pixel_tablet_portrait.webp +0 -0
  36. package/src/components/device-install/index.ts +0 -1
  37. package/src/components/duo-button-hinge.test.tsx +0 -138
  38. package/src/components/duo-button-hinge.ts +0 -75
  39. package/src/components/duo-controls.tsx +0 -142
  40. package/src/components/duo-flat-frame.tsx +0 -145
  41. package/src/components/duo-flat-geometry.test.ts +0 -63
  42. package/src/components/duo-flat-geometry.ts +0 -97
  43. package/src/components/duo-fold-motion.test.tsx +0 -245
  44. package/src/components/duo-fold-motion.tsx +0 -192
  45. package/src/components/duo-fold-renderer.test.ts +0 -29
  46. package/src/components/duo-fold-renderer.ts +0 -397
  47. package/src/components/duo-fold-timing.test.ts +0 -44
  48. package/src/components/duo-fold-timing.ts +0 -9
  49. package/src/components/duo-frame.tsx +0 -579
  50. package/src/components/duo-hardware.test.ts +0 -61
  51. package/src/components/duo-hardware.ts +0 -61
  52. package/src/components/duo-model.test.ts +0 -243
  53. package/src/components/duo-model.ts +0 -607
  54. package/src/components/duo-screen-handoff.ts +0 -52
  55. package/src/components/duo-view.test.tsx +0 -104
  56. package/src/components/duo-view.tsx +0 -315
  57. package/src/components/inspect-overlay.css +0 -223
  58. package/src/components/inspect-overlay.tsx +0 -437
  59. package/src/components/remote-control-framed.tsx +0 -14
  60. package/src/components/remote-control.css +0 -441
  61. package/src/components/remote-control.test.ts +0 -34
  62. package/src/components/remote-control.tsx +0 -3989
  63. package/src/core/ax-fetcher.test.ts +0 -418
  64. package/src/core/ax-fetcher.ts +0 -387
  65. package/src/core/ax-tree.test.ts +0 -491
  66. package/src/core/ax-tree.ts +0 -416
  67. package/src/core/camera.test.ts +0 -155
  68. package/src/core/camera.ts +0 -133
  69. package/src/core/constants.ts +0 -344
  70. package/src/core/duo.test.ts +0 -142
  71. package/src/core/duo.ts +0 -156
  72. package/src/core/webrtc-messages.ts +0 -148
  73. package/src/demo.tsx +0 -283
  74. package/src/frame-assets.ts +0 -22
  75. package/src/hooks/index.ts +0 -1
  76. package/src/index.ts +0 -29
  77. package/src/lite.test.ts +0 -30
  78. package/src/lite.ts +0 -30
  79. package/src/vite-env.d.ts +0 -1
  80. package/tsconfig.json +0 -26
  81. package/tsconfig.node.json +0 -26
  82. package/vite.config.ts +0 -35
  83. 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
- });