@limrun/ui 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/demo.tsx ADDED
@@ -0,0 +1,185 @@
1
+ import { useState, useRef } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { RemoteControl, RemoteControlHandle } from './components/remote-control';
4
+
5
+ function Demo() {
6
+ const [url, setUrl] = useState('ws://localhost:8833/signaling');
7
+ const [token, setToken] = useState('token');
8
+ const [platform, setPlatform] = useState<'ios' | 'android'>('ios');
9
+ const [isConnected, setIsConnected] = useState(false);
10
+ const [key, setKey] = useState(0);
11
+ const [showDebugInfo, setShowDebugInfo] = useState(false);
12
+
13
+ const remoteControlRef = useRef<RemoteControlHandle>(null);
14
+
15
+ const handleConnect = () => {
16
+ if (url) {
17
+ setIsConnected(true);
18
+ // Force remount by changing key
19
+ setKey((prev) => prev + 1);
20
+ }
21
+ };
22
+
23
+ const handleDisconnect = () => {
24
+ setIsConnected(false);
25
+ setKey((prev) => prev + 1);
26
+ };
27
+
28
+ const handleScreenshot = async () => {
29
+ if (remoteControlRef.current) {
30
+ try {
31
+ const screenshot = await remoteControlRef.current.screenshot();
32
+ // Open screenshot in new window
33
+ const win = window.open();
34
+ if (win) {
35
+ win.document.write(`<img src="${screenshot.dataUri}" style="max-width: 100%;" />`);
36
+ }
37
+ } catch (error) {
38
+ console.error('Screenshot failed:', error);
39
+ alert('Screenshot failed: ' + (error as Error).message);
40
+ }
41
+ }
42
+ };
43
+
44
+ return (
45
+ <>
46
+ <div className="header">
47
+ <h1>📱 RemoteControl Component Demo</h1>
48
+ <p>Test the iOS device frame and remote control features</p>
49
+ </div>
50
+
51
+ <div className="demo-container">
52
+ <div className="info-box">
53
+ <h4>â„šī¸ How to Use:</h4>
54
+ <p>
55
+ Enter your WebSocket URL and authentication token below, select iOS or Android platform, then
56
+ click Connect to see the remote control in action. The iOS platform will display a realistic
57
+ iPhone frame around the stream.
58
+ </p>
59
+ <p style={{ marginTop: '10px', fontWeight: 600 }}>
60
+ ✨ iOS Feature: Touches can start from the bottom bezel area (below the screen, near the home
61
+ indicator) to enable authentic iOS swipe-up gestures for going home or switching apps!
62
+ </p>
63
+ </div>
64
+
65
+ <div className="controls">
66
+ <div className="control-group">
67
+ <label htmlFor="url">WebSocket URL</label>
68
+ <input
69
+ id="url"
70
+ type="text"
71
+ value={url}
72
+ onChange={(e) => setUrl(e.target.value)}
73
+ placeholder="wss://your-instance.limrun.com/control"
74
+ disabled={isConnected}
75
+ />
76
+ </div>
77
+
78
+ <div className="control-group">
79
+ <label htmlFor="token">Authentication Token</label>
80
+ <input
81
+ id="token"
82
+ type="password"
83
+ value={token}
84
+ onChange={(e) => setToken(e.target.value)}
85
+ placeholder="Enter your token"
86
+ disabled={isConnected}
87
+ />
88
+ </div>
89
+
90
+ <div className="control-group">
91
+ <label htmlFor="platform">Platform</label>
92
+ <select
93
+ id="platform"
94
+ value={platform}
95
+ onChange={(e) => setPlatform(e.target.value as 'ios' | 'android')}
96
+ disabled={isConnected}
97
+ >
98
+ <option value="ios">iOS (with frame)</option>
99
+ <option value="android">Android (no frame)</option>
100
+ </select>
101
+ </div>
102
+
103
+ <div className="control-group">
104
+ <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
105
+ <input
106
+ type="checkbox"
107
+ checked={showDebugInfo}
108
+ onChange={(e) => setShowDebugInfo(e.target.checked)}
109
+ style={{ cursor: 'pointer' }}
110
+ />
111
+ Show iOS Extended Touch Area Info
112
+ </label>
113
+ </div>
114
+
115
+ <div className="button-group">
116
+ {!isConnected ?
117
+ <button className="primary" onClick={handleConnect} disabled={!url}>
118
+ Connect
119
+ </button>
120
+ : <>
121
+ <button className="secondary" onClick={handleDisconnect}>
122
+ Disconnect
123
+ </button>
124
+ <button className="primary" onClick={handleScreenshot}>
125
+ Take Screenshot
126
+ </button>
127
+ </>
128
+ }
129
+ </div>
130
+ </div>
131
+
132
+ {showDebugInfo && platform === 'ios' && (
133
+ <div
134
+ style={{
135
+ background: '#e0f2fe',
136
+ border: '2px solid #0284c7',
137
+ borderRadius: '8px',
138
+ padding: '15px',
139
+ marginBottom: '20px',
140
+ }}
141
+ >
142
+ <h4 style={{ color: '#0c4a6e', marginBottom: '8px', fontSize: '0.95rem' }}>
143
+ 🔧 iOS Extended Touch Area
144
+ </h4>
145
+ <p style={{ color: '#075985', fontSize: '0.9rem', lineHeight: '1.5', margin: 0 }}>
146
+ The iOS frame includes a <strong>60-pixel extended touch area</strong> below the visible screen.
147
+ This area (where the home indicator is located) can receive touch events and sends coordinates
148
+ beyond the screen bounds (y &gt; screenHeight), allowing iOS to properly detect gestures that
149
+ start from outside the screen - just like on a real iPhone. Try starting a swipe gesture from
150
+ the home indicator area!
151
+ </p>
152
+ </div>
153
+ )}
154
+
155
+ {isConnected ?
156
+ <div className="device-preview">
157
+ <div className="preview-item">
158
+ <h3>{platform === 'ios' ? '📱 iOS with Frame' : '🤖 Android (No Frame)'}</h3>
159
+ <div className="device-wrapper">
160
+ <RemoteControl key={key} ref={remoteControlRef} url={url} token={token} />
161
+ </div>
162
+ </div>
163
+ </div>
164
+ : <div
165
+ style={{
166
+ textAlign: 'center',
167
+ padding: '60px 20px',
168
+ color: '#9ca3af',
169
+ fontSize: '1.1rem',
170
+ }}
171
+ >
172
+ Enter your connection details above and click Connect to start
173
+ </div>
174
+ }
175
+ </div>
176
+ </>
177
+ );
178
+ }
179
+
180
+ // Mount the demo app
181
+ const container = document.getElementById('root');
182
+ if (container) {
183
+ const root = createRoot(container);
184
+ root.render(<Demo />);
185
+ }
package/tsconfig.json CHANGED
@@ -1,26 +1,26 @@
1
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
- }
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 +1,26 @@
1
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
- }
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
+ }