@qrkit/react 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,2 +1,136 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Account, ScannedUR } from '@qrkit/core';
1
3
 
2
- export { }
4
+ interface QRKitTheme {
5
+ /** Primary accent color. Defaults follow MD3: light #6750A4, dark #D0BCFF */
6
+ accent?: string;
7
+ /** Modal background color. Defaults follow MD3: light #FFFBFE, dark #1C1B1F */
8
+ background?: string;
9
+ /** Modal backdrop color. Default: rgba(0,0,0,0.6) */
10
+ backdrop?: string;
11
+ /** Text color. Defaults follow MD3: light #1C1B1F, dark #E6E1E5 */
12
+ text?: string;
13
+ /** Muted/secondary text color. Defaults follow MD3: light #49454F, dark #CAC4D0 */
14
+ textMuted?: string;
15
+ /** Border radius. Default: 12px (MD3 "medium") */
16
+ radius?: string;
17
+ /** Font family. Default: inherit */
18
+ fontFamily?: string;
19
+ }
20
+ interface SignRequest {
21
+ message: string;
22
+ address: string;
23
+ sourceFingerprint: number | undefined;
24
+ }
25
+ interface QRKitContextValue {
26
+ account: Account | null;
27
+ connect: () => void;
28
+ disconnect: () => void;
29
+ /** Open the sign modal and resolve with the hex signature. */
30
+ sign: (request: SignRequest) => Promise<string>;
31
+ }
32
+ interface QRKitProviderProps {
33
+ children: React.ReactNode;
34
+ theme?: QRKitTheme;
35
+ /** App name shown in the sign request origin field. Default: "qrkit" */
36
+ appName?: string;
37
+ }
38
+
39
+ declare function QRKitProvider({ children, theme, appName }: QRKitProviderProps): react_jsx_runtime.JSX.Element;
40
+ declare function useQRKit(): QRKitContextValue;
41
+
42
+ interface ConnectModalProps {
43
+ onConnect: (account: Account) => void;
44
+ onClose: () => void;
45
+ }
46
+ declare function ConnectModal({ onConnect, onClose }: ConnectModalProps): react_jsx_runtime.JSX.Element;
47
+
48
+ interface SignModalProps {
49
+ request: SignRequest;
50
+ appName: string;
51
+ onSign: (signature: string) => void;
52
+ onReject: () => void;
53
+ }
54
+ declare function SignModal({ request, appName, onSign, onReject }: SignModalProps): react_jsx_runtime.JSX.Element;
55
+
56
+ interface QRScannerProps {
57
+ onScan: (result: ScannedUR | string) => boolean | void;
58
+ hint?: string;
59
+ enabled?: boolean;
60
+ className?: string;
61
+ }
62
+ declare function QRScanner({ onScan, hint, enabled, className }: QRScannerProps): react_jsx_runtime.JSX.Element;
63
+
64
+ interface QRDisplayProps {
65
+ parts: string[];
66
+ interval?: number;
67
+ size?: number;
68
+ className?: string;
69
+ }
70
+ declare function QRDisplay({ parts, interval, size, className }: QRDisplayProps): react_jsx_runtime.JSX.Element;
71
+
72
+ interface UseQRScannerOptions {
73
+ /**
74
+ * Called when a QR code is decoded.
75
+ * Return false to keep scanning (e.g. on parse error), void/true to stop.
76
+ */
77
+ onScan: (result: ScannedUR | string) => boolean | void;
78
+ enabled?: boolean;
79
+ }
80
+ interface UseQRScannerResult {
81
+ videoRef: React.RefObject<HTMLVideoElement | null>;
82
+ /** 0–100 while scanning an animated UR, null otherwise */
83
+ progress: number | null;
84
+ error: string | null;
85
+ }
86
+ declare function useQRScanner({ onScan, enabled, }: UseQRScannerOptions): UseQRScannerResult;
87
+
88
+ interface UseQRDisplayOptions {
89
+ /** QR parts to cycle through. Single-frame: pass an array with one element. */
90
+ parts: string[];
91
+ /** Interval between frames in ms. Default: 200 */
92
+ interval?: number;
93
+ /** Canvas size in pixels. Default: 300 */
94
+ size?: number;
95
+ }
96
+ interface UseQRDisplayResult {
97
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
98
+ /** Current frame index */
99
+ frame: number;
100
+ total: number;
101
+ }
102
+ declare function useQRDisplay({ parts, interval, size, }: UseQRDisplayOptions): UseQRDisplayResult;
103
+
104
+ interface UseURDecoderOptions {
105
+ /**
106
+ * Called when a QR string is decoded.
107
+ * Return false to keep scanning (e.g. on parse error), void/true to stop.
108
+ */
109
+ onScan: (result: ScannedUR | string) => boolean | void;
110
+ }
111
+ interface UseURDecoderResult {
112
+ /** Feed a raw QR string into the decoder. Returns true when scanning is done. */
113
+ receivePart: (data: string) => boolean;
114
+ /** 0–100 while assembling an animated UR, null otherwise */
115
+ progress: number | null;
116
+ /** Reset decoder state (e.g. to start a new scan) */
117
+ reset: () => void;
118
+ }
119
+ declare function useURDecoder({ onScan }: UseURDecoderOptions): UseURDecoderResult;
120
+
121
+ interface UseQRPartsOptions {
122
+ /** QR parts to cycle through. Single-frame: pass an array with one element. */
123
+ parts: string[];
124
+ /** Interval between frames in ms. Default: 200 */
125
+ interval?: number;
126
+ }
127
+ interface UseQRPartsResult {
128
+ /** The current QR string to render */
129
+ part: string;
130
+ /** Current frame index (0-based) */
131
+ frame: number;
132
+ total: number;
133
+ }
134
+ declare function useQRParts({ parts, interval, }: UseQRPartsOptions): UseQRPartsResult;
135
+
136
+ export { ConnectModal, type ConnectModalProps, QRDisplay, type QRDisplayProps, type QRKitContextValue, QRKitProvider, type QRKitProviderProps, type QRKitTheme, QRScanner, type QRScannerProps, SignModal, type SignModalProps, type SignRequest, type UseQRDisplayOptions, type UseQRDisplayResult, type UseQRPartsOptions, type UseQRPartsResult, type UseQRScannerOptions, type UseQRScannerResult, type UseURDecoderOptions, type UseURDecoderResult, useQRDisplay, useQRKit, useQRParts, useQRScanner, useURDecoder };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,136 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Account, ScannedUR } from '@qrkit/core';
1
3
 
