@moldable-ai/ui 0.2.18 → 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 (228) 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/hardware/bluetooth-panel-helpers.d.ts +9 -0
  17. package/dist/components/hardware/bluetooth-panel-helpers.d.ts.map +1 -0
  18. package/dist/components/hardware/bluetooth-panel-helpers.js +37 -0
  19. package/dist/components/hardware/bluetooth-panel.d.ts +56 -0
  20. package/dist/components/hardware/bluetooth-panel.d.ts.map +1 -0
  21. package/dist/components/hardware/bluetooth-panel.js +470 -0
  22. package/dist/components/hardware/camera-preview.d.ts +39 -0
  23. package/dist/components/hardware/camera-preview.d.ts.map +1 -0
  24. package/dist/components/hardware/camera-preview.js +228 -0
  25. package/dist/components/hardware/capability-badge.d.ts +23 -0
  26. package/dist/components/hardware/capability-badge.d.ts.map +1 -0
  27. package/dist/components/hardware/capability-badge.js +39 -0
  28. package/dist/components/hardware/capability-matrix.d.ts +14 -0
  29. package/dist/components/hardware/capability-matrix.d.ts.map +1 -0
  30. package/dist/components/hardware/capability-matrix.js +97 -0
  31. package/dist/components/hardware/clipboard-button.d.ts +26 -0
  32. package/dist/components/hardware/clipboard-button.d.ts.map +1 -0
  33. package/dist/components/hardware/clipboard-button.js +114 -0
  34. package/dist/components/hardware/device-io-controls.d.ts +4 -0
  35. package/dist/components/hardware/device-io-controls.d.ts.map +1 -0
  36. package/dist/components/hardware/device-io-controls.js +28 -0
  37. package/dist/components/hardware/device-list.d.ts +20 -0
  38. package/dist/components/hardware/device-list.d.ts.map +1 -0
  39. package/dist/components/hardware/device-list.js +14 -0
  40. package/dist/components/hardware/display-map.d.ts +23 -0
  41. package/dist/components/hardware/display-map.d.ts.map +1 -0
  42. package/dist/components/hardware/display-map.js +114 -0
  43. package/dist/components/hardware/haptic-button.d.ts +26 -0
  44. package/dist/components/hardware/haptic-button.d.ts.map +1 -0
  45. package/dist/components/hardware/haptic-button.js +94 -0
  46. package/dist/components/hardware/hardware-panel.d.ts +11 -0
  47. package/dist/components/hardware/hardware-panel.d.ts.map +1 -0
  48. package/dist/components/hardware/hardware-panel.js +28 -0
  49. package/dist/components/hardware/hid-panel.d.ts +40 -0
  50. package/dist/components/hardware/hid-panel.d.ts.map +1 -0
  51. package/dist/components/hardware/hid-panel.js +373 -0
  52. package/dist/components/hardware/index.d.ts +27 -0
  53. package/dist/components/hardware/index.d.ts.map +1 -0
  54. package/dist/components/hardware/index.js +26 -0
  55. package/dist/components/hardware/live-indicator.d.ts +15 -0
  56. package/dist/components/hardware/live-indicator.d.ts.map +1 -0
  57. package/dist/components/hardware/live-indicator.js +26 -0
  58. package/dist/components/hardware/local-auth-button.d.ts +33 -0
  59. package/dist/components/hardware/local-auth-button.d.ts.map +1 -0
  60. package/dist/components/hardware/local-auth-button.js +115 -0
  61. package/dist/components/hardware/location-panel.d.ts +29 -0
  62. package/dist/components/hardware/location-panel.d.ts.map +1 -0
  63. package/dist/components/hardware/location-panel.js +154 -0
  64. package/dist/components/hardware/microphone-meter.d.ts +35 -0
  65. package/dist/components/hardware/microphone-meter.d.ts.map +1 -0
  66. package/dist/components/hardware/microphone-meter.js +244 -0
  67. package/dist/components/hardware/midi-monitor.d.ts +54 -0
  68. package/dist/components/hardware/midi-monitor.d.ts.map +1 -0
  69. package/dist/components/hardware/midi-monitor.js +534 -0
  70. package/dist/components/hardware/notification-panel.d.ts +24 -0
  71. package/dist/components/hardware/notification-panel.d.ts.map +1 -0
  72. package/dist/components/hardware/notification-panel.js +124 -0
  73. package/dist/components/hardware/power-blocker-controller.d.ts +19 -0
  74. package/dist/components/hardware/power-blocker-controller.d.ts.map +1 -0
  75. package/dist/components/hardware/power-blocker-controller.js +83 -0
  76. package/dist/components/hardware/power-panel.d.ts +67 -0
  77. package/dist/components/hardware/power-panel.d.ts.map +1 -0
  78. package/dist/components/hardware/power-panel.js +309 -0
  79. package/dist/components/hardware/readout.d.ts +10 -0
  80. package/dist/components/hardware/readout.d.ts.map +1 -0
  81. package/dist/components/hardware/readout.js +11 -0
  82. package/dist/components/hardware/screen-share-preview.d.ts +36 -0
  83. package/dist/components/hardware/screen-share-preview.d.ts.map +1 -0
  84. package/dist/components/hardware/screen-share-preview.js +191 -0
  85. package/dist/components/hardware/secure-storage-panel.d.ts +36 -0
  86. package/dist/components/hardware/secure-storage-panel.d.ts.map +1 -0
  87. package/dist/components/hardware/secure-storage-panel.js +193 -0
  88. package/dist/components/hardware/serial-console.d.ts +64 -0
  89. package/dist/components/hardware/serial-console.d.ts.map +1 -0
  90. package/dist/components/hardware/serial-console.js +399 -0
  91. package/dist/components/hardware/shortcut-recorder.d.ts +35 -0
  92. package/dist/components/hardware/shortcut-recorder.d.ts.map +1 -0
  93. package/dist/components/hardware/shortcut-recorder.js +217 -0
  94. package/dist/components/hardware/stream-log.d.ts +35 -0
  95. package/dist/components/hardware/stream-log.d.ts.map +1 -0
  96. package/dist/components/hardware/stream-log.js +65 -0
  97. package/dist/components/hardware/system-audio-monitor.d.ts +34 -0
  98. package/dist/components/hardware/system-audio-monitor.d.ts.map +1 -0
  99. package/dist/components/hardware/system-audio-monitor.js +275 -0
  100. package/dist/components/hardware/usb-panel.d.ts +54 -0
  101. package/dist/components/hardware/usb-panel.d.ts.map +1 -0
  102. package/dist/components/hardware/usb-panel.js +363 -0
  103. package/dist/components/hardware/use-capability.d.ts +20 -0
  104. package/dist/components/hardware/use-capability.d.ts.map +1 -0
  105. package/dist/components/hardware/use-capability.js +61 -0
  106. package/dist/components/ui/input-group.d.ts +1 -1
  107. package/dist/components/ui/slider.d.ts +4 -1
  108. package/dist/components/ui/slider.d.ts.map +1 -1
  109. package/dist/components/ui/slider.js +2 -2
  110. package/dist/index.d.ts +2 -0
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +4 -0
  113. package/dist/lib/native-hardware/bluetooth-access.d.ts +14 -0
  114. package/dist/lib/native-hardware/bluetooth-access.d.ts.map +1 -0
  115. package/dist/lib/native-hardware/bluetooth-access.js +40 -0
  116. package/dist/lib/native-hardware/bluetooth-connection.d.ts +19 -0
  117. package/dist/lib/native-hardware/bluetooth-connection.d.ts.map +1 -0
  118. package/dist/lib/native-hardware/bluetooth-connection.js +88 -0
  119. package/dist/lib/native-hardware/bluetooth-gatt.d.ts +7 -0
  120. package/dist/lib/native-hardware/bluetooth-gatt.d.ts.map +1 -0
  121. package/dist/lib/native-hardware/bluetooth-gatt.js +77 -0
  122. package/dist/lib/native-hardware/bluetooth-internal.d.ts +10 -0
  123. package/dist/lib/native-hardware/bluetooth-internal.d.ts.map +1 -0
  124. package/dist/lib/native-hardware/bluetooth-internal.js +105 -0
  125. package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts +33 -0
  126. package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts.map +1 -0
  127. package/dist/lib/native-hardware/bluetooth-native-fallback.js +214 -0
  128. package/dist/lib/native-hardware/bluetooth-types.d.ts +85 -0
  129. package/dist/lib/native-hardware/bluetooth-types.d.ts.map +1 -0
  130. package/dist/lib/native-hardware/bluetooth-types.js +2 -0
  131. package/dist/lib/native-hardware/bluetooth.d.ts +7 -0
  132. package/dist/lib/native-hardware/bluetooth.d.ts.map +1 -0
  133. package/dist/lib/native-hardware/bluetooth.js +4 -0
  134. package/dist/lib/native-hardware/bridge.d.ts +23 -0
  135. package/dist/lib/native-hardware/bridge.d.ts.map +1 -0
  136. package/dist/lib/native-hardware/bridge.js +266 -0
  137. package/dist/lib/native-hardware/capabilities.d.ts +7 -0
  138. package/dist/lib/native-hardware/capabilities.d.ts.map +1 -0
  139. package/dist/lib/native-hardware/capabilities.js +95 -0
  140. package/dist/lib/native-hardware/clipboard.d.ts +11 -0
  141. package/dist/lib/native-hardware/clipboard.d.ts.map +1 -0
  142. package/dist/lib/native-hardware/clipboard.js +55 -0
  143. package/dist/lib/native-hardware/device-io-errors.d.ts +11 -0
  144. package/dist/lib/native-hardware/device-io-errors.d.ts.map +1 -0
  145. package/dist/lib/native-hardware/device-io-errors.js +82 -0
  146. package/dist/lib/native-hardware/displays.d.ts +38 -0
  147. package/dist/lib/native-hardware/displays.d.ts.map +1 -0
  148. package/dist/lib/native-hardware/displays.js +12 -0
  149. package/dist/lib/native-hardware/global-shortcuts.d.ts +86 -0
  150. package/dist/lib/native-hardware/global-shortcuts.d.ts.map +1 -0
  151. package/dist/lib/native-hardware/global-shortcuts.js +351 -0
  152. package/dist/lib/native-hardware/haptics.d.ts +25 -0
  153. package/dist/lib/native-hardware/haptics.d.ts.map +1 -0
  154. package/dist/lib/native-hardware/haptics.js +18 -0
  155. package/dist/lib/native-hardware/hid-native.d.ts +26 -0
  156. package/dist/lib/native-hardware/hid-native.d.ts.map +1 -0
  157. package/dist/lib/native-hardware/hid-native.js +229 -0
  158. package/dist/lib/native-hardware/hid.d.ts +52 -0
  159. package/dist/lib/native-hardware/hid.d.ts.map +1 -0
  160. package/dist/lib/native-hardware/hid.js +160 -0
  161. package/dist/lib/native-hardware/index.d.ts +38 -0
  162. package/dist/lib/native-hardware/index.d.ts.map +1 -0
  163. package/dist/lib/native-hardware/index.js +20 -0
  164. package/dist/lib/native-hardware/local-authentication.d.ts +32 -0
  165. package/dist/lib/native-hardware/local-authentication.d.ts.map +1 -0
  166. package/dist/lib/native-hardware/local-authentication.js +46 -0
  167. package/dist/lib/native-hardware/location.d.ts +49 -0
  168. package/dist/lib/native-hardware/location.d.ts.map +1 -0
  169. package/dist/lib/native-hardware/location.js +114 -0
  170. package/dist/lib/native-hardware/media-permissions.d.ts +28 -0
  171. package/dist/lib/native-hardware/media-permissions.d.ts.map +1 -0
  172. package/dist/lib/native-hardware/media-permissions.js +37 -0
  173. package/dist/lib/native-hardware/media.d.ts +17 -0
  174. package/dist/lib/native-hardware/media.d.ts.map +1 -0
  175. package/dist/lib/native-hardware/media.js +99 -0
  176. package/dist/lib/native-hardware/midi-access.d.ts +13 -0
  177. package/dist/lib/native-hardware/midi-access.d.ts.map +1 -0
  178. package/dist/lib/native-hardware/midi-access.js +66 -0
  179. package/dist/lib/native-hardware/midi-internal.d.ts +12 -0
  180. package/dist/lib/native-hardware/midi-internal.d.ts.map +1 -0
  181. package/dist/lib/native-hardware/midi-internal.js +88 -0
  182. package/dist/lib/native-hardware/midi-native-fallback.d.ts +27 -0
  183. package/dist/lib/native-hardware/midi-native-fallback.d.ts.map +1 -0
  184. package/dist/lib/native-hardware/midi-native-fallback.js +127 -0
  185. package/dist/lib/native-hardware/midi-ports.d.ts +6 -0
  186. package/dist/lib/native-hardware/midi-ports.d.ts.map +1 -0
  187. package/dist/lib/native-hardware/midi-ports.js +115 -0
  188. package/dist/lib/native-hardware/midi-types.d.ts +25 -0
  189. package/dist/lib/native-hardware/midi-types.d.ts.map +1 -0
  190. package/dist/lib/native-hardware/midi-types.js +1 -0
  191. package/dist/lib/native-hardware/midi.d.ts +6 -0
  192. package/dist/lib/native-hardware/midi.d.ts.map +1 -0
  193. package/dist/lib/native-hardware/midi.js +3 -0
  194. package/dist/lib/native-hardware/native-device-io.d.ts +28 -0
  195. package/dist/lib/native-hardware/native-device-io.d.ts.map +1 -0
  196. package/dist/lib/native-hardware/native-device-io.js +91 -0
  197. package/dist/lib/native-hardware/native-device-lifecycle.d.ts +13 -0
  198. package/dist/lib/native-hardware/native-device-lifecycle.d.ts.map +1 -0
  199. package/dist/lib/native-hardware/native-device-lifecycle.js +40 -0
  200. package/dist/lib/native-hardware/notifications.d.ts +38 -0
  201. package/dist/lib/native-hardware/notifications.d.ts.map +1 -0
  202. package/dist/lib/native-hardware/notifications.js +50 -0
  203. package/dist/lib/native-hardware/power-session.d.ts +113 -0
  204. package/dist/lib/native-hardware/power-session.d.ts.map +1 -0
  205. package/dist/lib/native-hardware/power-session.js +264 -0
  206. package/dist/lib/native-hardware/secure-storage.d.ts +36 -0
  207. package/dist/lib/native-hardware/secure-storage.d.ts.map +1 -0
  208. package/dist/lib/native-hardware/secure-storage.js +64 -0
  209. package/dist/lib/native-hardware/serial-native.d.ts +23 -0
  210. package/dist/lib/native-hardware/serial-native.d.ts.map +1 -0
  211. package/dist/lib/native-hardware/serial-native.js +181 -0
  212. package/dist/lib/native-hardware/serial.d.ts +76 -0
  213. package/dist/lib/native-hardware/serial.d.ts.map +1 -0
  214. package/dist/lib/native-hardware/serial.js +212 -0
  215. package/dist/lib/native-hardware/system-audio.d.ts +127 -0
  216. package/dist/lib/native-hardware/system-audio.d.ts.map +1 -0
  217. package/dist/lib/native-hardware/system-audio.js +330 -0
  218. package/dist/lib/native-hardware/types.d.ts +62 -0
  219. package/dist/lib/native-hardware/types.d.ts.map +1 -0
  220. package/dist/lib/native-hardware/types.js +27 -0
  221. package/dist/lib/native-hardware/usb-native.d.ts +39 -0
  222. package/dist/lib/native-hardware/usb-native.d.ts.map +1 -0
  223. package/dist/lib/native-hardware/usb-native.js +217 -0
  224. package/dist/lib/native-hardware/usb.d.ts +85 -0
  225. package/dist/lib/native-hardware/usb.d.ts.map +1 -0
  226. package/dist/lib/native-hardware/usb.js +222 -0
  227. package/package.json +1 -1
  228. package/src/styles/index.css +377 -0
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ export type MoldableCameraStatus = 'idle' | 'starting' | 'live' | 'muted' | 'denied' | 'error';
3
+ export interface UseMoldableCameraOptions {
4
+ /** Extra video constraints merged under the selected device. */
5
+ constraints?: MediaTrackConstraints;
6
+ onStreamChange?: (stream: MediaStream | null) => void;
7
+ }
8
+ /**
9
+ * Camera state machine behind `CameraPreview`. Use directly to build custom
10
+ * camera UI on the same permission/device/stream handling.
11
+ */
12
+ export declare function useMoldableCamera(options?: UseMoldableCameraOptions): {
13
+ status: MoldableCameraStatus;
14
+ stream: MediaStream | null;
15
+ devices: MediaDeviceInfo[];
16
+ deviceId: string | null;
17
+ error: Error | null;
18
+ start: (nextDeviceId?: string) => Promise<void>;
19
+ stop: () => void;
20
+ selectDevice: (id: string) => void;
21
+ refreshDevices: () => Promise<void>;
22
+ };
23
+ export interface CameraPreviewProps extends React.ComponentProps<'div'> {
24
+ constraints?: MediaTrackConstraints;
25
+ /** Mirror the preview horizontally (natural for user-facing cameras). */
26
+ mirrored?: boolean;
27
+ /** Start the camera on mount. */
28
+ autoStart?: boolean;
29
+ /** When set, shows a shutter button and delivers PNG snapshots. */
30
+ onSnapshot?: (blob: Blob) => void;
31
+ onStreamChange?: (stream: MediaStream | null) => void;
32
+ }
33
+ /**
34
+ * Full-bleed live camera surface: black media well, hover scrim controls,
35
+ * device switching, permission and error states, optional snapshots.
36
+ */
37
+ declare function CameraPreview({ className, constraints, mirrored, autoStart, onSnapshot, onStreamChange, ...props }: CameraPreviewProps): import("react/jsx-runtime").JSX.Element;
38
+ export { CameraPreview };
39
+ //# sourceMappingURL=camera-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-preview.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/camera-preview.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAsB9B,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,MAAM,GACN,OAAO,GACP,QAAQ,GACR,OAAO,CAAA;AAEX,MAAM,WAAW,wBAAwB;IACvC,gEAAgE;IAChE,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CACtD;AAuBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B;;;;;;2BA+C9C,MAAM;;uBA2EvB,MAAM;;EA2Bd;AAED,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACrE,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,mEAAmE;IACnE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACjC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CACtD;AAED;;;GAGG;AACH,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,WAAW,EACX,QAAe,EACf,SAAiB,EACjB,UAAU,EACV,cAAc,EACd,GAAG,KAAK,EACT,EAAE,kBAAkB,2CA+LpB;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -0,0 +1,228 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Camera, CameraOff, RefreshCw, Square } from 'lucide-react';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { NativeHardwareBridgeError, enumerateMoldableMediaDevices, getMoldableCameraStream, getMoldableMediaPermissionStatus, requestMoldableMediaPermission, stopMoldableMediaStream, } from '../../lib/native-hardware';
6
+ import { cn } from '../../lib/utils';
7
+ import { Button } from '../ui/button';
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select';
9
+ import { Spinner } from '../ui/spinner';
10
+ import { LiveIndicator } from './live-indicator';
11
+ function isPermissionDenied(error) {
12
+ if (error instanceof NativeHardwareBridgeError) {
13
+ return error.code === 'permission_denied';
14
+ }
15
+ return (error instanceof DOMException &&
16
+ (error.name === 'NotAllowedError' || error.name === 'SecurityError'));
17
+ }
18
+ function assertMediaPermissionGranted(status) {
19
+ if (status.status !== 'denied' && status.status !== 'restricted')
20
+ return;
21
+ throw new NativeHardwareBridgeError({
22
+ code: 'permission_denied',
23
+ message: status.detail ?? 'Camera access was denied.',
24
+ retryable: false,
25
+ });
26
+ }
27
+ /**
28
+ * Camera state machine behind `CameraPreview`. Use directly to build custom
29
+ * camera UI on the same permission/device/stream handling.
30
+ */
31
+ export function useMoldableCamera(options = {}) {
32
+ const [status, setStatus] = useState('idle');
33
+ const [stream, setStream] = useState(null);
34
+ const [devices, setDevices] = useState([]);
35
+ const [deviceId, setDeviceIdState] = useState(null);
36
+ const [error, setError] = useState(null);
37
+ const streamRef = useRef(null);
38
+ const deviceIdRef = useRef(null);
39
+ const tokenRef = useRef(0);
40
+ const constraintsRef = useRef(options.constraints);
41
+ const onStreamChangeRef = useRef(options.onStreamChange);
42
+ const detachTrackListenersRef = useRef(null);
43
+ useEffect(() => {
44
+ constraintsRef.current = options.constraints;
45
+ onStreamChangeRef.current = options.onStreamChange;
46
+ }, [options.constraints, options.onStreamChange]);
47
+ const dropStream = useCallback((updateState = true) => {
48
+ detachTrackListenersRef.current?.();
49
+ detachTrackListenersRef.current = null;
50
+ const previousStream = streamRef.current;
51
+ if (!previousStream)
52
+ return false;
53
+ streamRef.current = null;
54
+ stopMoldableMediaStream(previousStream);
55
+ if (updateState)
56
+ setStream(null);
57
+ onStreamChangeRef.current?.(null);
58
+ return true;
59
+ }, []);
60
+ const stop = useCallback(() => {
61
+ tokenRef.current++;
62
+ dropStream();
63
+ setStatus('idle');
64
+ }, [dropStream]);
65
+ const refreshDevices = useCallback(async () => {
66
+ try {
67
+ const all = await enumerateMoldableMediaDevices();
68
+ setDevices(all.filter((device) => device.kind === 'videoinput'));
69
+ }
70
+ catch {
71
+ // Device labels are best-effort; enumeration can fail outside Moldable.
72
+ }
73
+ }, []);
74
+ const start = useCallback(async (nextDeviceId) => {
75
+ const token = ++tokenRef.current;
76
+ dropStream();
77
+ setStatus('starting');
78
+ setError(null);
79
+ try {
80
+ // Nudge the native permission first so macOS prompts before the
81
+ // webview does. Outside Moldable this fails and the browser prompt
82
+ // covers it on getUserMedia.
83
+ try {
84
+ const permission = await getMoldableMediaPermissionStatus('camera');
85
+ assertMediaPermissionGranted(permission);
86
+ if (permission.status === 'not-determined' && permission.canRequest) {
87
+ assertMediaPermissionGranted(await requestMoldableMediaPermission('camera'));
88
+ }
89
+ }
90
+ catch (cause) {
91
+ if (isPermissionDenied(cause))
92
+ throw cause;
93
+ // Not in Moldable — proceed straight to the stream request.
94
+ }
95
+ if (tokenRef.current !== token)
96
+ return;
97
+ const target = nextDeviceId ?? deviceIdRef.current;
98
+ const media = await getMoldableCameraStream({
99
+ width: { ideal: 1920 },
100
+ height: { ideal: 1080 },
101
+ ...constraintsRef.current,
102
+ ...(target ? { deviceId: { exact: target } } : {}),
103
+ });
104
+ if (tokenRef.current !== token) {
105
+ stopMoldableMediaStream(media);
106
+ return;
107
+ }
108
+ streamRef.current = media;
109
+ const [track] = media.getVideoTracks();
110
+ if (track) {
111
+ const handleEnded = () => {
112
+ if (tokenRef.current !== token)
113
+ return;
114
+ tokenRef.current++;
115
+ dropStream();
116
+ setStatus('idle');
117
+ };
118
+ const handleMute = () => {
119
+ if (tokenRef.current === token)
120
+ setStatus('muted');
121
+ };
122
+ const handleUnmute = () => {
123
+ if (tokenRef.current === token)
124
+ setStatus('live');
125
+ };
126
+ track.addEventListener('ended', handleEnded, { once: true });
127
+ track.addEventListener('mute', handleMute);
128
+ track.addEventListener('unmute', handleUnmute);
129
+ detachTrackListenersRef.current = () => {
130
+ track.removeEventListener('ended', handleEnded);
131
+ track.removeEventListener('mute', handleMute);
132
+ track.removeEventListener('unmute', handleUnmute);
133
+ };
134
+ }
135
+ setStream(media);
136
+ if (nextDeviceId !== undefined) {
137
+ deviceIdRef.current = nextDeviceId;
138
+ setDeviceIdState(nextDeviceId);
139
+ }
140
+ setStatus('live');
141
+ onStreamChangeRef.current?.(media);
142
+ void refreshDevices();
143
+ }
144
+ catch (cause) {
145
+ if (tokenRef.current !== token)
146
+ return;
147
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
148
+ setStatus(isPermissionDenied(cause) ? 'denied' : 'error');
149
+ }
150
+ }, [dropStream, refreshDevices]);
151
+ const selectDevice = useCallback((id) => {
152
+ deviceIdRef.current = id;
153
+ setDeviceIdState(id);
154
+ if (streamRef.current)
155
+ void start(id);
156
+ }, [start]);
157
+ useEffect(() => () => {
158
+ tokenRef.current++;
159
+ dropStream(false);
160
+ }, [dropStream]);
161
+ return {
162
+ status,
163
+ stream,
164
+ devices,
165
+ deviceId,
166
+ error,
167
+ start,
168
+ stop,
169
+ selectDevice,
170
+ refreshDevices,
171
+ };
172
+ }
173
+ /**
174
+ * Full-bleed live camera surface: black media well, hover scrim controls,
175
+ * device switching, permission and error states, optional snapshots.
176
+ */
177
+ function CameraPreview({ className, constraints, mirrored = true, autoStart = false, onSnapshot, onStreamChange, ...props }) {
178
+ const camera = useMoldableCamera({ constraints, onStreamChange });
179
+ const videoRef = useRef(null);
180
+ const [flash, setFlash] = useState(false);
181
+ const autoStartedRef = useRef(false);
182
+ useEffect(() => {
183
+ if (autoStart && !autoStartedRef.current) {
184
+ autoStartedRef.current = true;
185
+ void camera.start();
186
+ }
187
+ }, [autoStart, camera]);
188
+ useEffect(() => {
189
+ const video = videoRef.current;
190
+ if (!video)
191
+ return;
192
+ video.srcObject = camera.stream;
193
+ if (camera.stream) {
194
+ void video.play().catch(() => {
195
+ // Autoplay rejections resolve once the stream attaches.
196
+ });
197
+ }
198
+ }, [camera.stream]);
199
+ const capture = () => {
200
+ const video = videoRef.current;
201
+ if (!video || !onSnapshot || video.videoWidth === 0)
202
+ return;
203
+ const canvas = document.createElement('canvas');
204
+ canvas.width = video.videoWidth;
205
+ canvas.height = video.videoHeight;
206
+ const context = canvas.getContext('2d');
207
+ if (!context)
208
+ return;
209
+ if (mirrored) {
210
+ context.translate(canvas.width, 0);
211
+ context.scale(-1, 1);
212
+ }
213
+ context.drawImage(video, 0, 0);
214
+ setFlash(true);
215
+ window.setTimeout(() => setFlash(false), 160);
216
+ canvas.toBlob((blob) => {
217
+ if (blob)
218
+ onSnapshot(blob);
219
+ }, 'image/png');
220
+ };
221
+ const active = camera.status === 'live' || camera.status === 'muted';
222
+ const settings = active
223
+ ? camera.stream?.getVideoTracks()[0]?.getSettings()
224
+ : undefined;
225
+ return (_jsxs("div", { "data-slot": "camera-preview", "data-status": camera.status, className: cn('group relative isolate aspect-video w-full overflow-hidden rounded-xl border', active ? 'bg-black' : 'bg-muted/30', className), ...props, children: [_jsx("video", { ref: videoRef, muted: true, playsInline: true, className: cn('absolute inset-0 size-full object-cover', mirrored && '-scale-x-100', !active && 'invisible') }), _jsx("div", { "aria-hidden": true, className: "pointer-events-none absolute inset-0 z-30 bg-white transition-opacity duration-150", style: { opacity: flash ? 0.9 : 0 } }), active ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute left-3 top-3 z-20", children: _jsx(LiveIndicator, { variant: "rec", label: camera.status === 'muted' ? 'Paused' : 'Live' }) }), settings?.width && settings.height ? (_jsxs("div", { className: "absolute right-3 top-3 z-20 rounded-full border border-white/15 bg-black/60 px-2 py-0.5 font-mono text-[10px] tabular-nums text-white/90 backdrop-blur-sm", children: [settings.width, "\u00D7", settings.height] })) : null, _jsxs("div", { className: "absolute inset-x-0 bottom-0 z-20 flex items-end justify-between gap-3 bg-gradient-to-t from-black/70 via-black/25 to-transparent p-3 pt-10 opacity-0 transition-opacity duration-200 focus-within:opacity-100 group-hover:opacity-100", children: [_jsx("div", { className: "min-w-0", children: camera.devices.length > 1 ? (_jsxs(Select, { value: camera.deviceId ?? undefined, onValueChange: camera.selectDevice, children: [_jsx(SelectTrigger, { size: "sm", className: "h-7 w-auto max-w-56 border-white/15 bg-white/10 text-xs text-white backdrop-blur-md hover:bg-white/15 [&_svg]:text-white/70", children: _jsx(SelectValue, { placeholder: "Camera" }) }), _jsx(SelectContent, { children: camera.devices.map((device, index) => (_jsx(SelectItem, { value: device.deviceId, children: device.label || `Camera ${index + 1}` }, device.deviceId))) })] })) : null }), _jsxs("div", { className: "flex items-center gap-2", children: [onSnapshot ? (_jsx("button", { type: "button", onClick: capture, "aria-label": "Take snapshot", className: "size-9 rounded-full border-2 border-white/40 bg-white shadow-md transition-transform duration-150 hover:scale-105 active:scale-95 motion-reduce:transition-none" })) : null, _jsx(Button, { variant: "secondary", size: "icon-sm", "aria-label": "Stop camera", onClick: camera.stop, className: "border border-white/15 bg-white/10 text-white backdrop-blur-md hover:bg-white/20", children: _jsx(Square, { className: "fill-current" }) })] })] })] })) : (_jsx("div", { className: "animate-in fade-in absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 p-6 text-center duration-300 motion-reduce:animate-none", children: camera.status === 'starting' ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "text-muted-foreground" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Starting camera\u2026" })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground bg-background shadow-xs flex size-10 items-center justify-center rounded-lg border [&_svg]:size-5", children: camera.status === 'idle' ? _jsx(Camera, {}) : _jsx(CameraOff, {}) }), camera.status === 'idle' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Camera is off" }), _jsxs(Button, { size: "sm", onClick: () => void camera.start(), children: [_jsx(Camera, {}), " Start camera"] })] })) : camera.status === 'denied' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Camera access denied" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: "Enable camera access for Moldable in System Settings, then try again." }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void camera.start(), children: [_jsx(RefreshCw, {}), " Try again"] })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Camera unavailable" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: camera.error?.message ??
226
+ 'The camera could not be started.' }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void camera.start(), children: [_jsx(RefreshCw, {}), " Try again"] })] }))] })) }))] }));
227
+ }
228
+ export { CameraPreview };
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import type { NativeHardwareCapabilityId } from '../../lib/native-hardware';
3
+ import { type MoldableCapabilityStatus } from './use-capability';
4
+ declare function CapabilityDot({ status, className, ...props }: React.ComponentProps<'span'> & {
5
+ status: MoldableCapabilityStatus;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ interface CapabilityBadgeBaseProps extends React.ComponentProps<'span'> {
8
+ /** Override the automatic status label. */
9
+ label?: React.ReactNode;
10
+ }
11
+ export interface CapabilityBadgeProps extends CapabilityBadgeBaseProps {
12
+ /** Self-manage: fetch the manifest and show this capability's support. */
13
+ capability?: NativeHardwareCapabilityId;
14
+ /** Controlled: render a specific status directly. */
15
+ status?: MoldableCapabilityStatus;
16
+ }
17
+ /**
18
+ * Support-status pill. Pass `capability` to let it fetch the manifest itself,
19
+ * or `status` to control it.
20
+ */
21
+ declare function CapabilityBadge({ capability, status, ...props }: CapabilityBadgeProps): import("react/jsx-runtime").JSX.Element;
22
+ export { CapabilityBadge, CapabilityDot };
23
+ //# sourceMappingURL=capability-badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-badge.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/capability-badge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AAE3E,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,kBAAkB,CAAA;AAkBzB,iBAAS,aAAa,CAAC,EACrB,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG;IAAE,MAAM,EAAE,wBAAwB,CAAA;CAAE,2CAcrE;AAED,UAAU,wBAAyB,SAAQ,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;IACrE,2CAA2C;IAC3C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB;AAgCD,MAAM,WAAW,oBAAqB,SAAQ,wBAAwB;IACpE,0EAA0E;IAC1E,UAAU,CAAC,EAAE,0BAA0B,CAAA;IACvC,qDAAqD;IACrD,MAAM,CAAC,EAAE,wBAAwB,CAAA;CAClC;AAED;;;GAGG;AACH,iBAAS,eAAe,CAAC,EACvB,UAAU,EACV,MAAM,EACN,GAAG,KAAK,EACT,EAAE,oBAAoB,2CAKtB;AAED,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,CAAA"}
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../../lib/utils';
4
+ import { useMoldableCapability, } from './use-capability';
5
+ const STATUS_LABEL = {
6
+ checking: 'Checking',
7
+ supported: 'Supported',
8
+ partial: 'Partial',
9
+ unsupported: 'Unsupported',
10
+ unavailable: 'Unavailable',
11
+ };
12
+ const STATUS_DOT = {
13
+ checking: 'bg-muted-foreground/40 animate-pulse motion-reduce:animate-none',
14
+ supported: 'bg-status-running',
15
+ partial: 'bg-status-pending',
16
+ unsupported: 'bg-status-error',
17
+ unavailable: 'bg-status-stopped',
18
+ };
19
+ function CapabilityDot({ status, className, ...props }) {
20
+ return (_jsx("span", { "data-slot": "capability-dot", "data-status": status, "aria-hidden": true, className: cn('size-1.5 shrink-0 rounded-full', STATUS_DOT[status], className), ...props }));
21
+ }
22
+ function CapabilityBadgeBase({ status, label, className, ...props }) {
23
+ return (_jsxs("span", { "data-slot": "capability-badge", "data-status": status, className: cn('text-muted-foreground inline-flex w-fit shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border px-2 py-0.5 text-[11px] font-medium', className), ...props, children: [_jsx(CapabilityDot, { status: status }), label ?? STATUS_LABEL[status]] }));
24
+ }
25
+ function ManagedCapabilityBadge({ capability, ...props }) {
26
+ const { status } = useMoldableCapability(capability);
27
+ return _jsx(CapabilityBadgeBase, { status: status, ...props });
28
+ }
29
+ /**
30
+ * Support-status pill. Pass `capability` to let it fetch the manifest itself,
31
+ * or `status` to control it.
32
+ */
33
+ function CapabilityBadge({ capability, status, ...props }) {
34
+ if (capability && status === undefined) {
35
+ return _jsx(ManagedCapabilityBadge, { capability: capability, ...props });
36
+ }
37
+ return _jsx(CapabilityBadgeBase, { status: status ?? 'checking', ...props });
38
+ }
39
+ export { CapabilityBadge, CapabilityDot };
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import { type NativeHardwareCapabilityId } from '../../lib/native-hardware';
3
+ export interface CapabilityMatrixProps extends React.ComponentProps<'div'> {
4
+ /** Subset of capabilities to show; defaults to every known capability. */
5
+ capabilities?: NativeHardwareCapabilityId[];
6
+ }
7
+ /**
8
+ * Responsive support grid for every native capability: icon, humanized name,
9
+ * status dot, support level, and transport. Renders skeleton cells while the
10
+ * manifest loads and a quiet note when it is unavailable outside Moldable.
11
+ */
12
+ declare function CapabilityMatrix({ capabilities, className, ...props }: CapabilityMatrixProps): import("react/jsx-runtime").JSX.Element;
13
+ export { CapabilityMatrix };
14
+ //# sourceMappingURL=capability-matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-matrix.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/capability-matrix.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAEL,KAAK,0BAA0B,EAEhC,MAAM,2BAA2B,CAAA;AAyDlC,MAAM,WAAW,qBAAsB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACxE,0EAA0E;IAC1E,YAAY,CAAC,EAAE,0BAA0B,EAAE,CAAA;CAC5C;AAED;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EACxB,YAAY,EACZ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,qBAAqB,2CAgGvB;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Activity, BatteryCharging, Bell, Bluetooth, Cable, Camera, Clipboard, ClipboardCopy, Fingerprint, Gamepad2, KeyRound, Keyboard, MapPin, Mic, Monitor, MonitorUp, Moon, Music, Timer, Usb, Vibrate, Volume2, } from 'lucide-react';
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import { NATIVE_HARDWARE_CAPABILITY_IDS, } from '../../lib/native-hardware';
6
+ import { cn } from '../../lib/utils';
7
+ import { Skeleton } from '../ui/skeleton';
8
+ import { CapabilityDot } from './capability-badge';
9
+ import { loadMoldableCapabilities, } from './use-capability';
10
+ const CAPABILITY_ICON = {
11
+ camera: Camera,
12
+ microphone: Mic,
13
+ 'screen-capture': MonitorUp,
14
+ 'system-audio': Volume2,
15
+ location: MapPin,
16
+ 'clipboard-read': Clipboard,
17
+ 'clipboard-write': ClipboardCopy,
18
+ notifications: Bell,
19
+ displays: Monitor,
20
+ 'global-shortcuts': Keyboard,
21
+ 'power-monitor': BatteryCharging,
22
+ 'idle-state': Timer,
23
+ 'session-events': Activity,
24
+ 'sleep-blocker': Moon,
25
+ 'local-authentication': Fingerprint,
26
+ haptics: Vibrate,
27
+ 'secure-storage': KeyRound,
28
+ usb: Usb,
29
+ hid: Gamepad2,
30
+ serial: Cable,
31
+ midi: Music,
32
+ bluetooth: Bluetooth,
33
+ };
34
+ const SPECIAL_NAME = {
35
+ usb: 'USB',
36
+ hid: 'HID',
37
+ midi: 'MIDI',
38
+ };
39
+ const SUPPORT_LABEL = {
40
+ checking: 'Checking',
41
+ supported: 'Supported',
42
+ partial: 'Partial',
43
+ unsupported: 'Unsupported',
44
+ unavailable: 'Unavailable',
45
+ };
46
+ function humanizeCapabilityId(id) {
47
+ const special = SPECIAL_NAME[id];
48
+ if (special)
49
+ return special;
50
+ return id
51
+ .split('-')
52
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
53
+ .join(' ');
54
+ }
55
+ /**
56
+ * Responsive support grid for every native capability: icon, humanized name,
57
+ * status dot, support level, and transport. Renders skeleton cells while the
58
+ * manifest loads and a quiet note when it is unavailable outside Moldable.
59
+ */
60
+ function CapabilityMatrix({ capabilities, className, ...props }) {
61
+ const [manifest, setManifest] = useState(null);
62
+ const [checking, setChecking] = useState(true);
63
+ const generationRef = useRef(0);
64
+ useEffect(() => {
65
+ const generation = ++generationRef.current;
66
+ loadMoldableCapabilities()
67
+ .then((value) => {
68
+ if (generationRef.current !== generation)
69
+ return;
70
+ setManifest(value);
71
+ setChecking(false);
72
+ })
73
+ .catch(() => {
74
+ if (generationRef.current !== generation)
75
+ return;
76
+ setManifest(null);
77
+ setChecking(false);
78
+ });
79
+ const liveGeneration = generationRef;
80
+ return () => {
81
+ liveGeneration.current++;
82
+ };
83
+ }, []);
84
+ const ids = capabilities ?? NATIVE_HARDWARE_CAPABILITY_IDS;
85
+ const unavailable = !checking && manifest === null;
86
+ return (_jsxs("div", { "data-slot": "capability-matrix", "data-status": checking ? 'checking' : unavailable ? 'unavailable' : 'ready', className: cn('grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4', className), ...props, children: [ids.map((id, index) => {
87
+ const Icon = CAPABILITY_ICON[id];
88
+ const descriptor = manifest?.capabilities[id] ?? null;
89
+ const status = checking
90
+ ? 'checking'
91
+ : descriptor
92
+ ? descriptor.support
93
+ : 'unavailable';
94
+ return (_jsx("div", { "data-slot": "capability-matrix-cell", "data-capability": id, "data-status": status, className: "animate-in fade-in slide-in-from-bottom-1 bg-card rounded-lg border p-3 duration-300 [animation-fill-mode:backwards] motion-reduce:animate-none", style: { animationDelay: `${index * 25}ms` }, children: checking ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-3.5 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/2" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Icon, { className: "text-muted-foreground size-3.5 shrink-0" }), _jsx("span", { className: "truncate text-xs font-medium", children: humanizeCapabilityId(id) }), _jsx(CapabilityDot, { status: status, className: "ml-auto" })] }), _jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground text-[10px]", children: SUPPORT_LABEL[status] }), descriptor ? (_jsx("span", { className: "text-muted-foreground/70 truncate font-mono text-[10px]", children: descriptor.transport.name })) : null] })] })) }, id));
95
+ }), unavailable ? (_jsx("p", { "data-slot": "capability-matrix-note", className: "text-muted-foreground/70 col-span-full text-center text-xs", children: "Native capabilities require the Moldable desktop app." })) : null] }));
96
+ }
97
+ export { CapabilityMatrix };
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+ import { Button } from '../ui/button';
3
+ /**
4
+ * Clipboard state machine behind `ClipboardCopyButton`. Prefers the Moldable
5
+ * bridge helpers and falls back to the plain web Clipboard API, so it never
6
+ * throws — writes report success as a boolean and reads report `null`.
7
+ */
8
+ export declare function useMoldableClipboard(): {
9
+ writeText: (text: string) => Promise<boolean>;
10
+ readText: () => Promise<string | null>;
11
+ lastRead: string | null;
12
+ error: Error | null;
13
+ };
14
+ export interface ClipboardCopyButtonProps extends Omit<React.ComponentProps<typeof Button>, 'value'> {
15
+ /** Text to copy, or a resolver invoked at click time. */
16
+ value: string | (() => string | Promise<string>);
17
+ onCopied?: (text: string) => void;
18
+ onCopyError?: (error: Error) => void;
19
+ }
20
+ /**
21
+ * Copy-to-clipboard button: resolves `value`, writes it, and morphs the copy
22
+ * icon into a success check for a moment.
23
+ */
24
+ declare function ClipboardCopyButton({ value, onCopied, onCopyError, variant, size, onClick, children, ...props }: ClipboardCopyButtonProps): import("react/jsx-runtime").JSX.Element;
25
+ export { ClipboardCopyButton };
26
+ //# sourceMappingURL=clipboard-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-button.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/clipboard-button.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC;;;;GAIG;AACH,wBAAgB,oBAAoB;sBAIS,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;oBAsB7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;;;EAyB9D;AAED,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1D,yDAAyD;IACzD,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IAChD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACrC;AAED;;;GAGG;AACH,iBAAS,mBAAmB,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,WAAW,EACX,OAAiB,EACjB,IAAgB,EAChB,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,wBAAwB,2CAqE1B;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
@@ -0,0 +1,114 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Check, Copy } from 'lucide-react';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { readMoldableClipboardText, writeMoldableClipboardText, } from '../../lib/native-hardware';
6
+ import { Button } from '../ui/button';
7
+ /**
8
+ * Clipboard state machine behind `ClipboardCopyButton`. Prefers the Moldable
9
+ * bridge helpers and falls back to the plain web Clipboard API, so it never
10
+ * throws — writes report success as a boolean and reads report `null`.
11
+ */
12
+ export function useMoldableClipboard() {
13
+ const [lastRead, setLastRead] = useState(null);
14
+ const [error, setError] = useState(null);
15
+ const writeText = useCallback(async (text) => {
16
+ setError(null);
17
+ try {
18
+ await writeMoldableClipboardText(text);
19
+ return true;
20
+ }
21
+ catch (nativeCause) {
22
+ try {
23
+ await navigator.clipboard.writeText(text);
24
+ return true;
25
+ }
26
+ catch (browserCause) {
27
+ setError(browserCause instanceof Error
28
+ ? browserCause
29
+ : nativeCause instanceof Error
30
+ ? nativeCause
31
+ : new Error('Could not write clipboard text.'));
32
+ return false;
33
+ }
34
+ }
35
+ }, []);
36
+ const readText = useCallback(async () => {
37
+ setError(null);
38
+ let text;
39
+ try {
40
+ text = await readMoldableClipboardText();
41
+ }
42
+ catch (nativeCause) {
43
+ try {
44
+ text = await navigator.clipboard.readText();
45
+ }
46
+ catch (browserCause) {
47
+ setLastRead(null);
48
+ setError(browserCause instanceof Error
49
+ ? browserCause
50
+ : nativeCause instanceof Error
51
+ ? nativeCause
52
+ : new Error('Could not read clipboard text.'));
53
+ return null;
54
+ }
55
+ }
56
+ setLastRead(text);
57
+ return text;
58
+ }, []);
59
+ return { writeText, readText, lastRead, error };
60
+ }
61
+ /**
62
+ * Copy-to-clipboard button: resolves `value`, writes it, and morphs the copy
63
+ * icon into a success check for a moment.
64
+ */
65
+ function ClipboardCopyButton({ value, onCopied, onCopyError, variant = 'ghost', size = 'icon-sm', onClick, children, ...props }) {
66
+ const { writeText } = useMoldableClipboard();
67
+ const [copied, setCopied] = useState(false);
68
+ const [copyFailed, setCopyFailed] = useState(false);
69
+ const tokenRef = useRef(0);
70
+ const timeoutRef = useRef(null);
71
+ useEffect(() => () => {
72
+ tokenRef.current++;
73
+ if (timeoutRef.current !== null)
74
+ window.clearTimeout(timeoutRef.current);
75
+ }, []);
76
+ const copy = useCallback(async () => {
77
+ const token = ++tokenRef.current;
78
+ setCopied(false);
79
+ setCopyFailed(false);
80
+ let text;
81
+ try {
82
+ const source = value;
83
+ text = typeof source === 'function' ? await source() : source;
84
+ }
85
+ catch (cause) {
86
+ if (tokenRef.current !== token)
87
+ return;
88
+ const error = cause instanceof Error ? cause : new Error('Could not resolve text.');
89
+ setCopyFailed(true);
90
+ onCopyError?.(error);
91
+ return;
92
+ }
93
+ const written = await writeText(text);
94
+ if (tokenRef.current !== token)
95
+ return;
96
+ if (!written) {
97
+ const error = new Error('Could not write clipboard text.');
98
+ setCopyFailed(true);
99
+ onCopyError?.(error);
100
+ return;
101
+ }
102
+ onCopied?.(text);
103
+ setCopied(true);
104
+ if (timeoutRef.current !== null)
105
+ window.clearTimeout(timeoutRef.current);
106
+ timeoutRef.current = window.setTimeout(() => setCopied(false), 1500);
107
+ }, [onCopied, onCopyError, value, writeText]);
108
+ return (_jsxs(Button, { "data-slot": "clipboard-copy-button", "data-copied": copied || undefined, "data-copy-status": copied ? 'success' : copyFailed ? 'error' : 'idle', variant: variant, size: size, "aria-label": copied ? 'Copied' : copyFailed ? 'Copy failed; try again' : 'Copy', onClick: (event) => {
109
+ onClick?.(event);
110
+ if (!event.defaultPrevented)
111
+ void copy();
112
+ }, ...props, children: [copied ? (_jsx(Check, { className: "text-success animate-in zoom-in-50 duration-200 motion-reduce:animate-none" })) : (_jsx(Copy, {})), children] }));
113
+ }
114
+ export { ClipboardCopyButton };
@@ -0,0 +1,4 @@
1
+ export declare function parseIntegerInput(value: string, label: string, minimum: number, maximum: number): number;
2
+ export declare function parseHexBytes(value: string): Uint8Array<ArrayBuffer>;
3
+ export declare function formatHexBytes(data: ArrayBufferView | null | undefined): string;
4
+ //# sourceMappingURL=device-io-controls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-io-controls.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/device-io-controls.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,MAAM,CASR;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAepE;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,CAMR"}
@@ -0,0 +1,28 @@
1
+ export function parseIntegerInput(value, label, minimum, maximum) {
2
+ const normalized = value.trim();
3
+ const parsed = normalized.length > 0 ? Number(normalized) : Number.NaN;
4
+ if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) {
5
+ throw new Error(`${label} must be an integer from ${minimum} to ${maximum}.`);
6
+ }
7
+ return parsed;
8
+ }
9
+ export function parseHexBytes(value) {
10
+ const normalized = value.trim();
11
+ if (normalized.length === 0)
12
+ return new Uint8Array(0);
13
+ const tokens = normalized.split(/[\s,]+/u);
14
+ const bytes = tokens.map((token) => {
15
+ const digits = token.replace(/^0x/iu, '');
16
+ if (!/^[\da-f]{1,2}$/iu.test(digits)) {
17
+ throw new Error('Payload must contain hexadecimal bytes such as 01 A0 FF.');
18
+ }
19
+ return Number.parseInt(digits, 16);
20
+ });
21
+ return new Uint8Array(bytes);
22
+ }
23
+ export function formatHexBytes(data) {
24
+ if (!data || data.byteLength === 0)
25
+ return '(empty)';
26
+ const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
27
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ');
28
+ }