react-native-web-serial-api 0.0.3 → 0.2.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 (177) hide show
  1. package/README.md +198 -104
  2. package/TESTING.md +542 -0
  3. package/android/build.gradle +16 -2
  4. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +74 -11
  5. package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +61 -59
  6. package/bin/expose-serial.js +205 -0
  7. package/lib/commonjs/UsbSerial.js +58 -26
  8. package/lib/commonjs/UsbSerial.js.map +1 -1
  9. package/lib/commonjs/WebSerial.js +273 -77
  10. package/lib/commonjs/WebSerial.js.map +1 -1
  11. package/lib/commonjs/index.js +15 -3
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/lib/dom-exception.js +176 -0
  14. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  15. package/lib/commonjs/lib/event-target.js +140 -0
  16. package/lib/commonjs/lib/event-target.js.map +1 -0
  17. package/lib/commonjs/lib/promise.js +23 -0
  18. package/lib/commonjs/lib/promise.js.map +1 -0
  19. package/lib/commonjs/lib/web-streams.js +42 -0
  20. package/lib/commonjs/lib/web-streams.js.map +1 -0
  21. package/lib/commonjs/testing/device-fixture.js +70 -0
  22. package/lib/commonjs/testing/device-fixture.js.map +1 -0
  23. package/lib/commonjs/testing/expose.js +91 -0
  24. package/lib/commonjs/testing/expose.js.map +1 -0
  25. package/lib/commonjs/testing/harness.js +98 -0
  26. package/lib/commonjs/testing/harness.js.map +1 -0
  27. package/lib/commonjs/testing/in-memory-serial-transport.js +653 -0
  28. package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
  29. package/lib/commonjs/testing/index.js +153 -0
  30. package/lib/commonjs/testing/index.js.map +1 -0
  31. package/lib/commonjs/testing/install-in-memory-serial-transport.js +54 -0
  32. package/lib/commonjs/testing/install-in-memory-serial-transport.js.map +1 -0
  33. package/lib/commonjs/testing/serial-client.js +277 -0
  34. package/lib/commonjs/testing/serial-client.js.map +1 -0
  35. package/lib/commonjs/testing/simulated-device.js +164 -0
  36. package/lib/commonjs/testing/simulated-device.js.map +1 -0
  37. package/lib/commonjs/testing/test-suite.js +142 -0
  38. package/lib/commonjs/testing/test-suite.js.map +1 -0
  39. package/lib/commonjs/transport.js +61 -0
  40. package/lib/commonjs/transport.js.map +1 -0
  41. package/lib/commonjs/websocket/WebSocketSerialTransport.js +659 -0
  42. package/lib/commonjs/websocket/WebSocketSerialTransport.js.map +1 -0
  43. package/lib/commonjs/websocket/bridge.js +234 -0
  44. package/lib/commonjs/websocket/bridge.js.map +1 -0
  45. package/lib/commonjs/websocket/index.js +33 -0
  46. package/lib/commonjs/websocket/index.js.map +1 -0
  47. package/lib/commonjs/websocket/protocol.js +55 -0
  48. package/lib/commonjs/websocket/protocol.js.map +1 -0
  49. package/lib/commonjs/websocket/serial-device-bridge.js +130 -0
  50. package/lib/commonjs/websocket/serial-device-bridge.js.map +1 -0
  51. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  52. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  53. package/lib/typescript/src/WebSerial.d.ts +16 -7
  54. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  55. package/lib/typescript/src/index.d.ts +3 -1
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  58. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  59. package/lib/typescript/src/lib/event-target.d.ts +55 -0
  60. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  61. package/lib/typescript/src/lib/promise.d.ts +11 -0
  62. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  63. package/lib/typescript/src/lib/web-streams.d.ts +9 -0
  64. package/lib/typescript/src/lib/web-streams.d.ts.map +1 -0
  65. package/lib/typescript/src/testing/device-fixture.d.ts +70 -0
  66. package/lib/typescript/src/testing/device-fixture.d.ts.map +1 -0
  67. package/lib/typescript/src/testing/expose.d.ts +71 -0
  68. package/lib/typescript/src/testing/expose.d.ts.map +1 -0
  69. package/lib/typescript/src/testing/harness.d.ts +34 -0
  70. package/lib/typescript/src/testing/harness.d.ts.map +1 -0
  71. package/lib/typescript/src/testing/in-memory-serial-transport.d.ts +216 -0
  72. package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
  73. package/lib/typescript/src/testing/index.d.ts +33 -0
  74. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  75. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts +25 -0
  76. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts.map +1 -0
  77. package/lib/typescript/src/testing/serial-client.d.ts +62 -0
  78. package/lib/typescript/src/testing/serial-client.d.ts.map +1 -0
  79. package/lib/typescript/src/testing/simulated-device.d.ts +127 -0
  80. package/lib/typescript/src/testing/simulated-device.d.ts.map +1 -0
  81. package/lib/typescript/src/testing/test-suite.d.ts +75 -0
  82. package/lib/typescript/src/testing/test-suite.d.ts.map +1 -0
  83. package/lib/typescript/src/transport.d.ts +131 -0
  84. package/lib/typescript/src/transport.d.ts.map +1 -0
  85. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts +111 -0
  86. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts.map +1 -0
  87. package/lib/typescript/src/websocket/bridge.d.ts +66 -0
  88. package/lib/typescript/src/websocket/bridge.d.ts.map +1 -0
  89. package/lib/typescript/src/websocket/index.d.ts +19 -0
  90. package/lib/typescript/src/websocket/index.d.ts.map +1 -0
  91. package/lib/typescript/src/websocket/protocol.d.ts +64 -0
  92. package/lib/typescript/src/websocket/protocol.d.ts.map +1 -0
  93. package/lib/typescript/src/websocket/serial-device-bridge.d.ts +32 -0
  94. package/lib/typescript/src/websocket/serial-device-bridge.d.ts.map +1 -0
  95. package/package.json +57 -3
  96. package/src/UsbSerial.ts +65 -90
  97. package/src/WebSerial.ts +351 -113
  98. package/src/index.ts +6 -8
  99. package/src/lib/dom-exception.ts +129 -60
  100. package/src/lib/event-target.ts +58 -21
  101. package/src/lib/promise.ts +7 -7
  102. package/src/lib/web-streams.ts +43 -0
  103. package/src/testing/device-fixture.ts +150 -0
  104. package/src/testing/expose.ts +147 -0
  105. package/src/testing/harness.ts +124 -0
  106. package/src/testing/in-memory-serial-transport.ts +840 -0
  107. package/src/testing/index.ts +90 -0
  108. package/src/testing/install-in-memory-serial-transport.ts +65 -0
  109. package/src/testing/serial-client.ts +313 -0
  110. package/src/testing/simulated-device.ts +193 -0
  111. package/src/testing/test-suite.ts +186 -0
  112. package/src/transport.ts +200 -0
  113. package/src/websocket/WebSocketSerialTransport.ts +796 -0
  114. package/src/websocket/bridge.ts +299 -0
  115. package/src/websocket/index.ts +38 -0
  116. package/src/websocket/protocol.ts +101 -0
  117. package/src/websocket/serial-device-bridge.ts +160 -0
  118. package/babel.config.js +0 -3
  119. package/biome.json +0 -35
  120. package/example/.watchmanconfig +0 -1
  121. package/example/App.tsx +0 -71
  122. package/example/__tests__/App.test.tsx +0 -16
  123. package/example/__tests__/connectEvents.test.tsx +0 -81
  124. package/example/__tests__/getPorts.test.tsx +0 -140
  125. package/example/android/app/build.gradle +0 -120
  126. package/example/android/app/debug.keystore +0 -0
  127. package/example/android/app/proguard-rules.pro +0 -10
  128. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  129. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  130. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  131. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  132. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  133. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  134. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  135. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  136. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  137. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  138. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  139. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  140. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  141. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  142. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  143. package/example/android/app/src/main/res/values/strings.xml +0 -3
  144. package/example/android/app/src/main/res/values/styles.xml +0 -9
  145. package/example/android/build.gradle +0 -22
  146. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  147. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  148. package/example/android/gradle.properties +0 -47
  149. package/example/android/gradlew +0 -252
  150. package/example/android/gradlew.bat +0 -94
  151. package/example/android/settings.gradle +0 -6
  152. package/example/app.json +0 -4
  153. package/example/babel.config.js +0 -21
  154. package/example/biome.json +0 -47
  155. package/example/deploy.sh +0 -11
  156. package/example/index.html +0 -26
  157. package/example/index.js +0 -9
  158. package/example/index.web.js +0 -8
  159. package/example/jest.config.js +0 -12
  160. package/example/metro.config.js +0 -58
  161. package/example/package-lock.json +0 -14510
  162. package/example/package.json +0 -48
  163. package/example/react-native.config.js +0 -17
  164. package/example/src/components/AppBar.tsx +0 -73
  165. package/example/src/components/Menu.tsx +0 -90
  166. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  167. package/example/src/screens/ConnectScreen.tsx +0 -195
  168. package/example/src/screens/DevicesScreen.tsx +0 -252
  169. package/example/src/screens/TerminalScreen.tsx +0 -572
  170. package/example/src/settings.ts +0 -43
  171. package/example/src/theme.ts +0 -19
  172. package/example/src/util/TextUtil.ts +0 -129
  173. package/example/tsconfig.json +0 -10
  174. package/example/vite.config.mjs +0 -55
  175. package/scripts/deploy-release.sh +0 -127
  176. package/tsconfig.build.json +0 -7
  177. package/tsconfig.json +0 -20