2
- export { }
4
+ interface QRKitTheme {
5
+ /** Primary accent color. Defaults follow MD3: light #6750A4, dark #D0BCFF */
6
+ accent?: string;
7
+ /** Modal background color. Defaults follow MD3: light #FFFBFE, dark #1C1B1F */
8
+ background?: string;
9
+ /** Modal backdrop color. Default: rgba(0,0,0,0.6) */
10
+ backdrop?: string;
11
+ /** Text color. Defaults follow MD3: light #1C1B1F, dark #E6E1E5 */
12
+ text?: string;
13
+ /** Muted/secondary text color. Defaults follow MD3: light #49454F, dark #CAC4D0 */
14
+ textMuted?: string;
15
+ /** Border radius. Default: 12px (MD3 "medium") */
16
+ radius?: string;
17
+ /** Font family. Default: inherit */
18
+ fontFamily?: string;
19
+ }
20
+ interface SignRequest {
21
+ message: string;
22
+ address: string;
23
+ sourceFingerprint: number | undefined;
24
+ }
25
+ interface QRKitContextValue {
26
+ account: Account | null;
27
+ connect: () => void;
28
+ disconnect: () => void;
29
+ /** Open the sign modal and resolve with the hex signature. */
30
+ sign: (request: SignRequest) => Promise<string>;
31
+ }
32
+ interface QRKitProviderProps {
33
+ children: React.ReactNode;
34
+ theme?: QRKitTheme;
35
+ /** App name shown in the sign request origin field. Default: "qrkit" */
36
+ appName?: string;
37
+ }
38
+
39
+ declare function QRKitProvider({ children, theme, appName }: QRKitProviderProps): react_jsx_runtime.JSX.Element;
40
+ declare function useQRKit(): QRKitContextValue;
41
+
42
+ interface ConnectModalProps {
43
+ onConnect: (account: Account) => void;
44
+ onClose: () => void;
45
+ }
46
+ declare function ConnectModal({ onConnect, onClose }: ConnectModalProps): react_jsx_runtime.JSX.Element;
47
+
48
+ interface SignModalProps {
49
+ request: SignRequest;
50
+ appName: string;
51
+ onSign: (signature: string) => void;
52
+ onReject: () => void;
53
+ }
54
+ declare function SignModal({ request, appName, onSign, onReject }: SignModalProps): react_jsx_runtime.JSX.Element;
55
+
56
+ interface QRScannerProps {
57
+ onScan: (result: ScannedUR | string) => boolean | void;
58
+ hint?: string;
59
+ enabled?: boolean;
60
+ className?: string;
61
+ }
62
+ declare function QRScanner({ onScan, hint, enabled, className }: QRScannerProps): react_jsx_runtime.JSX.Element;
63
+
64
+ interface QRDisplayProps {
65
+ parts: string[];
66
+ interval?: number;
67
+ size?: number;
68
+ className?: string;
69
+ }
70
+ declare function QRDisplay({ parts, interval, size, className }: QRDisplayProps): react_jsx_runtime.JSX.Element;
71
+
72
+ interface UseQRScannerOptions {
73
+ /**
74
+ * Called when a QR code is decoded.
75
+ * Return false to keep scanning (e.g. on parse error), void/true to stop.
76
+ */
77
+ onScan: (result: ScannedUR | string) => boolean | void;
78
+ enabled?: boolean;
79
+ }
80
+ interface UseQRScannerResult {
81
+ videoRef: React.RefObject<HTMLVideoElement | null>;
82
+ /** 0–100 while scanning an animated UR, null otherwise */
83
+ progress: number | null;
84
+ error: string | null;
85
+ }
86
+ declare function useQRScanner({ onScan, enabled, }: UseQRScannerOptions): UseQRScannerResult;
87
+
88
+ interface UseQRDisplayOptions {
89
+ /** QR parts to cycle through. Single-frame: pass an array with one element. */
90
+ parts: string[];
91
+ /** Interval between frames in ms. Default: 200 */
92
+ interval?: number;
93
+ /** Canvas size in pixels. Default: 300 */
94
+ size?: number;
95
+ }
96
+ interface UseQRDisplayResult {
97
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
98
+ /** Current frame index */
99
+ frame: number;
100
+ total: number;
101
+ }
102
+ declare function useQRDisplay({ parts, interval, size, }: UseQRDisplayOptions): UseQRDisplayResult;
103
+
104
+ interface UseURDecoderOptions {
105
+ /**
106
+ * Called when a QR string is decoded.
107
+ * Return false to keep scanning (e.g. on parse error), void/true to stop.
108
+ */
109
+ onScan: (result: ScannedUR | string) => boolean | void;
110
+ }
111
+ interface UseURDecoderResult {
112
+ /** Feed a raw QR string into the decoder. Returns true when scanning is done. */
113
+ receivePart: (data: string) => boolean;
114
+ /** 0–100 while assembling an animated UR, null otherwise */
115
+ progress: number | null;
116
+ /** Reset decoder state (e.g. to start a new scan) */
117
+ reset: () => void;
118
+ }
119
+ declare function useURDecoder({ onScan }: UseURDecoderOptions): UseURDecoderResult;
120
+
121
+ interface UseQRPartsOptions {
122
+ /** QR parts to cycle through. Single-frame: pass an array with one element. */
123
+ parts: string[];
124
+ /** Interval between frames in ms. Default: 200 */
125
+ interval?: number;
126
+ }
127
+ interface UseQRPartsResult {
128
+ /** The current QR string to render */
129
+ part: string;
130
+ /** Current frame index (0-based) */
131
+ frame: number;
132
+ total: number;
133
+ }
134
+ declare function useQRParts({ parts, interval, }: UseQRPartsOptions): UseQRPartsResult;
135
+
136
+ export { ConnectModal, type ConnectModalProps, QRDisplay, type QRDisplayProps, type QRKitContextValue, QRKitProvider, type QRKitProviderProps, type QRKitTheme, QRScanner, type QRScannerProps, SignModal, type SignModalProps, type SignRequest, type UseQRDisplayOptions, type UseQRDisplayResult, type UseQRPartsOptions, type UseQRPartsResult, type UseQRScannerOptions, type UseQRScannerResult, type UseURDecoderOptions, type UseURDecoderResult, useQRDisplay, useQRKit, useQRParts, useQRScanner, useURDecoder };
package/dist/index.js CHANGED
@@ -1 +1,385 @@
1
+ // src/context.tsx
2
+ import { createContext, useCallback as useCallback5, useContext, useEffect as useEffect5, useMemo, useRef as useRef6, useState as useState5 } from "react";
3
+ import { createPortal } from "react-dom";
4
+
5
+ // src/components/ConnectModal.tsx
6
+ import { useCallback as useCallback3 } from "react";
7
+ import { parseConnection } from "@qrkit/core";
8
+
9
+ // src/components/Modal.tsx
10
+ import { useEffect, useRef } from "react";
11
+ import { createFocusTrap } from "focus-trap";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function Modal({ title, onClose, children, className }) {
14
+ const containerRef = useRef(null);
15
+ useEffect(() => {
16
+ const el = containerRef.current;
17
+ const trap = el ? createFocusTrap(el, {
18
+ escapeDeactivates: true,
19
+ onDeactivate: onClose,
20
+ allowOutsideClick: true
21
+ }) : null;
22
+ trap?.activate();
23
+ return () => {
24
+ trap?.deactivate();
25
+ };
26
+ }, [onClose]);
27
+ useEffect(() => {
28
+ const handleKey = (e) => {
29
+ if (e.key === "Escape") onClose();
30
+ };
31
+ document.addEventListener("keydown", handleKey);
32
+ return () => document.removeEventListener("keydown", handleKey);
33
+ }, [onClose]);
34
+ return /* @__PURE__ */ jsx("div", { className: "qrkit qrkit-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true", children: /* @__PURE__ */ jsxs(
35
+ "div",
36
+ {
37
+ ref: containerRef,
38
+ className: `qrkit-modal${className ? ` ${className}` : ""}`,
39
+ onClick: (e) => e.stopPropagation(),
40
+ children: [
41
+ /* @__PURE__ */ jsxs("div", { className: "qrkit-modal-header", children: [
42
+ /* @__PURE__ */ jsx("h2", { className: "qrkit-modal-title", children: title }),
43
+ /* @__PURE__ */ jsx("button", { className: "qrkit-close-btn", onClick: onClose, "aria-label": "Close", children: "\u2715" })
44
+ ] }),
45
+ children
46
+ ]
47
+ }
48
+ ) });
49
+ }
50
+
51
+ // src/hooks/useQRScanner.ts
52
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
53
+ import QrScanner from "qr-scanner";
54
+
55
+ // src/hooks/useURDecoder.ts
56
+ import { useCallback, useRef as useRef2, useState } from "react";
57
+ import { URDecoder } from "@ngraveio/bc-ur";
58
+ function useURDecoder({ onScan }) {
59
+ const decoderRef = useRef2(new URDecoder());
60
+ const onScanRef = useRef2(onScan);
61
+ const [progress, setProgress] = useState(null);
62
+ onScanRef.current = onScan;
63
+ const reset = useCallback(() => {
64
+ decoderRef.current = new URDecoder();
65
+ setProgress(null);
66
+ }, []);
67
+ const receivePart = useCallback(
68
+ (data) => {
69
+ if (!data.toLowerCase().startsWith("ur:")) {
70
+ return onScanRef.current(data) !== false;
71
+ }
72
+ decoderRef.current.receivePart(data.toLowerCase());
73
+ setProgress(Math.round(decoderRef.current.estimatedPercentComplete() * 100));
74
+ if (!decoderRef.current.isComplete()) return false;
75
+ const ur = decoderRef.current.resultUR();
76
+ const scanned = { type: ur.type, cbor: new Uint8Array(ur.cbor) };
77
+ if (onScanRef.current(scanned) !== false) return true;
78
+ reset();
79
+ return false;
80
+ },
81
+ [reset]
82
+ );
83
+ return { receivePart, progress, reset };
84
+ }
85
+
86
+ // src/hooks/useQRScanner.ts
87
+ function useQRScanner({
88
+ onScan,
89
+ enabled = true
90
+ }) {
91
+ const videoRef = useRef3(null);
92
+ const scannerRef = useRef3(null);
93
+ const [error, setError] = useState2(null);
94
+ const { receivePart, progress } = useURDecoder({ onScan });
95
+ const processResult = useCallback2(
96
+ (data, scanner) => {
97
+ const done = receivePart(data);
98
+ if (done) scanner.stop();
99
+ },
100
+ [receivePart]
101
+ );
102
+ useEffect2(() => {
103
+ if (!enabled || !videoRef.current) return;
104
+ const scanner = new QrScanner(
105
+ videoRef.current,
106
+ (result) => processResult(result.data, scanner),
107
+ {
108
+ preferredCamera: "environment",
109
+ highlightScanRegion: false,
110
+ highlightCodeOutline: false
111
+ }
112
+ );
113
+ scannerRef.current = scanner;
114
+ scanner.start().catch(() => {
115
+ setError("Camera access denied. Please allow camera permissions.");
116
+ });
117
+ return () => {
118
+ scanner.stop();
119
+ scanner.destroy();
120
+ scannerRef.current = null;
121
+ };
122
+ }, [enabled, processResult]);
123
+ return { videoRef, progress, error };
124
+ }
125
+
126
+ // src/components/QRScanner.tsx
127
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
128
+ function QRScanner({ onScan, hint, enabled = true, className }) {
129
+ const { videoRef, progress, error } = useQRScanner({ onScan, enabled });
130
+ if (error) {
131
+ return /* @__PURE__ */ jsx2("div", { className: `qrkit-scanner-error${className ? ` ${className}` : ""}`, children: error });
132
+ }
133
+ return /* @__PURE__ */ jsxs2("div", { className: `qrkit-scanner-wrap${className ? ` ${className}` : ""}`, children: [
134
+ /* @__PURE__ */ jsx2("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "qrkit-scanner-video" }),
135
+ /* @__PURE__ */ jsxs2("div", { className: "qrkit-scanner-overlay", children: [
136
+ /* @__PURE__ */ jsx2("div", { className: "qrkit-scanner-corner tl" }),
137
+ /* @__PURE__ */ jsx2("div", { className: "qrkit-scanner-corner tr" }),
138
+ /* @__PURE__ */ jsx2("div", { className: "qrkit-scanner-corner bl" }),
139
+ /* @__PURE__ */ jsx2("div", { className: "qrkit-scanner-corner br" })
140
+ ] }),
141
+ progress !== null && progress < 100 && /* @__PURE__ */ jsxs2("div", { className: "qrkit-scanner-progress", children: [
142
+ progress,
143
+ "%"
144
+ ] }),
145
+ /* @__PURE__ */ jsx2("p", { className: "qrkit-hint", style: { position: "absolute", bottom: 8, left: 0, right: 0 }, children: progress !== null && progress < 100 ? "Keep scanning \u2014 animated QR in progress\u2026" : hint ?? "Point camera at the QR code" })
146
+ ] });
147
+ }
148
+
149
+ // src/components/ConnectModal.tsx
150
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
151
+ function ConnectModal({ onConnect, onClose }) {
152
+ const handleScan = useCallback3(
153
+ (data) => {
154
+ try {
155
+ const accounts = parseConnection(data, { chains: ["evm"] });
156
+ const account = accounts[0];
157
+ if (!account) return false;
158
+ onConnect(account);
159
+ } catch {
160
+ return false;
161
+ }
162
+ },
163
+ [onConnect]
164
+ );
165
+ return /* @__PURE__ */ jsxs3(Modal, { title: "Connect Wallet", onClose, children: [
166
+ /* @__PURE__ */ jsxs3("p", { className: "qrkit-step", children: [
167
+ "On your hardware wallet, go to ",
168
+ /* @__PURE__ */ jsx3("strong", { children: "Connect software wallet" }),
169
+ " and point the screen at this camera."
170
+ ] }),
171
+ /* @__PURE__ */ jsx3(QRScanner, { onScan: handleScan, hint: "Scan the wallet's connection QR code" })
172
+ ] });
173
+ }
174
+
175
+ // src/components/SignModal.tsx
176
+ import { useCallback as useCallback4, useState as useState4 } from "react";
177
+ import { buildEthSignRequestURParts, parseEthSignature } from "@qrkit/core";
178
+
179
+ // src/hooks/useQRDisplay.ts
180
+ import { useEffect as useEffect4, useRef as useRef5 } from "react";
181
+ import QRCode from "qrcode";
182
+
183
+ // src/hooks/useQRParts.ts
184
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
185
+ function useQRParts({
186
+ parts,
187
+ interval = 200
188
+ }) {
189
+ const [frame, setFrame] = useState3(0);
190
+ const frameRef = useRef4(0);
191
+ useEffect3(() => {
192
+ frameRef.current = 0;
193
+ setFrame(0);
194
+ }, [parts]);
195
+ useEffect3(() => {
196
+ if (parts.length <= 1) return;
197
+ const id = setInterval(() => {
198
+ frameRef.current = (frameRef.current + 1) % parts.length;
199
+ setFrame(frameRef.current);
200
+ }, interval);
201
+ return () => clearInterval(id);
202
+ }, [parts, interval]);
203
+ return {
204
+ part: parts[frameRef.current % Math.max(parts.length, 1)] ?? "",
205
+ frame,
206
+ total: parts.length
207
+ };
208
+ }
209
+
210
+ // src/hooks/useQRDisplay.ts
211
+ function useQRDisplay({
212
+ parts,
213
+ interval,
214
+ size = 300
215
+ }) {
216
+ const canvasRef = useRef5(null);
217
+ const { part, frame, total } = useQRParts({ parts, interval });
218
+ useEffect4(() => {
219
+ if (!part || !canvasRef.current) return;
220
+ QRCode.toCanvas(canvasRef.current, part, {
221
+ width: size,
222
+ margin: 2,
223
+ errorCorrectionLevel: "M"
224
+ }).catch(() => {
225
+ });
226
+ }, [part, size]);
227
+ return { canvasRef, frame, total };
228
+ }
229
+
230
+ // src/components/QRDisplay.tsx
231
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
232
+ function QRDisplay({ parts, interval, size = 300, className }) {
233
+ const { canvasRef, frame, total } = useQRDisplay({ parts, interval, size });
234
+ return /* @__PURE__ */ jsxs4("div", { className: `qrkit-qr-wrap${className ? ` ${className}` : ""}`, children: [
235
+ /* @__PURE__ */ jsx4("canvas", { ref: canvasRef, className: "qrkit-qr-canvas", width: size, height: size }),
236
+ total > 1 && /* @__PURE__ */ jsxs4("p", { className: "qrkit-hint", children: [
237
+ "Frame ",
238
+ frame + 1,
239
+ " / ",
240
+ total,
241
+ " \u2014 keep Shell pointed at the screen"
242
+ ] })
243
+ ] });
244
+ }
245
+
246
+ // src/components/SignModal.tsx
247
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
248
+ function SignModal({ request, appName, onSign, onReject }) {
249
+ const [step, setStep] = useState4("display");
250
+ const parts = buildEthSignRequestURParts(
251
+ request.message,
252
+ request.address,
253
+ request.sourceFingerprint,
254
+ appName
255
+ );
256
+ const handleScan = useCallback4(
257
+ (data) => {
258
+ try {
259
+ const sig = parseEthSignature(data);
260
+ onSign(sig);
261
+ } catch {
262
+ return false;
263
+ }
264
+ },
265
+ [onSign]
266
+ );
267
+ return /* @__PURE__ */ jsxs5(
268
+ Modal,
269
+ {
270
+ title: step === "display" ? "Sign Request" : "Scan Signature",
271
+ onClose: onReject,
272
+ children: [
273
+ step === "display" && /* @__PURE__ */ jsxs5(Fragment, { children: [
274
+ /* @__PURE__ */ jsx5("p", { className: "qrkit-step", children: "Point your hardware wallet camera at this QR code to approve the sign request." }),
275
+ /* @__PURE__ */ jsx5(QRDisplay, { parts }),
276
+ /* @__PURE__ */ jsx5("button", { className: "qrkit-btn qrkit-btn-primary", onClick: () => setStep("scan"), children: "Wallet signed \u2014 scan response" }),
277
+ /* @__PURE__ */ jsx5("button", { className: "qrkit-btn qrkit-btn-ghost", onClick: onReject, children: "Cancel" })
278
+ ] }),
279
+ step === "scan" && /* @__PURE__ */ jsxs5(Fragment, { children: [
280
+ /* @__PURE__ */ jsx5("p", { className: "qrkit-step", children: "On your hardware wallet, show the signature QR and point it at this camera." }),
281
+ /* @__PURE__ */ jsx5(QRScanner, { onScan: handleScan, hint: "Scan the wallet's signature QR code" }),
282
+ /* @__PURE__ */ jsx5("button", { className: "qrkit-btn qrkit-btn-ghost", onClick: () => setStep("display"), children: "\u2190 Back" })
283
+ ] })
284
+ ]
285
+ }
286
+ );
287
+ }
288
+
289
+ // src/context.tsx
290
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
291
+ var QRKitContext = createContext(null);
292
+ function buildThemeStyle(theme) {
293
+ const vars = {
294
+ "--qrkit-accent": theme.accent,
295
+ "--qrkit-bg": theme.background,
296
+ "--qrkit-backdrop": theme.backdrop,
297
+ "--qrkit-text": theme.text,
298
+ "--qrkit-text-muted": theme.textMuted,
299
+ "--qrkit-radius": theme.radius,
300
+ "--qrkit-font": theme.fontFamily
301
+ };
302
+ const declarations = Object.entries(vars).filter(([, v]) => v !== void 0).map(([k, v]) => ` ${k}: ${v};`).join("\n");
303
+ return declarations ? `.qrkit {
304
+ ${declarations}
305
+ }` : "";
306
+ }
307
+ function QRKitProvider({ children, theme = {}, appName = "qrkit" }) {
308
+ const [account, setAccount] = useState5(null);
309
+ const [connectOpen, setConnectOpen] = useState5(false);
310
+ const [pendingSign, setPendingSign] = useState5(null);
311
+ const pendingSignRef = useRef6(null);
312
+ const themeStyle = useMemo(() => buildThemeStyle(theme), [theme]);
313
+ useEffect5(() => {
314
+ if (!themeStyle) return;
315
+ const el = document.createElement("style");
316
+ el.setAttribute("data-qrkit-theme", "");
317
+ el.textContent = themeStyle;
318
+ document.head.appendChild(el);
319
+ return () => el.remove();
320
+ }, [themeStyle]);
321
+ const connect = useCallback5(() => setConnectOpen(true), []);
322
+ const disconnect = useCallback5(() => setAccount(null), []);
323
+ const handleConnect = useCallback5((acc) => {
324
+ setAccount(acc);
325
+ setConnectOpen(false);
326
+ }, []);
327
+ const sign = useCallback5((request) => {
328
+ return new Promise((resolve, reject) => {
329
+ const pending = { request, resolve, reject };
330
+ pendingSignRef.current = pending;
331
+ setPendingSign(pending);
332
+ });
333
+ }, []);
334
+ const handleSign = useCallback5((sig) => {
335
+ pendingSignRef.current?.resolve(sig);
336
+ pendingSignRef.current = null;
337
+ setPendingSign(null);
338
+ }, []);
339
+ const handleReject = useCallback5(() => {
340
+ pendingSignRef.current?.reject(new Error("User rejected the sign request"));
341
+ pendingSignRef.current = null;
342
+ setPendingSign(null);
343
+ }, []);
344
+ const value = useMemo(
345
+ () => ({ account, connect, disconnect, sign }),
346
+ [account, connect, disconnect, sign]
347
+ );
348
+ return /* @__PURE__ */ jsxs6(QRKitContext.Provider, { value, children: [
349
+ children,
350
+ connectOpen && createPortal(
351
+ /* @__PURE__ */ jsx6(ConnectModal, { onConnect: handleConnect, onClose: () => setConnectOpen(false) }),
352
+ document.body
353
+ ),
354
+ pendingSign && createPortal(
355
+ /* @__PURE__ */ jsx6(
356
+ SignModal,
357
+ {
358
+ request: pendingSign.request,
359
+ appName,
360
+ onSign: handleSign,
361
+ onReject: handleReject
362
+ }
363
+ ),
364
+ document.body
365
+ )
366
+ ] });
367
+ }
368
+ function useQRKit() {
369
+ const ctx = useContext(QRKitContext);
370
+ if (!ctx) throw new Error("useQRKit must be used within a QRKitProvider");
371
+ return ctx;
372
+ }
373
+ export {
374
+ ConnectModal,
375
+ QRDisplay,
376
+ QRKitProvider,
377
+ QRScanner,
378
+ SignModal,
379
+ useQRDisplay,
380
+ useQRKit,
381
+ useQRParts,
382
+ useQRScanner,
383
+ useURDecoder
384
+ };
1
385
  //# sourceMappingURL=index.js.map