@schnsrw/casual-sheets 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Three signature-capture surfaces: drawn (canvas), typed (input
3
+ * rendered in a script font), uploaded (image file picker).
4
+ *
5
+ * Each one emits an `{ bytes, mime }` pair that the parent feeds
6
+ * into SigningContext.signField. The components are presentation +
7
+ * input only — they don't know about the controller or the field
8
+ * they're capturing for.
9
+ */
10
+
11
+ import { useEffect, useRef, useState, type CSSProperties } from 'react';
12
+
13
+ export interface CapturedSignature {
14
+ bytes: ArrayBuffer;
15
+ mime: string;
16
+ }
17
+
18
+ // ---------------------------------------------------------------
19
+ // Drawn — canvas
20
+ // ---------------------------------------------------------------
21
+
22
+ export interface DrawnSignaturePadProps {
23
+ /** Fired when the user clicks "Use this signature". */
24
+ onCapture: (sig: CapturedSignature) => void;
25
+ /** Optional clear-button label override. */
26
+ clearLabel?: string;
27
+ /** Optional save-button label override. */
28
+ saveLabel?: string;
29
+ /** Canvas pixel size. Default 480 × 160. */
30
+ width?: number;
31
+ height?: number;
32
+ }
33
+
34
+ export function DrawnSignaturePad({
35
+ onCapture,
36
+ clearLabel = 'Clear',
37
+ saveLabel = 'Use this signature',
38
+ width = 480,
39
+ height = 160,
40
+ }: DrawnSignaturePadProps) {
41
+ const canvasRef = useRef<HTMLCanvasElement>(null);
42
+ const drawingRef = useRef(false);
43
+ const [hasInk, setHasInk] = useState(false);
44
+
45
+ // Set up a clean canvas on mount. Background is transparent so
46
+ // the stamped image composites cleanly over the document.
47
+ useEffect(() => {
48
+ const canvas = canvasRef.current;
49
+ if (!canvas) return;
50
+ const ctx = canvas.getContext('2d');
51
+ if (!ctx) return;
52
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
53
+ ctx.strokeStyle = '#0f172a';
54
+ ctx.lineWidth = 2;
55
+ ctx.lineCap = 'round';
56
+ ctx.lineJoin = 'round';
57
+ }, []);
58
+
59
+ const start = (e: React.PointerEvent<HTMLCanvasElement>) => {
60
+ const canvas = canvasRef.current;
61
+ if (!canvas) return;
62
+ const ctx = canvas.getContext('2d');
63
+ if (!ctx) return;
64
+ const { x, y } = pointerPos(e, canvas);
65
+ ctx.beginPath();
66
+ ctx.moveTo(x, y);
67
+ drawingRef.current = true;
68
+ canvas.setPointerCapture(e.pointerId);
69
+ };
70
+
71
+ const move = (e: React.PointerEvent<HTMLCanvasElement>) => {
72
+ if (!drawingRef.current) return;
73
+ const canvas = canvasRef.current;
74
+ if (!canvas) return;
75
+ const ctx = canvas.getContext('2d');
76
+ if (!ctx) return;
77
+ const { x, y } = pointerPos(e, canvas);
78
+ ctx.lineTo(x, y);
79
+ ctx.stroke();
80
+ if (!hasInk) setHasInk(true);
81
+ };
82
+
83
+ const end = (e: React.PointerEvent<HTMLCanvasElement>) => {
84
+ drawingRef.current = false;
85
+ canvasRef.current?.releasePointerCapture(e.pointerId);
86
+ };
87
+
88
+ const clear = () => {
89
+ const canvas = canvasRef.current;
90
+ if (!canvas) return;
91
+ const ctx = canvas.getContext('2d');
92
+ if (!ctx) return;
93
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
94
+ setHasInk(false);
95
+ };
96
+
97
+ const save = async () => {
98
+ const canvas = canvasRef.current;
99
+ if (!canvas || !hasInk) return;
100
+ const blob = await new Promise<Blob | null>((resolve) => {
101
+ canvas.toBlob((b) => resolve(b), 'image/png');
102
+ });
103
+ if (!blob) return;
104
+ const bytes = await blob.arrayBuffer();
105
+ onCapture({ bytes, mime: 'image/png' });
106
+ clear();
107
+ };
108
+
109
+ return (
110
+ <div style={padWrapStyle} data-testid="drawn-signature-pad">
111
+ <canvas
112
+ ref={canvasRef}
113
+ width={width}
114
+ height={height}
115
+ style={padCanvasStyle(width, height)}
116
+ onPointerDown={start}
117
+ onPointerMove={move}
118
+ onPointerUp={end}
119
+ onPointerLeave={end}
120
+ data-testid="drawn-signature-canvas"
121
+ />
122
+ <div style={padActionsStyle}>
123
+ <button type="button" onClick={clear} style={secondaryBtnStyle(false)}>
124
+ {clearLabel}
125
+ </button>
126
+ <button
127
+ type="button"
128
+ onClick={save}
129
+ disabled={!hasInk}
130
+ style={primaryBtnStyle(!hasInk)}
131
+ data-testid="drawn-signature-save"
132
+ >
133
+ {saveLabel}
134
+ </button>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ function pointerPos(e: React.PointerEvent<HTMLCanvasElement>, canvas: HTMLCanvasElement) {
141
+ const rect = canvas.getBoundingClientRect();
142
+ const scaleX = canvas.width / rect.width;
143
+ const scaleY = canvas.height / rect.height;
144
+ return {
145
+ x: (e.clientX - rect.left) * scaleX,
146
+ y: (e.clientY - rect.top) * scaleY,
147
+ };
148
+ }
149
+
150
+ // ---------------------------------------------------------------
151
+ // Typed
152
+ // ---------------------------------------------------------------
153
+
154
+ export interface TypedSignatureFieldProps {
155
+ onCapture: (sig: CapturedSignature) => void;
156
+ defaultText?: string;
157
+ saveLabel?: string;
158
+ }
159
+
160
+ export function TypedSignatureField({
161
+ onCapture,
162
+ defaultText = '',
163
+ saveLabel = 'Use this signature',
164
+ }: TypedSignatureFieldProps) {
165
+ const [value, setValue] = useState(defaultText);
166
+ const save = () => {
167
+ const trimmed = value.trim();
168
+ if (!trimmed) return;
169
+ const bytes = new TextEncoder().encode(trimmed).buffer;
170
+ onCapture({ bytes, mime: 'text/plain' });
171
+ setValue('');
172
+ };
173
+ return (
174
+ <div style={padWrapStyle} data-testid="typed-signature-field">
175
+ <input
176
+ type="text"
177
+ value={value}
178
+ onChange={(e) => setValue(e.target.value)}
179
+ placeholder="Type your full name"
180
+ style={typedInputStyle}
181
+ data-testid="typed-signature-input"
182
+ autoFocus
183
+ />
184
+ <div style={padActionsStyle}>
185
+ <button
186
+ type="button"
187
+ onClick={save}
188
+ disabled={!value.trim()}
189
+ style={primaryBtnStyle(!value.trim())}
190
+ data-testid="typed-signature-save"
191
+ >
192
+ {saveLabel}
193
+ </button>
194
+ </div>
195
+ </div>
196
+ );
197
+ }
198
+
199
+ // ---------------------------------------------------------------
200
+ // Uploaded
201
+ // ---------------------------------------------------------------
202
+
203
+ export interface UploadedSignatureFieldProps {
204
+ onCapture: (sig: CapturedSignature) => void;
205
+ /** Accept attribute. Default image/*. */
206
+ accept?: string;
207
+ }
208
+
209
+ export function UploadedSignatureField({
210
+ onCapture,
211
+ accept = 'image/png,image/jpeg,image/svg+xml',
212
+ }: UploadedSignatureFieldProps) {
213
+ const inputRef = useRef<HTMLInputElement>(null);
214
+ const [fileName, setFileName] = useState<string | null>(null);
215
+ const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
216
+ const file = e.target.files?.[0];
217
+ if (!file) return;
218
+ const bytes = await file.arrayBuffer();
219
+ onCapture({ bytes, mime: file.type || 'application/octet-stream' });
220
+ setFileName(file.name);
221
+ if (inputRef.current) inputRef.current.value = '';
222
+ };
223
+ return (
224
+ <div style={padWrapStyle} data-testid="uploaded-signature-field">
225
+ <label style={uploadLabelStyle}>
226
+ <input
227
+ ref={inputRef}
228
+ type="file"
229
+ accept={accept}
230
+ onChange={onChange}
231
+ style={{ display: 'none' }}
232
+ data-testid="uploaded-signature-input"
233
+ />
234
+ <span>{fileName ?? 'Choose image…'}</span>
235
+ </label>
236
+ </div>
237
+ );
238
+ }
239
+
240
+ // ---------------------------------------------------------------
241
+ // Shared styles
242
+ // ---------------------------------------------------------------
243
+
244
+ const padWrapStyle: CSSProperties = {
245
+ display: 'flex',
246
+ flexDirection: 'column',
247
+ gap: 10,
248
+ };
249
+
250
+ const padCanvasStyle = (w: number, h: number): CSSProperties => ({
251
+ width: w,
252
+ height: h,
253
+ maxWidth: '100%',
254
+ border: '1px dashed var(--doc-border, #cbd5e1)',
255
+ borderRadius: 8,
256
+ background: 'var(--doc-surface, #fff)',
257
+ cursor: 'crosshair',
258
+ touchAction: 'none',
259
+ });
260
+
261
+ const padActionsStyle: CSSProperties = {
262
+ display: 'flex',
263
+ justifyContent: 'flex-end',
264
+ gap: 8,
265
+ };
266
+
267
+ const typedInputStyle: CSSProperties = {
268
+ width: '100%',
269
+ padding: '10px 12px',
270
+ border: '1px solid var(--doc-border, #cbd5e1)',
271
+ borderRadius: 6,
272
+ fontSize: 18,
273
+ fontFamily: '"Caveat", "Dancing Script", "Brush Script MT", cursive',
274
+ background: 'var(--doc-surface, #fff)',
275
+ color: 'var(--doc-text, #0f172a)',
276
+ };
277
+
278
+ const uploadLabelStyle: CSSProperties = {
279
+ display: 'inline-flex',
280
+ padding: '8px 14px',
281
+ border: '1px dashed var(--doc-border, #cbd5e1)',
282
+ borderRadius: 6,
283
+ background: 'var(--doc-surface, #fff)',
284
+ color: 'var(--doc-text, #0f172a)',
285
+ fontSize: 13,
286
+ cursor: 'pointer',
287
+ alignSelf: 'flex-start',
288
+ };
289
+
290
+ function primaryBtnStyle(disabled: boolean): CSSProperties {
291
+ return {
292
+ padding: '8px 16px',
293
+ borderRadius: 6,
294
+ border: '1px solid transparent',
295
+ background: disabled ? 'var(--doc-border, #cbd5e1)' : 'var(--doc-accent, #2563eb)',
296
+ color: disabled ? 'var(--doc-text-muted, #64748b)' : '#fff',
297
+ fontSize: 13,
298
+ fontWeight: 600,
299
+ cursor: disabled ? 'not-allowed' : 'pointer',
300
+ fontFamily: 'inherit',
301
+ };
302
+ }
303
+
304
+ function secondaryBtnStyle(disabled: boolean): CSSProperties {
305
+ return {
306
+ padding: '8px 16px',
307
+ borderRadius: 6,
308
+ border: '1px solid var(--doc-border, #cbd5e1)',
309
+ background: 'transparent',
310
+ color: 'var(--doc-text, #0f172a)',
311
+ fontSize: 13,
312
+ fontWeight: 500,
313
+ cursor: disabled ? 'not-allowed' : 'pointer',
314
+ fontFamily: 'inherit',
315
+ };
316
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Signing state machine — pure with respect to React so bun-test
3
+ * can exercise it without a renderer. The React wrappers
4
+ * (SigningProvider / SigningPane) sit on top of this.
5
+ *
6
+ * Single source of truth for "which field is the signer on,
7
+ * which fields are done, can we complete yet" — the React layer
8
+ * never recomputes these from scratch.
9
+ */
10
+
11
+ import type { SignedFieldPayload, SignatureField, SignatureMode } from './types';
12
+
13
+ export interface SigningSnapshot {
14
+ fields: SignatureField[];
15
+ mode: SignatureMode;
16
+ /** Fields completed in order they were signed. */
17
+ signed: Record<string, SignedFieldPayload>;
18
+ /** Index into `fields` of the field the signer should focus on
19
+ * next. -1 once everything required is done. */
20
+ activeFieldIndex: number;
21
+ /** True once every required field has a payload. */
22
+ canComplete: boolean;
23
+ /** True once the controller has received a completion command. */
24
+ isComplete: boolean;
25
+ /** True if a cancel has been emitted. */
26
+ isCancelled: boolean;
27
+ }
28
+
29
+ export interface SigningController {
30
+ snapshot(): SigningSnapshot;
31
+ subscribe(listener: (s: SigningSnapshot) => void): () => void;
32
+ /** Record a signed field. Throws if the field id is unknown. */
33
+ signField(payload: SignedFieldPayload): void;
34
+ /** Move focus to a specific field (concurrent mode let users
35
+ * pick); in sequential mode the controller silently no-ops if
36
+ * the requested field isn't the next required. */
37
+ focusField(fieldId: string): void;
38
+ /** Mark the session complete. Returns the snapshot at the
39
+ * moment of completion; throws if not yet canComplete. */
40
+ complete(): SigningSnapshot;
41
+ /** Mark the session cancelled. Idempotent. */
42
+ cancel(): void;
43
+ }
44
+
45
+ export function createSigningController(
46
+ fields: SignatureField[],
47
+ mode: SignatureMode,
48
+ ): SigningController {
49
+ if (fields.length === 0) {
50
+ throw new Error('createSigningController: at least one field required');
51
+ }
52
+ const fieldIds = new Set(fields.map((f) => f.fieldId));
53
+ if (fieldIds.size !== fields.length) {
54
+ throw new Error('createSigningController: duplicate fieldId');
55
+ }
56
+
57
+ const signed: Record<string, SignedFieldPayload> = {};
58
+ let activeFieldIndex = nextRequiredIndex(fields, signed);
59
+ let isComplete = false;
60
+ let isCancelled = false;
61
+ const listeners = new Set<(s: SigningSnapshot) => void>();
62
+
63
+ function emit() {
64
+ const snap = snapshotInternal();
65
+ for (const l of listeners) l(snap);
66
+ }
67
+
68
+ function snapshotInternal(): SigningSnapshot {
69
+ return {
70
+ fields,
71
+ mode,
72
+ signed: { ...signed },
73
+ activeFieldIndex,
74
+ canComplete: allRequiredSigned(fields, signed),
75
+ isComplete,
76
+ isCancelled,
77
+ };
78
+ }
79
+
80
+ return {
81
+ snapshot: snapshotInternal,
82
+ subscribe(listener) {
83
+ listeners.add(listener);
84
+ return () => {
85
+ listeners.delete(listener);
86
+ };
87
+ },
88
+ signField(payload) {
89
+ if (isComplete || isCancelled) return;
90
+ if (!fieldIds.has(payload.fieldId)) {
91
+ throw new Error(`signField: unknown fieldId ${payload.fieldId}`);
92
+ }
93
+ signed[payload.fieldId] = payload;
94
+ activeFieldIndex = nextRequiredIndex(fields, signed);
95
+ emit();
96
+ },
97
+ focusField(fieldId) {
98
+ if (isComplete || isCancelled) return;
99
+ const idx = fields.findIndex((f) => f.fieldId === fieldId);
100
+ if (idx < 0) return;
101
+ if (mode === 'sequential') {
102
+ // Sequential mode: only the next-required field is focusable.
103
+ const next = nextRequiredIndex(fields, signed);
104
+ if (idx !== next) return;
105
+ }
106
+ activeFieldIndex = idx;
107
+ emit();
108
+ },
109
+ complete() {
110
+ if (!allRequiredSigned(fields, signed)) {
111
+ throw new Error('complete: required fields are still unsigned');
112
+ }
113
+ isComplete = true;
114
+ activeFieldIndex = -1;
115
+ emit();
116
+ return snapshotInternal();
117
+ },
118
+ cancel() {
119
+ if (isComplete || isCancelled) return;
120
+ isCancelled = true;
121
+ activeFieldIndex = -1;
122
+ emit();
123
+ },
124
+ };
125
+ }
126
+
127
+ // ---------------------------------------------------------------
128
+ // Helpers
129
+ // ---------------------------------------------------------------
130
+
131
+ function allRequiredSigned(
132
+ fields: SignatureField[],
133
+ signed: Record<string, SignedFieldPayload>,
134
+ ): boolean {
135
+ return fields.every((f) => !f.required || signed[f.fieldId] !== undefined);
136
+ }
137
+
138
+ function nextRequiredIndex(
139
+ fields: SignatureField[],
140
+ signed: Record<string, SignedFieldPayload>,
141
+ ): number {
142
+ for (let i = 0; i < fields.length; i++) {
143
+ if (fields[i].required && !signed[fields[i].fieldId]) return i;
144
+ }
145
+ // Either all required are done OR no required exists; pick the
146
+ // first unsigned optional.
147
+ for (let i = 0; i < fields.length; i++) {
148
+ if (!signed[fields[i].fieldId]) return i;
149
+ }
150
+ return -1;
151
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Signing controller — pure state-machine tests. Mirrors the
3
+ * document/ repo's controller.test.ts; the controller code itself
4
+ * is byte-identical across both products (signing is a uniform
5
+ * concern). When you change controller.ts here, change it there
6
+ * too.
7
+ */
8
+ import { strict as assert } from 'node:assert';
9
+ import { test } from 'node:test';
10
+
11
+ import { createSigningController } from './controller';
12
+ import type { SignatureField, SignedFieldPayload } from './types';
13
+
14
+ const ALICE: SignatureField = {
15
+ fieldId: 'alice',
16
+ label: 'Alice',
17
+ required: true,
18
+ anchor: { kind: 'sheet', sheet: 'Sheet1', cell: 'B2' },
19
+ methods: ['drawn'],
20
+ };
21
+
22
+ const BOB: SignatureField = {
23
+ fieldId: 'bob',
24
+ label: 'Bob',
25
+ required: true,
26
+ anchor: { kind: 'sheet', sheet: 'Sheet1', cell: 'B3' },
27
+ methods: ['typed'],
28
+ };
29
+
30
+ const CAROL_OPTIONAL: SignatureField = {
31
+ fieldId: 'carol',
32
+ label: 'Carol (witness)',
33
+ required: false,
34
+ anchor: { kind: 'sheet', sheet: 'Sheet1', cell: 'B4' },
35
+ methods: ['drawn', 'typed'],
36
+ };
37
+
38
+ function payload(fieldId: string): SignedFieldPayload {
39
+ return {
40
+ fieldId,
41
+ method: 'drawn',
42
+ bytes: new Uint8Array([1, 2, 3]).buffer,
43
+ mime: 'image/png',
44
+ signedAt: '2026-06-08T00:00:00Z',
45
+ };
46
+ }
47
+
48
+ test('createSigningController: starts with the first required field active', () => {
49
+ const c = createSigningController([ALICE, BOB], 'sequential');
50
+ assert.equal(c.snapshot().activeFieldIndex, 0);
51
+ assert.equal(c.snapshot().canComplete, false);
52
+ assert.equal(c.snapshot().isComplete, false);
53
+ });
54
+
55
+ test('signField advances activeFieldIndex to the next required', () => {
56
+ const c = createSigningController([ALICE, BOB], 'sequential');
57
+ c.signField(payload('alice'));
58
+ assert.equal(c.snapshot().activeFieldIndex, 1);
59
+ assert.equal(c.snapshot().canComplete, false);
60
+ });
61
+
62
+ test('canComplete flips true once every required field is signed', () => {
63
+ const c = createSigningController([ALICE, BOB, CAROL_OPTIONAL], 'sequential');
64
+ c.signField(payload('alice'));
65
+ c.signField(payload('bob'));
66
+ assert.equal(c.snapshot().canComplete, true);
67
+ assert.equal(c.snapshot().signed['carol'], undefined);
68
+ });
69
+
70
+ test('complete throws when required fields are still unsigned', () => {
71
+ const c = createSigningController([ALICE, BOB], 'sequential');
72
+ c.signField(payload('alice'));
73
+ assert.throws(() => c.complete());
74
+ });
75
+
76
+ test('complete succeeds when all required done', () => {
77
+ const c = createSigningController([ALICE, BOB], 'sequential');
78
+ c.signField(payload('alice'));
79
+ c.signField(payload('bob'));
80
+ const snap = c.complete();
81
+ assert.equal(snap.isComplete, true);
82
+ assert.equal(snap.activeFieldIndex, -1);
83
+ });
84
+
85
+ test('cancel is idempotent and prevents further signField', () => {
86
+ const c = createSigningController([ALICE, BOB], 'sequential');
87
+ c.cancel();
88
+ c.cancel();
89
+ assert.equal(c.snapshot().isCancelled, true);
90
+ c.signField(payload('alice'));
91
+ assert.equal(c.snapshot().signed['alice'], undefined);
92
+ });
93
+
94
+ test('focusField in sequential mode rejects jumps to non-active fields', () => {
95
+ const c = createSigningController([ALICE, BOB], 'sequential');
96
+ c.focusField('bob');
97
+ assert.equal(c.snapshot().activeFieldIndex, 0);
98
+ });
99
+
100
+ test('focusField in concurrent mode allows any unsigned field', () => {
101
+ const c = createSigningController([ALICE, BOB], 'concurrent');
102
+ c.focusField('bob');
103
+ assert.equal(c.snapshot().activeFieldIndex, 1);
104
+ });
105
+
106
+ test('subscribers receive snapshots on every change', () => {
107
+ const c = createSigningController([ALICE, BOB], 'sequential');
108
+ const events: number[] = [];
109
+ const unsub = c.subscribe((s) => events.push(s.activeFieldIndex));
110
+ c.signField(payload('alice'));
111
+ c.signField(payload('bob'));
112
+ c.complete();
113
+ unsub();
114
+ assert.deepEqual(events, [1, -1, -1]);
115
+ });
116
+
117
+ test('signField throws on unknown fieldId', () => {
118
+ const c = createSigningController([ALICE], 'sequential');
119
+ assert.throws(() =>
120
+ c.signField({
121
+ ...payload('not-a-field'),
122
+ fieldId: 'not-a-field',
123
+ }),
124
+ );
125
+ });
126
+
127
+ test('rejects duplicate fieldId at construction', () => {
128
+ assert.throws(() => createSigningController([ALICE, { ...BOB, fieldId: 'alice' }], 'sequential'));
129
+ });
130
+
131
+ test('rejects empty field array at construction', () => {
132
+ assert.throws(() => createSigningController([], 'sequential'));
133
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Signing — document-signature pipeline.
3
+ *
4
+ * Surfaces:
5
+ * - SigningProvider + useSigning — React context wrapping the
6
+ * pure controller.
7
+ * - SigningPane — floating sidebar walking the signer through
8
+ * fields.
9
+ * - DrawnSignaturePad / TypedSignatureField / UploadedSignatureField
10
+ * — capture surfaces emitting { bytes, mime } pairs.
11
+ *
12
+ * Types mirror the iframe envelopes from
13
+ * `docs/internal/13-iframe-protocol.md` — same shape across docs
14
+ * and sheet.
15
+ */
16
+
17
+ export { SigningProvider, useSigning, type SigningProviderProps } from './SigningProvider';
18
+ export { SigningPane, type SigningPaneProps } from './SigningPane';
19
+ export {
20
+ DrawnSignaturePad,
21
+ TypedSignatureField,
22
+ UploadedSignatureField,
23
+ type CapturedSignature,
24
+ type DrawnSignaturePadProps,
25
+ type TypedSignatureFieldProps,
26
+ type UploadedSignatureFieldProps,
27
+ } from './captures';
28
+ export {
29
+ createSigningController,
30
+ type SigningController,
31
+ type SigningSnapshot,
32
+ } from './controller';
33
+ export type {
34
+ CancelReason,
35
+ DocAnchor,
36
+ SheetAnchor,
37
+ SignatureAnchor,
38
+ SignatureCompletePayload,
39
+ SignatureField,
40
+ SignatureMethod,
41
+ SignatureMode,
42
+ SignedFieldPayload,
43
+ SigningSessionConfig,
44
+ } from './types';