@@ -1,48 +0,0 @@
1
- {
2
- "name": "react-native-web-serial-api-example",
3
- "version": "0.0.1",
4
- "private": true,
5
- "scripts": {
6
- "android": "react-native run-android",
7
- "start": "react-native start",
8
- "build:android": "cd android && ./gradlew assembleDebug",
9
- "web": "vite",
10
- "web:build": "vite build",
11
- "web:preview": "vite preview",
12
- "lint": "biome check",
13
- "format": "biome check --write",
14
- "test": "jest"
15
- },
16
- "dependencies": {
17
- "react": "19.2.3",
18
- "react-dom": "19.2.3",
19
- "react-native": "0.85.3",
20
- "react-native-web": "^0.21.2",
21
- "react-native-web-serial-api": "file:.."
22
- },
23
- "devDependencies": {
24
- "@babel/core": "^7.25.2",
25
- "@babel/preset-env": "^7.25.3",
26
- "@babel/runtime": "^7.25.0",
27
- "@biomejs/biome": "2.4.16",
28
- "@react-native-community/cli": "20.1.3",
29
- "@react-native-community/cli-platform-android": "20.1.3",
30
- "@react-native/babel-preset": "0.85.3",
31
- "@react-native/jest-preset": "0.85.3",
32
- "@react-native/metro-config": "0.85.3",
33
- "@react-native/typescript-config": "0.85.3",
34
- "@testing-library/react-native": "^13.3.3",
35
- "@types/react": "^19.2.6",
36
- "@types/react-dom": "^19.2.0",
37
- "@vitejs/plugin-react": "^6.0.2",
38
- "babel-jest": "^29.6.3",
39
- "babel-plugin-module-resolver": "^5.0.3",
40
- "jest": "^29.6.3",
41
- "react-test-renderer": "19.2.3",
42
- "typescript": "6.0.3",
43
- "vite": "^8.0.0"
44
- },
45
- "engines": {
46
- "node": ">=22"
47
- }
48
- }
@@ -1,17 +0,0 @@
1
- const path = require('node:path');
2
- const pkg = require('../package.json');
3
-
4
- /**
5
- * The library lives one level up (repo root) and is installed here as a symlink
6
- * pointing to an ancestor of this example dir. React Native autolinking skips
7
- * dependencies whose resolved path is an ancestor of the project, so we map the
8
- * dependency explicitly to the library's android module. This mirrors what
9
- * create-react-native-library scaffolds for its example app.
10
- */
11
- module.exports = {
12
- dependencies: {
13
- [pkg.name]: {
14
- root: path.join(__dirname, '..'),
15
- },
16
- },
17
- };
@@ -1,73 +0,0 @@
1
- import React from 'react';
2
- import {
3
- Platform,
4
- StatusBar,
5
- StyleSheet,
6
- Text,
7
- TouchableOpacity,
8
- View,
9
- } from 'react-native';
10
- import {colors} from '../theme';
11
- import {Menu, type MenuItem} from './Menu';
12
-
13
- type Props = {
14
- title: string;
15
- onBack?: () => void;
16
- menu?: MenuItem[];
17
- };
18
-
19
- const statusBarHeight =
20
- Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;
21
-
22
- export function AppBar({title, onBack, menu}: Props) {
23
- const [open, setOpen] = React.useState(false);
24
- return (
25
- <View style={styles.wrap}>
26
- <View style={styles.bar}>
27
- {onBack ? (
28
- <TouchableOpacity onPress={onBack} style={styles.iconBtn}>
29
- <Text style={styles.icon}>←</Text>
30
- </TouchableOpacity>
31
- ) : null}
32
- <Text style={styles.title} numberOfLines={1}>
33
- {title}
34
- </Text>
35
- <View style={styles.spacer} />
36
- {menu?.length ? (
37
- <TouchableOpacity
38
- onPress={() => setOpen(true)}
39
- style={styles.iconBtn}>
40
- <Text style={styles.icon}>⋮</Text>
41
- </TouchableOpacity>
42
- ) : null}
43
- </View>
44
- {menu ? (
45
- <Menu visible={open} items={menu} onClose={() => setOpen(false)} />
46
- ) : null}
47
- </View>
48
- );
49
- }
50
-
51
- const styles = StyleSheet.create({
52
- wrap: {backgroundColor: colors.primary, paddingTop: statusBarHeight},
53
- bar: {
54
- height: 56,
55
- flexDirection: 'row',
56
- alignItems: 'center',
57
- paddingHorizontal: 4,
58
- },
59
- iconBtn: {
60
- width: 44,
61
- height: 44,
62
- alignItems: 'center',
63
- justifyContent: 'center',
64
- },
65
- icon: {color: colors.onPrimary, fontSize: 22},
66
- title: {
67
- color: colors.onPrimary,
68
- fontSize: 20,
69
- fontWeight: '600',
70
- marginLeft: 8,
71
- },
72
- spacer: {flex: 1},
73
- });
@@ -1,90 +0,0 @@
1
- import {
2
- Modal,
3
- Pressable,
4
- ScrollView,
5
- StyleSheet,
6
- Text,
7
- View,
8
- } from 'react-native';
9
- import {colors} from '../theme';
10
-
11
- export type MenuItem = {
12
- key: string;
13
- title: string;
14
- checkable?: boolean;
15
- checked?: boolean;
16
- disabled?: boolean;
17
- onPress: () => void;
18
- };
19
-
20
- type Props = {
21
- visible: boolean;
22
- items: MenuItem[];
23
- onClose: () => void;
24
- };
25
-
26
- /** Android-style overflow (⋮) dropdown anchored to the top-right. */
27
- export function Menu({visible, items, onClose}: Props) {
28
- return (
29
- <Modal
30
- visible={visible}
31
- transparent
32
- animationType="fade"
33
- onRequestClose={onClose}>
34
- <Pressable style={styles.backdrop} onPress={onClose}>
35
- <View style={styles.menu}>
36
- <ScrollView>
37
- {items.map(item => (
38
- <Pressable
39
- key={item.key}
40
- style={styles.item}
41
- disabled={item.disabled}
42
- onPress={() => {
43
- onClose();
44
- item.onPress();
45
- }}>
46
- <Text
47
- style={[styles.itemText, item.disabled && styles.disabled]}>
48
- {item.title}
49
- </Text>
50
- {item.checkable ? (
51
- <Text style={styles.check}>{item.checked ? '☑' : '☐'}</Text>
52
- ) : null}
53
- </Pressable>
54
- ))}
55
- </ScrollView>
56
- </View>
57
- </Pressable>
58
- </Modal>
59
- );
60
- }
61
-
62
- const styles = StyleSheet.create({
63
- backdrop: {
64
- flex: 1,
65
- alignItems: 'flex-end',
66
- paddingTop: 4,
67
- paddingRight: 4,
68
- },
69
- menu: {
70
- minWidth: 220,
71
- maxHeight: '80%',
72
- backgroundColor: colors.background,
73
- borderRadius: 4,
74
- paddingVertical: 4,
75
- elevation: 8,
76
- shadowColor: '#000',
77
- shadowOpacity: 0.3,
78
- shadowRadius: 6,
79
- shadowOffset: {width: 0, height: 2},
80
- },
81
- item: {
82
- flexDirection: 'row',
83
- alignItems: 'center',
84
- paddingVertical: 12,
85
- paddingHorizontal: 16,
86
- },
87
- itemText: {flex: 1, fontSize: 16, color: colors.text},
88
- disabled: {color: colors.textSecondary, opacity: 0.5},
89
- check: {fontSize: 16, marginLeft: 16, color: colors.text},
90
- });
@@ -1,120 +0,0 @@
1
- import {
2
- Alert,
3
- Modal,
4
- Pressable,
5
- ScrollView,
6
- StyleSheet,
7
- Text,
8
- View,
9
- } from 'react-native';
10
- import {colors} from '../theme';
11
-
12
- export type Choice<T> = {label: string; value: T};
13
-
14
- type Props<T> = {
15
- visible: boolean;
16
- title: string;
17
- options: Choice<T>[];
18
- selected: T;
19
- onSelect: (value: T) => void;
20
- onClose: () => void;
21
- /** When set, shows an "Info" button that pops this message. */
22
- infoMessage?: string;
23
- };
24
-
25
- /** Single-choice (radio) dialog, mirroring AlertDialog.setSingleChoiceItems. */
26
- export function SingleChoiceDialog<T extends string | number>({
27
- visible,
28
- title,
29
- options,
30
- selected,
31
- onSelect,
32
- onClose,
33
- infoMessage,
34
- }: Props<T>) {
35
- return (
36
- <Modal
37
- visible={visible}
38
- transparent
39
- animationType="fade"
40
- onRequestClose={onClose}>
41
- <Pressable style={styles.backdrop} onPress={onClose}>
42
- <Pressable style={styles.card} onPress={() => {}}>
43
- <Text style={styles.title}>{title}</Text>
44
- <ScrollView style={styles.list}>
45
- {options.map(opt => {
46
- const isSelected = opt.value === selected;
47
- return (
48
- <Pressable
49
- key={String(opt.value)}
50
- style={styles.row}
51
- onPress={() => {
52
- onSelect(opt.value);
53
- onClose();
54
- }}>
55
- <Text style={styles.radio}>{isSelected ? '◉' : '◯'}</Text>
56
- <Text style={styles.label}>{opt.label}</Text>
57
- </Pressable>
58
- );
59
- })}
60
- </ScrollView>
61
- <View style={styles.footer}>
62
- {infoMessage ? (
63
- <Pressable
64
- style={styles.footerBtn}
65
- onPress={() => Alert.alert(title, infoMessage)}>
66
- <Text style={styles.footerText}>INFO</Text>
67
- </Pressable>
68
- ) : null}
69
- <Pressable style={styles.footerBtn} onPress={onClose}>
70
- <Text style={styles.footerText}>CANCEL</Text>
71
- </Pressable>
72
- </View>
73
- </Pressable>
74
- </Pressable>
75
- </Modal>
76
- );
77
- }
78
-
79
- const styles = StyleSheet.create({
80
- backdrop: {
81
- flex: 1,
82
- backgroundColor: 'rgba(0,0,0,0.4)',
83
- justifyContent: 'center',
84
- alignItems: 'center',
85
- padding: 24,
86
- },
87
- card: {
88
- width: '100%',
89
- maxWidth: 360,
90
- maxHeight: '80%',
91
- backgroundColor: colors.background,
92
- borderRadius: 6,
93
- paddingVertical: 16,
94
- elevation: 12,
95
- },
96
- title: {
97
- fontSize: 18,
98
- fontWeight: '600',
99
- color: colors.text,
100
- paddingHorizontal: 20,
101
- paddingBottom: 12,
102
- },
103
- list: {flexGrow: 0},
104
- row: {
105
- flexDirection: 'row',
106
- alignItems: 'center',
107
- paddingVertical: 12,
108
- paddingHorizontal: 20,
109
- },
110
- radio: {fontSize: 18, color: colors.primary, marginRight: 16},
111
- label: {fontSize: 16, color: colors.text},
112
- footer: {
113
- flexDirection: 'row',
114
- justifyContent: 'flex-end',
115
- paddingHorizontal: 12,
116
- paddingTop: 8,
117
- },
118
- footerBtn: {paddingVertical: 8, paddingHorizontal: 12},
119
- footerText: {color: colors.primary, fontWeight: '600', fontSize: 14},
120
- });
@@ -1,195 +0,0 @@
1
- import React from 'react';
2
- import {
3
- ScrollView,
4
- StyleSheet,
5
- Text,
6
- TouchableOpacity,
7
- View,
8
- } from 'react-native';
9
- import type {SerialPort} from 'react-native-web-serial-api';
10
- import {AppBar} from '../components/AppBar';
11
- import {
12
- type Choice,
13
- SingleChoiceDialog,
14
- } from '../components/SingleChoiceDialog';
15
- import {
16
- BAUD_RATES,
17
- type ConnectionSettings,
18
- DATA_BITS,
19
- DEFAULT_SETTINGS,
20
- FLOW_CONTROL_LABELS,
21
- FLOW_CONTROLS,
22
- PARITIES,
23
- STOP_BITS,
24
- } from '../settings';
25
- import {colors} from '../theme';
26
-
27
- type Props = {
28
- port: SerialPort;
29
- initial?: ConnectionSettings;
30
- onBack: () => void;
31
- onConnect: (settings: ConnectionSettings) => void;
32
- };
33
-
34
- function hex4(n: number | undefined): string {
35
- return (n ?? 0).toString(16).toUpperCase().padStart(4, '0');
36
- }
37
-
38
- export function ConnectScreen({port, initial, onBack, onConnect}: Props) {
39
- const [settings, setSettings] = React.useState<ConnectionSettings>(
40
- initial ?? DEFAULT_SETTINGS,
41
- );
42
- // Which dropdown's dialog is open (null = none).
43
- const [open, setOpen] = React.useState<keyof ConnectionSettings | null>(null);
44
-
45
- const info = port.getInfo();
46
-
47
- const set = <K extends keyof ConnectionSettings>(
48
- key: K,
49
- value: ConnectionSettings[K],
50
- ) => setSettings(s => ({...s, [key]: value}));
51
-
52
- return (
53
- <View style={styles.container}>
54
- <AppBar title="Connection settings" onBack={onBack} />
55
-
56
- <ScrollView contentContainerStyle={styles.form}>
57
- <Text style={styles.device}>
58
- {`Vendor ${hex4(info.usbVendorId)} · Product ${hex4(
59
- info.usbProductId,
60
- )}`}
61
- </Text>
62
-
63
- <Dropdown
64
- label="Baud rate"
65
- value={String(settings.baudRate)}
66
- onPress={() => setOpen('baudRate')}
67
- />
68
- <Dropdown
69
- label="Data bits"
70
- value={String(settings.dataBits)}
71
- onPress={() => setOpen('dataBits')}
72
- />
73
- <Dropdown
74
- label="Stop bits"
75
- value={String(settings.stopBits)}
76
- onPress={() => setOpen('stopBits')}
77
- />
78
- <Dropdown
79
- label="Parity"
80
- value={settings.parity}
81
- onPress={() => setOpen('parity')}
82
- />
83
- <Dropdown
84
- label="Flow control"
85
- value={FLOW_CONTROL_LABELS[settings.flowControl]}
86
- onPress={() => setOpen('flowControl')}
87
- />
88
-
89
- <TouchableOpacity
90
- style={styles.connectBtn}
91
- onPress={() => onConnect(settings)}>
92
- <Text style={styles.connectText}>Connect</Text>
93
- </TouchableOpacity>
94
- </ScrollView>
95
-
96
- <SingleChoiceDialog
97
- visible={open === 'baudRate'}
98
- title="Baud rate"
99
- options={BAUD_RATES.map(b => ({label: String(b), value: b}))}
100
- selected={settings.baudRate}
101
- onSelect={v => set('baudRate', v)}
102
- onClose={() => setOpen(null)}
103
- />
104
- <SingleChoiceDialog
105
- visible={open === 'dataBits'}
106
- title="Data bits"
107
- options={DATA_BITS.map(b => ({label: String(b), value: b}))}
108
- selected={settings.dataBits}
109
- onSelect={v => set('dataBits', v as ConnectionSettings['dataBits'])}
110
- onClose={() => setOpen(null)}
111
- />
112
- <SingleChoiceDialog
113
- visible={open === 'stopBits'}
114
- title="Stop bits"
115
- options={STOP_BITS.map(b => ({label: String(b), value: b}))}
116
- selected={settings.stopBits}
117
- onSelect={v => set('stopBits', v as ConnectionSettings['stopBits'])}
118
- onClose={() => setOpen(null)}
119
- />
120
- <SingleChoiceDialog
121
- visible={open === 'parity'}
122
- title="Parity"
123
- options={PARITIES.map(p => ({label: p, value: p})) as Choice<string>[]}
124
- selected={settings.parity}
125
- onSelect={v => set('parity', v as ConnectionSettings['parity'])}
126
- onClose={() => setOpen(null)}
127
- />
128
- <SingleChoiceDialog
129
- visible={open === 'flowControl'}
130
- title="Flow control"
131
- options={
132
- FLOW_CONTROLS.map(f => ({
133
- label: FLOW_CONTROL_LABELS[f],
134
- value: f,
135
- })) as Choice<string>[]
136
- }
137
- selected={settings.flowControl}
138
- onSelect={v =>
139
- set('flowControl', v as ConnectionSettings['flowControl'])
140
- }
141
- onClose={() => setOpen(null)}
142
- />
143
- </View>
144
- );
145
- }
146
-
147
- function Dropdown({
148
- label,
149
- value,
150
- onPress,
151
- }: {
152
- label: string;
153
- value: string;
154
- onPress: () => void;
155
- }) {
156
- return (
157
- <TouchableOpacity style={styles.row} onPress={onPress}>
158
- <Text style={styles.rowLabel}>{label}</Text>
159
- <View style={styles.rowValueWrap}>
160
- <Text style={styles.rowValue}>{value}</Text>
161
- <Text style={styles.caret}>▾</Text>
162
- </View>
163
- </TouchableOpacity>
164
- );
165
- }
166
-
167
- const styles = StyleSheet.create({
168
- container: {flex: 1, backgroundColor: colors.background},
169
- form: {padding: 16},
170
- device: {
171
- fontSize: 13,
172
- color: colors.textSecondary,
173
- marginBottom: 16,
174
- },
175
- row: {
176
- flexDirection: 'row',
177
- alignItems: 'center',
178
- justifyContent: 'space-between',
179
- paddingVertical: 14,
180
- borderBottomWidth: 1,
181
- borderBottomColor: colors.divider,
182
- },
183
- rowLabel: {fontSize: 16, color: colors.text},
184
- rowValueWrap: {flexDirection: 'row', alignItems: 'center'},
185
- rowValue: {fontSize: 16, color: colors.primary, fontWeight: '600'},
186
- caret: {fontSize: 14, color: colors.primary, marginLeft: 8},
187
- connectBtn: {
188
- marginTop: 28,
189
- backgroundColor: colors.primary,
190
- borderRadius: 6,
191
- paddingVertical: 14,
192
- alignItems: 'center',
193
- },
194
- connectText: {color: colors.onPrimary, fontSize: 16, fontWeight: '600'},
195
- });