@moldable-ai/ui 0.2.17 → 0.2.19

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 (232) hide show
  1. package/dist/components/chat/chat-panel.d.ts +18 -12
  2. package/dist/components/chat/chat-panel.d.ts.map +1 -1
  3. package/dist/components/chat/chat-panel.js +73 -19
  4. package/dist/components/chat/index.d.ts +2 -1
  5. package/dist/components/chat/index.d.ts.map +1 -1
  6. package/dist/components/chat/index.js +2 -1
  7. package/dist/components/chat/model-effort-power-effects.d.ts +7 -0
  8. package/dist/components/chat/model-effort-power-effects.d.ts.map +1 -0
  9. package/dist/components/chat/model-effort-power-effects.js +54 -0
  10. package/dist/components/chat/model-effort-selector.d.ts +26 -0
  11. package/dist/components/chat/model-effort-selector.d.ts.map +1 -0
  12. package/dist/components/chat/model-effort-selector.js +179 -0
  13. package/dist/components/chat/model-effort-ultra-effects.d.ts +5 -0
  14. package/dist/components/chat/model-effort-ultra-effects.d.ts.map +1 -0
  15. package/dist/components/chat/model-effort-ultra-effects.js +56 -0
  16. package/dist/components/chat/tool-handlers.d.ts.map +1 -1
  17. package/dist/components/hardware/bluetooth-panel-helpers.d.ts +9 -0
  18. package/dist/components/hardware/bluetooth-panel-helpers.d.ts.map +1 -0
  19. package/dist/components/hardware/bluetooth-panel-helpers.js +37 -0
  20. package/dist/components/hardware/bluetooth-panel.d.ts +56 -0
  21. package/dist/components/hardware/bluetooth-panel.d.ts.map +1 -0
  22. package/dist/components/hardware/bluetooth-panel.js +470 -0
  23. package/dist/components/hardware/camera-preview.d.ts +39 -0
  24. package/dist/components/hardware/camera-preview.d.ts.map +1 -0
  25. package/dist/components/hardware/camera-preview.js +228 -0
  26. package/dist/components/hardware/capability-badge.d.ts +23 -0
  27. package/dist/components/hardware/capability-badge.d.ts.map +1 -0
  28. package/dist/components/hardware/capability-badge.js +39 -0
  29. package/dist/components/hardware/capability-matrix.d.ts +14 -0
  30. package/dist/components/hardware/capability-matrix.d.ts.map +1 -0
  31. package/dist/components/hardware/capability-matrix.js +97 -0
  32. package/dist/components/hardware/clipboard-button.d.ts +26 -0
  33. package/dist/components/hardware/clipboard-button.d.ts.map +1 -0
  34. package/dist/components/hardware/clipboard-button.js +114 -0
  35. package/dist/components/hardware/device-io-controls.d.ts +4 -0
  36. package/dist/components/hardware/device-io-controls.d.ts.map +1 -0
  37. package/dist/components/hardware/device-io-controls.js +28 -0
  38. package/dist/components/hardware/device-list.d.ts +20 -0
  39. package/dist/components/hardware/device-list.d.ts.map +1 -0
  40. package/dist/components/hardware/device-list.js +14 -0
  41. package/dist/components/hardware/display-map.d.ts +23 -0
  42. package/dist/components/hardware/display-map.d.ts.map +1 -0
  43. package/dist/components/hardware/display-map.js +114 -0
  44. package/dist/components/hardware/haptic-button.d.ts +26 -0
  45. package/dist/components/hardware/haptic-button.d.ts.map +1 -0
  46. package/dist/components/hardware/haptic-button.js +94 -0
  47. package/dist/components/hardware/hardware-panel.d.ts +11 -0
  48. package/dist/components/hardware/hardware-panel.d.ts.map +1 -0
  49. package/dist/components/hardware/hardware-panel.js +28 -0
  50. package/dist/components/hardware/hid-panel.d.ts +40 -0
  51. package/dist/components/hardware/hid-panel.d.ts.map +1 -0
  52. package/dist/components/hardware/hid-panel.js +373 -0
  53. package/dist/components/hardware/index.d.ts +27 -0
  54. package/dist/components/hardware/index.d.ts.map +1 -0
  55. package/dist/components/hardware/index.js +26 -0
  56. package/dist/components/hardware/live-indicator.d.ts +15 -0
  57. package/dist/components/hardware/live-indicator.d.ts.map +1 -0
  58. package/dist/components/hardware/live-indicator.js +26 -0
  59. package/dist/components/hardware/local-auth-button.d.ts +33 -0
  60. package/dist/components/hardware/local-auth-button.d.ts.map +1 -0
  61. package/dist/components/hardware/local-auth-button.js +115 -0
  62. package/dist/components/hardware/location-panel.d.ts +29 -0
  63. package/dist/components/hardware/location-panel.d.ts.map +1 -0
  64. package/dist/components/hardware/location-panel.js +154 -0
  65. package/dist/components/hardware/microphone-meter.d.ts +35 -0
  66. package/dist/components/hardware/microphone-meter.d.ts.map +1 -0
  67. package/dist/components/hardware/microphone-meter.js +244 -0
  68. package/dist/components/hardware/midi-monitor.d.ts +54 -0
  69. package/dist/components/hardware/midi-monitor.d.ts.map +1 -0
  70. package/dist/components/hardware/midi-monitor.js +534 -0
  71. package/dist/components/hardware/notification-panel.d.ts +24 -0
  72. package/dist/components/hardware/notification-panel.d.ts.map +1 -0
  73. package/dist/components/hardware/notification-panel.js +124 -0
  74. package/dist/components/hardware/power-blocker-controller.d.ts +19 -0
  75. package/dist/components/hardware/power-blocker-controller.d.ts.map +1 -0
  76. package/dist/components/hardware/power-blocker-controller.js +83 -0
  77. package/dist/components/hardware/power-panel.d.ts +67 -0
  78. package/dist/components/hardware/power-panel.d.ts.map +1 -0
  79. package/dist/components/hardware/power-panel.js +309 -0
  80. package/dist/components/hardware/readout.d.ts +10 -0
  81. package/dist/components/hardware/readout.d.ts.map +1 -0
  82. package/dist/components/hardware/readout.js +11 -0
  83. package/dist/components/hardware/screen-share-preview.d.ts +36 -0
  84. package/dist/components/hardware/screen-share-preview.d.ts.map +1 -0
  85. package/dist/components/hardware/screen-share-preview.js +191 -0
  86. package/dist/components/hardware/secure-storage-panel.d.ts +36 -0
  87. package/dist/components/hardware/secure-storage-panel.d.ts.map +1 -0
  88. package/dist/components/hardware/secure-storage-panel.js +193 -0
  89. package/dist/components/hardware/serial-console.d.ts +64 -0
  90. package/dist/components/hardware/serial-console.d.ts.map +1 -0
  91. package/dist/components/hardware/serial-console.js +399 -0
  92. package/dist/components/hardware/shortcut-recorder.d.ts +35 -0
  93. package/dist/components/hardware/shortcut-recorder.d.ts.map +1 -0
  94. package/dist/components/hardware/shortcut-recorder.js +217 -0
  95. package/dist/components/hardware/stream-log.d.ts +35 -0
  96. package/dist/components/hardware/stream-log.d.ts.map +1 -0
  97. package/dist/components/hardware/stream-log.js +65 -0
  98. package/dist/components/hardware/system-audio-monitor.d.ts +34 -0
  99. package/dist/components/hardware/system-audio-monitor.d.ts.map +1 -0
  100. package/dist/components/hardware/system-audio-monitor.js +275 -0
  101. package/dist/components/hardware/usb-panel.d.ts +54 -0
  102. package/dist/components/hardware/usb-panel.d.ts.map +1 -0
  103. package/dist/components/hardware/usb-panel.js +363 -0
  104. package/dist/components/hardware/use-capability.d.ts +20 -0
  105. package/dist/components/hardware/use-capability.d.ts.map +1 -0
  106. package/dist/components/hardware/use-capability.js +61 -0
  107. package/dist/components/ui/input-group.d.ts +1 -1
  108. package/dist/components/ui/slider.d.ts +4 -1
  109. package/dist/components/ui/slider.d.ts.map +1 -1
  110. package/dist/components/ui/slider.js +2 -2
  111. package/dist/index.d.ts +3 -1
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +5 -1
  114. package/dist/lib/commands.d.ts +59 -0
  115. package/dist/lib/commands.d.ts.map +1 -1
  116. package/dist/lib/commands.js +97 -0
  117. package/dist/lib/native-hardware/bluetooth-access.d.ts +14 -0
  118. package/dist/lib/native-hardware/bluetooth-access.d.ts.map +1 -0
  119. package/dist/lib/native-hardware/bluetooth-access.js +40 -0
  120. package/dist/lib/native-hardware/bluetooth-connection.d.ts +19 -0
  121. package/dist/lib/native-hardware/bluetooth-connection.d.ts.map +1 -0
  122. package/dist/lib/native-hardware/bluetooth-connection.js +88 -0
  123. package/dist/lib/native-hardware/bluetooth-gatt.d.ts +7 -0
  124. package/dist/lib/native-hardware/bluetooth-gatt.d.ts.map +1 -0
  125. package/dist/lib/native-hardware/bluetooth-gatt.js +77 -0
  126. package/dist/lib/native-hardware/bluetooth-internal.d.ts +10 -0
  127. package/dist/lib/native-hardware/bluetooth-internal.d.ts.map +1 -0
  128. package/dist/lib/native-hardware/bluetooth-internal.js +105 -0
  129. package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts +33 -0
  130. package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts.map +1 -0
  131. package/dist/lib/native-hardware/bluetooth-native-fallback.js +214 -0
  132. package/dist/lib/native-hardware/bluetooth-types.d.ts +85 -0
  133. package/dist/lib/native-hardware/bluetooth-types.d.ts.map +1 -0
  134. package/dist/lib/native-hardware/bluetooth-types.js +2 -0
  135. package/dist/lib/native-hardware/bluetooth.d.ts +7 -0
  136. package/dist/lib/native-hardware/bluetooth.d.ts.map +1 -0
  137. package/dist/lib/native-hardware/bluetooth.js +4 -0
  138. package/dist/lib/native-hardware/bridge.d.ts +23 -0
  139. package/dist/lib/native-hardware/bridge.d.ts.map +1 -0
  140. package/dist/lib/native-hardware/bridge.js +266 -0
  141. package/dist/lib/native-hardware/capabilities.d.ts +7 -0
  142. package/dist/lib/native-hardware/capabilities.d.ts.map +1 -0
  143. package/dist/lib/native-hardware/capabilities.js +95 -0
  144. package/dist/lib/native-hardware/clipboard.d.ts +11 -0
  145. package/dist/lib/native-hardware/clipboard.d.ts.map +1 -0
  146. package/dist/lib/native-hardware/clipboard.js +55 -0
  147. package/dist/lib/native-hardware/device-io-errors.d.ts +11 -0
  148. package/dist/lib/native-hardware/device-io-errors.d.ts.map +1 -0
  149. package/dist/lib/native-hardware/device-io-errors.js +82 -0
  150. package/dist/lib/native-hardware/displays.d.ts +38 -0
  151. package/dist/lib/native-hardware/displays.d.ts.map +1 -0
  152. package/dist/lib/native-hardware/displays.js +12 -0
  153. package/dist/lib/native-hardware/global-shortcuts.d.ts +86 -0
  154. package/dist/lib/native-hardware/global-shortcuts.d.ts.map +1 -0
  155. package/dist/lib/native-hardware/global-shortcuts.js +351 -0
  156. package/dist/lib/native-hardware/haptics.d.ts +25 -0
  157. package/dist/lib/native-hardware/haptics.d.ts.map +1 -0
  158. package/dist/lib/native-hardware/haptics.js +18 -0
  159. package/dist/lib/native-hardware/hid-native.d.ts +26 -0
  160. package/dist/lib/native-hardware/hid-native.d.ts.map +1 -0
  161. package/dist/lib/native-hardware/hid-native.js +229 -0
  162. package/dist/lib/native-hardware/hid.d.ts +52 -0
  163. package/dist/lib/native-hardware/hid.d.ts.map +1 -0
  164. package/dist/lib/native-hardware/hid.js +160 -0
  165. package/dist/lib/native-hardware/index.d.ts +38 -0
  166. package/dist/lib/native-hardware/index.d.ts.map +1 -0
  167. package/dist/lib/native-hardware/index.js +20 -0
  168. package/dist/lib/native-hardware/local-authentication.d.ts +32 -0
  169. package/dist/lib/native-hardware/local-authentication.d.ts.map +1 -0
  170. package/dist/lib/native-hardware/local-authentication.js +46 -0
  171. package/dist/lib/native-hardware/location.d.ts +49 -0
  172. package/dist/lib/native-hardware/location.d.ts.map +1 -0
  173. package/dist/lib/native-hardware/location.js +114 -0
  174. package/dist/lib/native-hardware/media-permissions.d.ts +28 -0
  175. package/dist/lib/native-hardware/media-permissions.d.ts.map +1 -0
  176. package/dist/lib/native-hardware/media-permissions.js +37 -0
  177. package/dist/lib/native-hardware/media.d.ts +17 -0
  178. package/dist/lib/native-hardware/media.d.ts.map +1 -0
  179. package/dist/lib/native-hardware/media.js +99 -0
  180. package/dist/lib/native-hardware/midi-access.d.ts +13 -0
  181. package/dist/lib/native-hardware/midi-access.d.ts.map +1 -0
  182. package/dist/lib/native-hardware/midi-access.js +66 -0
  183. package/dist/lib/native-hardware/midi-internal.d.ts +12 -0
  184. package/dist/lib/native-hardware/midi-internal.d.ts.map +1 -0
  185. package/dist/lib/native-hardware/midi-internal.js +88 -0
  186. package/dist/lib/native-hardware/midi-native-fallback.d.ts +27 -0
  187. package/dist/lib/native-hardware/midi-native-fallback.d.ts.map +1 -0
  188. package/dist/lib/native-hardware/midi-native-fallback.js +127 -0
  189. package/dist/lib/native-hardware/midi-ports.d.ts +6 -0
  190. package/dist/lib/native-hardware/midi-ports.d.ts.map +1 -0
  191. package/dist/lib/native-hardware/midi-ports.js +115 -0
  192. package/dist/lib/native-hardware/midi-types.d.ts +25 -0
  193. package/dist/lib/native-hardware/midi-types.d.ts.map +1 -0
  194. package/dist/lib/native-hardware/midi-types.js +1 -0
  195. package/dist/lib/native-hardware/midi.d.ts +6 -0
  196. package/dist/lib/native-hardware/midi.d.ts.map +1 -0
  197. package/dist/lib/native-hardware/midi.js +3 -0
  198. package/dist/lib/native-hardware/native-device-io.d.ts +28 -0
  199. package/dist/lib/native-hardware/native-device-io.d.ts.map +1 -0
  200. package/dist/lib/native-hardware/native-device-io.js +91 -0
  201. package/dist/lib/native-hardware/native-device-lifecycle.d.ts +13 -0
  202. package/dist/lib/native-hardware/native-device-lifecycle.d.ts.map +1 -0
  203. package/dist/lib/native-hardware/native-device-lifecycle.js +40 -0
  204. package/dist/lib/native-hardware/notifications.d.ts +38 -0
  205. package/dist/lib/native-hardware/notifications.d.ts.map +1 -0
  206. package/dist/lib/native-hardware/notifications.js +50 -0
  207. package/dist/lib/native-hardware/power-session.d.ts +113 -0
  208. package/dist/lib/native-hardware/power-session.d.ts.map +1 -0
  209. package/dist/lib/native-hardware/power-session.js +264 -0
  210. package/dist/lib/native-hardware/secure-storage.d.ts +36 -0
  211. package/dist/lib/native-hardware/secure-storage.d.ts.map +1 -0
  212. package/dist/lib/native-hardware/secure-storage.js +64 -0
  213. package/dist/lib/native-hardware/serial-native.d.ts +23 -0
  214. package/dist/lib/native-hardware/serial-native.d.ts.map +1 -0
  215. package/dist/lib/native-hardware/serial-native.js +181 -0
  216. package/dist/lib/native-hardware/serial.d.ts +76 -0
  217. package/dist/lib/native-hardware/serial.d.ts.map +1 -0
  218. package/dist/lib/native-hardware/serial.js +212 -0
  219. package/dist/lib/native-hardware/system-audio.d.ts +127 -0
  220. package/dist/lib/native-hardware/system-audio.d.ts.map +1 -0
  221. package/dist/lib/native-hardware/system-audio.js +330 -0
  222. package/dist/lib/native-hardware/types.d.ts +62 -0
  223. package/dist/lib/native-hardware/types.d.ts.map +1 -0
  224. package/dist/lib/native-hardware/types.js +27 -0
  225. package/dist/lib/native-hardware/usb-native.d.ts +39 -0
  226. package/dist/lib/native-hardware/usb-native.d.ts.map +1 -0
  227. package/dist/lib/native-hardware/usb-native.js +217 -0
  228. package/dist/lib/native-hardware/usb.d.ts +85 -0
  229. package/dist/lib/native-hardware/usb.d.ts.map +1 -0
  230. package/dist/lib/native-hardware/usb.js +222 -0
  231. package/package.json +1 -1
  232. package/src/styles/index.css +377 -0
@@ -0,0 +1,373 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Gamepad2, RefreshCw, Trash2 } from 'lucide-react';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { NativeHardwareBridgeError, closeMoldableHidDevice, forgetMoldableHidDevice, getMoldableHidDevices, listenForMoldableHidInputReports, openMoldableHidDevice, receiveMoldableHidFeatureReport, requestMoldableHidDevices, sendMoldableHidFeatureReport, sendMoldableHidReport, } from '../../lib/native-hardware';
6
+ import { Button } from '../ui/button';
7
+ import { Input } from '../ui/input';
8
+ import { Spinner } from '../ui/spinner';
9
+ import { CapabilityBadge } from './capability-badge';
10
+ import { formatHexBytes, parseHexBytes, parseIntegerInput, } from './device-io-controls';
11
+ import { DeviceList, DeviceListEmpty, DeviceListItem } from './device-list';
12
+ import { HardwarePanel, HardwarePanelAction, HardwarePanelContent, HardwarePanelHeader, HardwarePanelIcon, HardwarePanelTitle, } from './hardware-panel';
13
+ import { LiveIndicator } from './live-indicator';
14
+ import { StreamLog, useStreamLog } from './stream-log';
15
+ const HEX_DUMP_MAX_BYTES = 32;
16
+ function toError(cause) {
17
+ return cause instanceof Error ? cause : new Error(String(cause));
18
+ }
19
+ function isDismissedChooser(cause) {
20
+ return (cause instanceof NativeHardwareBridgeError &&
21
+ (cause.code === 'cancelled' || cause.code === 'aborted'));
22
+ }
23
+ function asBytes(data) {
24
+ return data instanceof ArrayBuffer
25
+ ? new Uint8Array(data)
26
+ : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
27
+ }
28
+ function viewBytes(view) {
29
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
30
+ }
31
+ function formatHexDump(bytes) {
32
+ if (bytes.byteLength === 0)
33
+ return '(empty)';
34
+ const shown = Array.from(bytes.subarray(0, HEX_DUMP_MAX_BYTES))
35
+ .map((byte) => byte.toString(16).padStart(2, '0').toUpperCase())
36
+ .join(' ');
37
+ const hidden = bytes.byteLength - HEX_DUMP_MAX_BYTES;
38
+ return hidden > 0 ? `${shown} +${hidden} more` : shown;
39
+ }
40
+ function formatHexId(value) {
41
+ return value.toString(16).padStart(4, '0');
42
+ }
43
+ /** WebHID exposes report collections; the native fallback does not. */
44
+ function describeHidUsage(device) {
45
+ const collections = device.collections;
46
+ if (!Array.isArray(collections))
47
+ return null;
48
+ const first = collections[0];
49
+ if (!first ||
50
+ typeof first.usagePage !== 'number' ||
51
+ typeof first.usage !== 'number') {
52
+ return null;
53
+ }
54
+ return `usage ${formatHexId(first.usagePage)}:${formatHexId(first.usage)}`;
55
+ }
56
+ /**
57
+ * HID state machine behind `HidDevicePanel`: granted-device list, chooser
58
+ * requests, open/close with input-report streaming, and output reports. Use
59
+ * directly to build custom HID UI on the same lifecycle handling.
60
+ */
61
+ export function useMoldableHid(options = {}) {
62
+ const [devices, setDevices] = useState([]);
63
+ const [status, setStatus] = useState('idle');
64
+ const [error, setError] = useState(null);
65
+ const [openIds, setOpenIds] = useState([]);
66
+ const { entries, append, clear } = useStreamLog();
67
+ const generationRef = useRef(0);
68
+ const openRef = useRef(new Map());
69
+ const idsRef = useRef(new WeakMap());
70
+ const idCounterRef = useRef(0);
71
+ const onInputReportRef = useRef(options.onInputReport);
72
+ onInputReportRef.current = options.onInputReport;
73
+ /** Stable identity for a device instance (HIDDevice has no intrinsic id). */
74
+ const deviceId = useCallback((device) => {
75
+ let id = idsRef.current.get(device);
76
+ if (!id) {
77
+ id = `hid-${++idCounterRef.current}`;
78
+ idsRef.current.set(device, id);
79
+ }
80
+ return id;
81
+ }, []);
82
+ const detach = useCallback((device) => {
83
+ const record = openRef.current.get(device);
84
+ if (!record)
85
+ return false;
86
+ openRef.current.delete(device);
87
+ record.stop();
88
+ device.removeEventListener('disconnect', record.onDisconnect);
89
+ const id = deviceId(device);
90
+ setOpenIds((previous) => previous.filter((value) => value !== id));
91
+ return true;
92
+ }, [deviceId]);
93
+ const refresh = useCallback(async () => {
94
+ const generation = generationRef.current;
95
+ try {
96
+ const granted = await getMoldableHidDevices();
97
+ if (generationRef.current !== generation)
98
+ return;
99
+ setDevices((previous) => [
100
+ ...granted,
101
+ ...previous.filter((device) => openRef.current.has(device) && !granted.includes(device)),
102
+ ]);
103
+ }
104
+ catch {
105
+ // Granted-device enumeration is unavailable on the native fallback;
106
+ // devices arrive through the chooser instead.
107
+ }
108
+ }, []);
109
+ const request = useCallback(async (filters = []) => {
110
+ const generation = generationRef.current;
111
+ setStatus('requesting');
112
+ setError(null);
113
+ try {
114
+ const selected = await requestMoldableHidDevices({ filters });
115
+ if (generationRef.current !== generation)
116
+ return;
117
+ setStatus('idle');
118
+ if (selected.length > 0) {
119
+ setDevices((previous) => [
120
+ ...previous,
121
+ ...selected.filter((device) => !previous.includes(device)),
122
+ ]);
123
+ }
124
+ }
125
+ catch (cause) {
126
+ if (generationRef.current !== generation)
127
+ return;
128
+ if (isDismissedChooser(cause)) {
129
+ setStatus('idle');
130
+ return;
131
+ }
132
+ setError(toError(cause));
133
+ setStatus('error');
134
+ }
135
+ }, []);
136
+ const open = useCallback(async (device) => {
137
+ if (openRef.current.has(device))
138
+ return;
139
+ const generation = generationRef.current;
140
+ setError(null);
141
+ let opened = false;
142
+ try {
143
+ await openMoldableHidDevice(device);
144
+ opened = true;
145
+ if (generationRef.current !== generation) {
146
+ void closeMoldableHidDevice(device).catch(() => undefined);
147
+ return;
148
+ }
149
+ const stop = listenForMoldableHidInputReports(device, (event) => {
150
+ append({
151
+ direction: 'in',
152
+ data: `[${event.reportId}] ${formatHexDump(viewBytes(event.data))}`,
153
+ });
154
+ onInputReportRef.current?.(event);
155
+ });
156
+ const onDisconnect = () => {
157
+ if (!detach(device))
158
+ return;
159
+ append({
160
+ direction: 'info',
161
+ data: `${device.productName || 'HID device'} disconnected`,
162
+ });
163
+ void closeMoldableHidDevice(device).catch(() => undefined);
164
+ };
165
+ device.addEventListener('disconnect', onDisconnect);
166
+ openRef.current.set(device, { stop, onDisconnect });
167
+ const id = deviceId(device);
168
+ setOpenIds((previous) => previous.includes(id) ? previous : [...previous, id]);
169
+ setStatus('idle');
170
+ }
171
+ catch (cause) {
172
+ if (opened && !openRef.current.has(device)) {
173
+ void closeMoldableHidDevice(device).catch(() => undefined);
174
+ }
175
+ if (generationRef.current !== generation)
176
+ return;
177
+ setError(toError(cause));
178
+ setStatus('error');
179
+ }
180
+ }, [append, detach, deviceId]);
181
+ const close = useCallback(async (device) => {
182
+ const generation = generationRef.current;
183
+ try {
184
+ await closeMoldableHidDevice(device);
185
+ if (generationRef.current !== generation)
186
+ return false;
187
+ detach(device);
188
+ setError(null);
189
+ setStatus('idle');
190
+ return true;
191
+ }
192
+ catch (cause) {
193
+ if (generationRef.current !== generation)
194
+ return false;
195
+ setError(toError(cause));
196
+ setStatus('error');
197
+ return false;
198
+ }
199
+ }, [detach]);
200
+ const sendReport = useCallback(async (device, reportId, data) => {
201
+ const generation = generationRef.current;
202
+ setError(null);
203
+ try {
204
+ await sendMoldableHidReport(device, reportId, data);
205
+ if (generationRef.current !== generation)
206
+ return false;
207
+ append({
208
+ direction: 'out',
209
+ data: `[${reportId}] ${formatHexDump(asBytes(data))}`,
210
+ });
211
+ setStatus('idle');
212
+ return true;
213
+ }
214
+ catch (cause) {
215
+ if (generationRef.current !== generation)
216
+ return false;
217
+ setError(toError(cause));
218
+ setStatus('error');
219
+ return false;
220
+ }
221
+ }, [append]);
222
+ const sendFeatureReport = useCallback(async (device, reportId, data) => {
223
+ const generation = generationRef.current;
224
+ setError(null);
225
+ try {
226
+ await sendMoldableHidFeatureReport(device, reportId, data);
227
+ if (generationRef.current !== generation)
228
+ return false;
229
+ append({
230
+ direction: 'out',
231
+ data: `[feature ${reportId}] ${formatHexDump(asBytes(data))}`,
232
+ });
233
+ setStatus('idle');
234
+ return true;
235
+ }
236
+ catch (cause) {
237
+ if (generationRef.current !== generation)
238
+ return false;
239
+ setError(toError(cause));
240
+ setStatus('error');
241
+ return false;
242
+ }
243
+ }, [append]);
244
+ const receiveFeatureReport = useCallback(async (device, reportId) => {
245
+ const generation = generationRef.current;
246
+ setError(null);
247
+ try {
248
+ const data = await receiveMoldableHidFeatureReport(device, reportId);
249
+ if (generationRef.current !== generation)
250
+ return;
251
+ append({
252
+ direction: 'in',
253
+ data: `[feature ${reportId}] ${formatHexDump(viewBytes(data))}`,
254
+ });
255
+ setStatus('idle');
256
+ return data;
257
+ }
258
+ catch (cause) {
259
+ if (generationRef.current !== generation)
260
+ return;
261
+ setError(toError(cause));
262
+ setStatus('error');
263
+ }
264
+ }, [append]);
265
+ const forget = useCallback(async (device) => {
266
+ if (openRef.current.has(device) && !(await close(device)))
267
+ return false;
268
+ const generation = generationRef.current;
269
+ setError(null);
270
+ try {
271
+ await forgetMoldableHidDevice(device);
272
+ if (generationRef.current !== generation)
273
+ return false;
274
+ setDevices((previous) => previous.filter((known) => known !== device));
275
+ setStatus('idle');
276
+ return true;
277
+ }
278
+ catch (cause) {
279
+ if (generationRef.current !== generation)
280
+ return false;
281
+ setError(toError(cause));
282
+ setStatus('error');
283
+ return false;
284
+ }
285
+ }, [close]);
286
+ useEffect(() => {
287
+ void refresh();
288
+ const liveGeneration = generationRef;
289
+ const liveOpen = openRef;
290
+ return () => {
291
+ liveGeneration.current++;
292
+ for (const [device, record] of liveOpen.current) {
293
+ record.stop();
294
+ device.removeEventListener('disconnect', record.onDisconnect);
295
+ void closeMoldableHidDevice(device).catch(() => undefined);
296
+ }
297
+ liveOpen.current.clear();
298
+ };
299
+ }, [refresh]);
300
+ return {
301
+ devices,
302
+ status,
303
+ error,
304
+ openIds,
305
+ entries,
306
+ deviceId,
307
+ refresh,
308
+ request,
309
+ open,
310
+ close,
311
+ sendReport,
312
+ sendFeatureReport,
313
+ receiveFeatureReport,
314
+ forget,
315
+ clearEntries: clear,
316
+ };
317
+ }
318
+ function HidReportControls({ device, hid }) {
319
+ const [reportId, setReportId] = useState('0');
320
+ const [payload, setPayload] = useState('');
321
+ const [result, setResult] = useState('Ready');
322
+ const [busy, setBusy] = useState(false);
323
+ const parsedReportId = () => parseIntegerInput(reportId, 'Report ID', 0, 255);
324
+ const run = async (operation) => {
325
+ setBusy(true);
326
+ try {
327
+ setResult(await operation());
328
+ }
329
+ catch (cause) {
330
+ setResult(toError(cause).message);
331
+ }
332
+ finally {
333
+ setBusy(false);
334
+ }
335
+ };
336
+ return (_jsxs("div", { "data-slot": "hid-report-controls", className: "bg-muted/30 mt-2 space-y-2 rounded-md border p-2.5", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-[8rem_1fr]", children: [_jsx(Input, { "aria-label": "HID report ID", value: reportId, onChange: (event) => setReportId(event.target.value), placeholder: "Report ID", className: "h-8 font-mono text-xs" }), _jsx(Input, { "aria-label": "HID report payload", value: payload, onChange: (event) => setPayload(event.target.value), placeholder: "Hex payload, for example 01 A0 FF", className: "h-8 font-mono text-xs" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx(Button, { size: "xs", variant: "outline", disabled: busy, onClick: () => void run(async () => {
337
+ const id = parsedReportId();
338
+ const data = parseHexBytes(payload);
339
+ const sent = await hid.sendReport(device, id, data);
340
+ return sent
341
+ ? `Output ${id} · ${formatHexBytes(data)}`
342
+ : `Output ${id} failed`;
343
+ }), children: "Send output" }), _jsx(Button, { size: "xs", variant: "outline", disabled: busy, onClick: () => void run(async () => {
344
+ const id = parsedReportId();
345
+ const data = parseHexBytes(payload);
346
+ const sent = await hid.sendFeatureReport(device, id, data);
347
+ return sent
348
+ ? `Feature OUT ${id} · ${formatHexBytes(data)}`
349
+ : `Feature OUT ${id} failed`;
350
+ }), children: "Feature OUT" }), _jsx(Button, { size: "xs", variant: "outline", disabled: busy, onClick: () => void run(async () => {
351
+ const id = parsedReportId();
352
+ const data = await hid.receiveFeatureReport(device, id);
353
+ return data
354
+ ? `Feature IN ${id} · ${formatHexBytes(data)}`
355
+ : `Feature IN ${id} failed`;
356
+ }), children: "Feature IN" }), _jsx("span", { className: "text-muted-foreground ml-auto truncate font-mono text-[10px]", children: busy ? 'Working…' : result })] })] }));
357
+ }
358
+ /**
359
+ * HID device panel: chooser access, granted-device list with open/close, and
360
+ * a live hex log of incoming input reports.
361
+ */
362
+ function HidDevicePanel({ className, filters, onInputReport, ...props }) {
363
+ const hid = useMoldableHid({ onInputReport });
364
+ const requesting = hid.status === 'requesting';
365
+ const anyOpen = hid.openIds.length > 0;
366
+ return (_jsxs(HardwarePanel, { "data-slot": "hid-device-panel", "data-status": hid.status, className: className, ...props, children: [_jsxs(HardwarePanelHeader, { children: [_jsx(HardwarePanelIcon, { children: _jsx(Gamepad2, {}) }), _jsx(HardwarePanelTitle, { children: "HID" }), _jsx(CapabilityBadge, { capability: "hid" }), _jsxs(HardwarePanelAction, { children: [_jsxs(Button, { size: "sm", disabled: requesting, onClick: () => void hid.request(filters), children: [requesting ? _jsx(Spinner, {}) : null, requesting ? 'Requesting…' : 'Request device'] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Refresh devices", onClick: () => void hid.refresh(), children: _jsx(RefreshCw, {}) })] })] }), _jsxs(HardwarePanelContent, { className: "space-y-4", children: [_jsx(DeviceList, { children: hid.devices.length === 0 ? (_jsxs(DeviceListEmpty, { icon: _jsx(Gamepad2, {}), children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: "No HID devices" }), _jsx("p", { className: "text-xs", children: "Request a device to grant this app access." })] })) : (hid.devices.map((device) => {
367
+ const id = hid.deviceId(device);
368
+ const open = hid.openIds.includes(id);
369
+ const usage = describeHidUsage(device);
370
+ return (_jsx(DeviceListItem, { icon: _jsx(Gamepad2, {}), name: device.productName || 'HID device', description: `${formatHexId(device.vendorId)}:${formatHexId(device.productId)}${usage ? ` · ${usage}` : ''}`, connected: open, action: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "xs", onClick: () => void (open ? hid.close(device) : hid.open(device)), children: open ? 'Close' : 'Open' }), _jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": `Forget ${device.productName || 'HID device'}`, onClick: () => void hid.forget(device), children: _jsx(Trash2, {}) })] }), children: open ? (_jsx(HidReportControls, { device: device, hid: hid })) : null }, id));
371
+ })) }), hid.error ? (_jsx("p", { className: "text-status-error text-xs", children: hid.error.message })) : null, _jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-[10px] font-medium uppercase tracking-wider", children: "Input reports" }), anyOpen ? _jsx(LiveIndicator, {}) : null] }), _jsx(StreamLog, { entries: hid.entries, maxHeight: 200, emptyLabel: "Waiting for input reports" })] })] })] }));
372
+ }
373
+ export { HidDevicePanel };
@@ -0,0 +1,27 @@
1
+ export * from './bluetooth-panel';
2
+ export * from './camera-preview';
3
+ export * from './capability-badge';
4
+ export * from './capability-matrix';
5
+ export * from './clipboard-button';
6
+ export * from './device-list';
7
+ export * from './display-map';
8
+ export * from './haptic-button';
9
+ export * from './hardware-panel';
10
+ export * from './hid-panel';
11
+ export * from './live-indicator';
12
+ export * from './local-auth-button';
13
+ export * from './location-panel';
14
+ export * from './microphone-meter';
15
+ export * from './midi-monitor';
16
+ export * from './notification-panel';
17
+ export * from './power-panel';
18
+ export * from './readout';
19
+ export * from './screen-share-preview';
20
+ export * from './secure-storage-panel';
21
+ export * from './serial-console';
22
+ export * from './shortcut-recorder';
23
+ export * from './stream-log';
24
+ export * from './system-audio-monitor';
25
+ export * from './usb-panel';
26
+ export * from './use-capability';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,26 @@
1
+ export * from './bluetooth-panel';
2
+ export * from './camera-preview';
3
+ export * from './capability-badge';
4
+ export * from './capability-matrix';
5
+ export * from './clipboard-button';
6
+ export * from './device-list';
7
+ export * from './display-map';
8
+ export * from './haptic-button';
9
+ export * from './hardware-panel';
10
+ export * from './hid-panel';
11
+ export * from './live-indicator';
12
+ export * from './local-auth-button';
13
+ export * from './location-panel';
14
+ export * from './microphone-meter';
15
+ export * from './midi-monitor';
16
+ export * from './notification-panel';
17
+ export * from './power-panel';
18
+ export * from './readout';
19
+ export * from './screen-share-preview';
20
+ export * from './secure-storage-panel';
21
+ export * from './serial-console';
22
+ export * from './shortcut-recorder';
23
+ export * from './stream-log';
24
+ export * from './system-audio-monitor';
25
+ export * from './usb-panel';
26
+ export * from './use-capability';
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import { type VariantProps } from 'class-variance-authority';
3
+ declare const liveIndicatorVariants: (props?: ({
4
+ variant?: "live" | "rec" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ export interface LiveIndicatorProps extends React.ComponentProps<'span'>, VariantProps<typeof liveIndicatorVariants> {
7
+ label?: React.ReactNode;
8
+ }
9
+ /**
10
+ * Pulsing activity chip. `live` reads on app surfaces; `rec` is styled for
11
+ * overlaying dark media wells (video, screen share).
12
+ */
13
+ declare function LiveIndicator({ label, variant, className, ...props }: LiveIndicatorProps): import("react/jsx-runtime").JSX.Element;
14
+ export { LiveIndicator };
15
+ //# sourceMappingURL=live-indicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/live-indicator.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,KAAK,YAAY,EAAO,MAAM,0BAA0B,CAAA;AAEjE,QAAA,MAAM,qBAAqB;;8EAW1B,CAAA;AAOD,MAAM,WAAW,kBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAClC,YAAY,CAAC,OAAO,qBAAqB,CAAC;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB;AAED;;;GAGG;AACH,iBAAS,aAAa,CAAC,EACrB,KAAK,EACL,OAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,kBAAkB,2CA0BpB;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../../lib/utils';
4
+ import { cva } from 'class-variance-authority';
5
+ const liveIndicatorVariants = cva('inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-widest', {
6
+ variants: {
7
+ variant: {
8
+ live: 'border-border bg-background/80 text-foreground backdrop-blur-sm',
9
+ rec: 'border-white/15 bg-black/60 text-white backdrop-blur-sm',
10
+ },
11
+ },
12
+ defaultVariants: { variant: 'live' },
13
+ });
14
+ const DOT_CLASS = {
15
+ live: 'bg-status-running',
16
+ rec: 'bg-destructive',
17
+ };
18
+ /**
19
+ * Pulsing activity chip. `live` reads on app surfaces; `rec` is styled for
20
+ * overlaying dark media wells (video, screen share).
21
+ */
22
+ function LiveIndicator({ label, variant = 'live', className, ...props }) {
23
+ const tone = variant ?? 'live';
24
+ return (_jsxs("span", { "data-slot": "live-indicator", "data-variant": tone, className: cn(liveIndicatorVariants({ variant: tone }), className), ...props, children: [_jsxs("span", { className: "relative flex size-1.5 shrink-0", children: [_jsx("span", { className: cn('absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:animate-none', DOT_CLASS[tone]) }), _jsx("span", { className: cn('relative inline-flex size-1.5 rounded-full', DOT_CLASS[tone]) })] }), label ?? (tone === 'rec' ? 'Rec' : 'Live')] }));
25
+ }
26
+ export { LiveIndicator };
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { type MoldableLocalAuthenticationAvailability, type MoldableLocalAuthenticationPolicy, type MoldableLocalAuthenticationResult } from '../../lib/native-hardware';
3
+ import { Button } from '../ui/button';
4
+ export type MoldableLocalAuthStatus = 'idle' | 'authenticating' | 'success' | 'failed';
5
+ /**
6
+ * Local (device-owner / biometric) authentication state machine behind
7
+ * `LocalAuthButton`. `authenticate` resolves the host outcome, or `null` when
8
+ * the bridge call itself failed or the result arrived after unmount.
9
+ */
10
+ export declare function useMoldableLocalAuth(): {
11
+ availability: MoldableLocalAuthenticationAvailability | null;
12
+ status: MoldableLocalAuthStatus;
13
+ outcome: MoldableLocalAuthenticationResult | null;
14
+ error: Error | null;
15
+ authenticate: (reason?: string, policy?: MoldableLocalAuthenticationPolicy) => Promise<MoldableLocalAuthenticationResult | null>;
16
+ reset: () => void;
17
+ };
18
+ export interface LocalAuthButtonProps extends React.ComponentProps<typeof Button> {
19
+ /** Shown by the OS in the authentication prompt. */
20
+ reason?: string;
21
+ /** Defaults to the host's `device-owner` policy. */
22
+ policy?: MoldableLocalAuthenticationPolicy;
23
+ /** Called with the host outcome after each attempt. */
24
+ onResult?: (outcome: MoldableLocalAuthenticationResult) => void;
25
+ }
26
+ /**
27
+ * A `Button` that asks the OS to verify the current user. The idle label
28
+ * follows the available method (Touch ID when biometrics are available),
29
+ * and success/failure states resolve back to idle after ~2s.
30
+ */
31
+ declare function LocalAuthButton({ reason, policy, onResult, onClick, className, disabled, children, ...props }: LocalAuthButtonProps): import("react/jsx-runtime").JSX.Element;
32
+ export { LocalAuthButton };
33
+ //# sourceMappingURL=local-auth-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-auth-button.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/local-auth-button.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,KAAK,uCAAuC,EAC5C,KAAK,iCAAiC,EACtC,KAAK,iCAAiC,EAGvC,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAGrC,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,gBAAgB,GAChB,SAAS,GACT,QAAQ,CAAA;AAIZ;;;;GAIG;AACH,wBAAgB,oBAAoB;;;;;4BA6BtB,MAAM,WACL,iCAAiC;;EAwC/C;AAkBD,MAAM,WAAW,oBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC;IAC3C,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,MAAM,CAAC,EAAE,iCAAiC,CAAA;IAC1C,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,iCAAiC,KAAK,IAAI,CAAA;CAChE;AAED;;;;GAIG;AACH,iBAAS,eAAe,CAAC,EACvB,MAAuB,EACvB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,oBAAoB,2CA8DtB;AAED,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Check, Fingerprint, ShieldX } from 'lucide-react';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { authenticateMoldableLocalUser, getMoldableLocalAuthenticationAvailability, } from '../../lib/native-hardware';
6
+ import { cn } from '../../lib/utils';
7
+ import { Button } from '../ui/button';
8
+ import { Spinner } from '../ui/spinner';
9
+ const DEFAULT_REASON = 'Confirm your identity';
10
+ /**
11
+ * Local (device-owner / biometric) authentication state machine behind
12
+ * `LocalAuthButton`. `authenticate` resolves the host outcome, or `null` when
13
+ * the bridge call itself failed or the result arrived after unmount.
14
+ */
15
+ export function useMoldableLocalAuth() {
16
+ const [availability, setAvailability] = useState(null);
17
+ const [status, setStatus] = useState('idle');
18
+ const [outcome, setOutcome] = useState(null);
19
+ const [error, setError] = useState(null);
20
+ const tokenRef = useRef(0);
21
+ const availabilityGenerationRef = useRef(0);
22
+ useEffect(() => {
23
+ const generation = ++availabilityGenerationRef.current;
24
+ getMoldableLocalAuthenticationAvailability()
25
+ .then((value) => {
26
+ if (availabilityGenerationRef.current === generation) {
27
+ setAvailability(value);
28
+ }
29
+ })
30
+ .catch(() => {
31
+ // Availability detail is best-effort outside Moldable.
32
+ });
33
+ const liveGeneration = availabilityGenerationRef;
34
+ return () => {
35
+ liveGeneration.current++;
36
+ };
37
+ }, []);
38
+ const authenticate = useCallback(async (reason = DEFAULT_REASON, policy) => {
39
+ const token = ++tokenRef.current;
40
+ setStatus('authenticating');
41
+ setOutcome(null);
42
+ setError(null);
43
+ try {
44
+ const result = await authenticateMoldableLocalUser(reason, policy ? { policy } : {});
45
+ if (tokenRef.current !== token)
46
+ return null;
47
+ setOutcome(result);
48
+ setStatus(result.outcome === 'authenticated' ? 'success' : 'failed');
49
+ return result;
50
+ }
51
+ catch (cause) {
52
+ if (tokenRef.current !== token)
53
+ return null;
54
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
55
+ setStatus('failed');
56
+ return null;
57
+ }
58
+ }, []);
59
+ const reset = useCallback(() => {
60
+ tokenRef.current++;
61
+ setStatus('idle');
62
+ setOutcome(null);
63
+ setError(null);
64
+ }, []);
65
+ useEffect(() => () => {
66
+ tokenRef.current++;
67
+ }, []);
68
+ return { availability, status, outcome, error, authenticate, reset };
69
+ }
70
+ const LOCAL_AUTH_SHAKE_STYLE = `
71
+ @keyframes moldable-local-auth-shake {
72
+ 0%, 100% { transform: translateX(0); }
73
+ 20% { transform: translateX(-3px); }
74
+ 40% { transform: translateX(3px); }
75
+ 60% { transform: translateX(-2px); }
76
+ 80% { transform: translateX(2px); }
77
+ }
78
+ [data-slot='local-auth-button'][data-status='failed'] {
79
+ animation: moldable-local-auth-shake 320ms ease-in-out;
80
+ }
81
+ @media (prefers-reduced-motion: reduce) {
82
+ [data-slot='local-auth-button'][data-status='failed'] { animation: none; }
83
+ }
84
+ `;
85
+ /**
86
+ * A `Button` that asks the OS to verify the current user. The idle label
87
+ * follows the available method (Touch ID when biometrics are available),
88
+ * and success/failure states resolve back to idle after ~2s.
89
+ */
90
+ function LocalAuthButton({ reason = DEFAULT_REASON, policy, onResult, onClick, className, disabled, children, ...props }) {
91
+ const auth = useMoldableLocalAuth();
92
+ const onResultRef = useRef(onResult);
93
+ onResultRef.current = onResult;
94
+ const { status, reset } = auth;
95
+ useEffect(() => {
96
+ if (status !== 'success' && status !== 'failed')
97
+ return;
98
+ const timeout = window.setTimeout(reset, 2000);
99
+ return () => window.clearTimeout(timeout);
100
+ }, [status, reset]);
101
+ const biometric = auth.availability?.available === true &&
102
+ auth.availability.method === 'biometric';
103
+ const idleLabel = biometric ? 'Touch ID' : 'Authenticate';
104
+ const handleClick = (event) => {
105
+ onClick?.(event);
106
+ if (event.defaultPrevented || status === 'authenticating')
107
+ return;
108
+ void auth.authenticate(reason, policy).then((result) => {
109
+ if (result)
110
+ onResultRef.current?.(result);
111
+ });
112
+ };
113
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: LOCAL_AUTH_SHAKE_STYLE }), _jsx(Button, { "data-slot": "local-auth-button", "data-status": status, className: cn('motion-reduce:animate-none', status === 'success' && 'text-success', className), disabled: disabled || status === 'authenticating', onClick: handleClick, ...props, children: status === 'authenticating' ? (_jsxs(_Fragment, { children: [_jsx(Spinner, {}), " Authenticating\u2026"] })) : status === 'success' ? (_jsxs(_Fragment, { children: [_jsx(Check, {}), " Verified"] })) : status === 'failed' ? (_jsxs(_Fragment, { children: [_jsx(ShieldX, {}), " Failed"] })) : ((children ?? (_jsxs(_Fragment, { children: [_jsx(Fingerprint, {}), " ", idleLabel] })))) })] }));
114
+ }
115
+ export { LocalAuthButton };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { type MoldableLocationAuthorizationState, type MoldableLocationOptions, type MoldableLocationResult } from '../../lib/native-hardware';
3
+ export type MoldableLocationStatus = 'idle' | 'locating' | 'success' | 'denied' | 'error';
4
+ /**
5
+ * One-shot location state machine behind `LocationPanel`. Use directly to
6
+ * build custom location UI on the same authorization/error handling.
7
+ */
8
+ export declare function useMoldableLocation(options?: MoldableLocationOptions): {
9
+ status: MoldableLocationStatus;
10
+ position: MoldableLocationResult | null;
11
+ authorization: MoldableLocationAuthorizationState | null;
12
+ error: Error | null;
13
+ locate: () => Promise<MoldableLocationResult | null | undefined>;
14
+ };
15
+ declare function formatCoordinate(value: number, axis: 'lat' | 'lng'): string;
16
+ export interface LocationPanelProps extends React.ComponentProps<'div'> {
17
+ /** Location request options (high accuracy is on by default). */
18
+ options?: MoldableLocationOptions;
19
+ /** Request a fix on mount. */
20
+ autoLocate?: boolean;
21
+ onLocation?: (position: MoldableLocationResult) => void;
22
+ }
23
+ /**
24
+ * Current-position panel: authorization state, one-shot fix, mono coordinate
25
+ * readout with copy, accuracy/altitude/speed details.
26
+ */
27
+ declare function LocationPanel({ className, options, autoLocate, onLocation, ...props }: LocationPanelProps): import("react/jsx-runtime").JSX.Element;
28
+ export { LocationPanel, formatCoordinate };
29
+ //# sourceMappingURL=location-panel.d.ts.map