@unlev/exeq 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/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # Exeq
2
+
3
+ Embeddable PDF form builder and document signing for the browser. Design form templates on any PDF, then let users fill and sign them — all client-side, no backend required.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @unlev/exeq
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### React Components
14
+
15
+ ```tsx
16
+ import { DesignerView, SignerView } from '@unlev/exeq';
17
+ import '@unlev/exeq/styles';
18
+
19
+ // Template editor — upload a PDF, place form fields, export template JSON
20
+ function Editor() {
21
+ return <DesignerView />;
22
+ }
23
+
24
+ // Signing UI — load a template, fill fields, download signed PDF
25
+ function Signer() {
26
+ return (
27
+ <SignerView
28
+ initialPdfUrl="/contracts/template.pdf"
29
+ initialTemplate={template}
30
+ initialSigner="Signer 1"
31
+ onComplete={(blob) => {
32
+ // upload the signed PDF
33
+ }}
34
+ />
35
+ );
36
+ }
37
+ ```
38
+
39
+ ### Script Tag (CDN / iframe)
40
+
41
+ ```html
42
+ <script src="https://unpkg.com/@unlev/exeq/dist/embed.global.js"></script>
43
+ <link rel="stylesheet" href="https://unpkg.com/@unlev/exeq/dist/styles.css" />
44
+
45
+ <div id="editor" style="width: 100%; height: 800px;"></div>
46
+
47
+ <script>
48
+ Exeq.editor({ target: '#editor', pdf: '/blank.pdf' });
49
+ </script>
50
+ ```
51
+
52
+ ## API
53
+
54
+ ### Components
55
+
56
+ | Component | Description |
57
+ |-----------|-------------|
58
+ | `DesignerView` | Full template editor — PDF upload, field placement, role assignment, template export |
59
+ | `SignerView` | Signing UI — displays PDF with fields for the signer to fill and sign |
60
+ | `PdfViewer` | Low-level PDF page renderer with draggable field overlays |
61
+ | `SignatureCanvas` | Freehand signature/initials drawing canvas |
62
+ | `FieldPropertyPanel` | Field property editor (type, label, dimensions, assignee) |
63
+ | `FieldNavigator` | Prev/Next navigation through signer fields |
64
+ | `SignerRoleSelector` | Manage signer roles (add, remove, switch active role) |
65
+
66
+ ### `SignerView` Props
67
+
68
+ | Prop | Type | Description |
69
+ |------|------|-------------|
70
+ | `initialPdfUrl` | `string` | URL to the PDF |
71
+ | `initialTemplate` | `Template` | Template object with fields and signer roles |
72
+ | `initialSigner` | `string` | Signer role name (default: `"Signer 1"`) |
73
+ | `callbackUrl` | `string` | URL to POST the signed PDF to |
74
+ | `onComplete` | `(blob: Blob) => void` | Callback with the signed PDF |
75
+
76
+ ### Utilities
77
+
78
+ ```ts
79
+ import { renderPdfPages, generateFilledPdf, downloadPdf } from '@unlev/exeq';
80
+
81
+ // Render PDF pages to images
82
+ const pages = await renderPdfPages(pdfUrlOrBytes);
83
+
84
+ // Generate a filled PDF with form values overlaid
85
+ const bytes = await generateFilledPdf(pdfSource, fields);
86
+
87
+ // Trigger browser download
88
+ downloadPdf(bytes, 'signed-document.pdf');
89
+ ```
90
+
91
+ ### Types
92
+
93
+ ```ts
94
+ import type { FormField, Template, FieldType, RenderedPage } from '@unlev/exeq';
95
+ ```
96
+
97
+ **Field types:** `text`, `signature`, `initials`, `signed-date`, `checkbox`
98
+
99
+ **Text subtypes:** `freeform`, `number`, `date`, `email`, `phone`
100
+
101
+ ### Template JSON
102
+
103
+ The template exported by the editor:
104
+
105
+ ```json
106
+ {
107
+ "pdfUrl": "https://example.com/contract.pdf",
108
+ "signerRoles": ["Sender", "Signer 1"],
109
+ "fields": [
110
+ {
111
+ "id": "uuid",
112
+ "type": "text",
113
+ "label": "Full Name",
114
+ "placeholder": "Enter your full name",
115
+ "required": true,
116
+ "assignee": "Signer 1",
117
+ "page": 0,
118
+ "x": 15.5,
119
+ "y": 42.3,
120
+ "width": 25,
121
+ "height": 3,
122
+ "fontSize": 12,
123
+ "value": ""
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ Coordinates (`x`, `y`, `width`, `height`) are percentages of the page dimensions (0-100). Origin is top-left.
130
+
131
+ ## Workflow
132
+
133
+ 1. **Design** — Use `DesignerView` (or `Exeq.editor()`) to place fields on a PDF
134
+ 2. **Pre-fill** — Fill Sender fields (company name, your signature, etc.)
135
+ 3. **Export** — Save the template JSON and host the PDF
136
+ 4. **Sign** — Embed `SignerView` (or `Exeq.sign()`) for end users to fill and sign
137
+ 5. **Collect** — Receive the signed PDF via `onComplete` or `callbackUrl`
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ # Run the demo site
143
+ npm run dev
144
+
145
+ # Build the npm package
146
+ npm run build:lib
147
+
148
+ # Build everything (package + Next.js site)
149
+ npm run build
150
+ ```
151
+
152
+ ## License
153
+
154
+ MIT
package/dist/embed.css ADDED
@@ -0,0 +1 @@
1
+ .designer-layout,.signer-layout{display:flex;flex-direction:column;height:100vh;font-family:system-ui,-apple-system,sans-serif;color:#222;background:#fafafa}.designer-toolbar{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #e5e5e5;background:#fff;gap:1rem;flex-wrap:wrap}.toolbar-left,.toolbar-right{display:flex;align-items:center;gap:.5rem}.designer-content,.signer-content{display:flex;flex:1;overflow:hidden}.designer-pdf-area,.signer-pdf-area{flex:1;overflow-y:auto;padding:1rem;background:#eee}.designer-panel,.signer-panel{width:320px;overflow-y:auto;border-left:1px solid #e5e5e5;background:#fff;padding:1rem;display:flex;flex-direction:column;gap:1rem}.pdf-viewer{display:flex;flex-direction:column;gap:1rem;align-items:center}.pdf-page{position:relative;width:100%;max-width:800px;background:#fff;box-shadow:0 1px 4px #0000001a;cursor:crosshair}.pdf-page-image{width:100%;height:100%;display:block;pointer-events:none}.field-overlay{position:absolute;border:1.5px solid;border-radius:2px;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:box-shadow .1s}.field-overlay.selected{box-shadow:0 0 0 2px #0003;z-index:10}.field-overlay.editable{cursor:text}.field-overlay-label{position:absolute;top:-18px;left:-1px;font-size:10px;color:#fff;padding:1px 6px;border-radius:2px 2px 0 0;white-space:nowrap;pointer-events:none}.field-overlay-placeholder{color:#999;font-size:11px;padding:2px 4px;width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.field-overlay-placeholder.readonly{color:#666}.field-overlay-value{font-size:11px;padding:2px 4px;width:100%}.field-inline-input{width:100%;height:100%;border:none;background:transparent;padding:2px 4px;outline:none;font-family:inherit}.field-signature-preview{width:100%;height:100%;object-fit:contain}.field-signature-filled{width:100%;height:100%;cursor:pointer}.field-checkbox-display{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700}.field-checkbox-display.editable{cursor:pointer}.field-resize-handle{position:absolute;bottom:0;right:0;width:10px;height:10px;background:#fff;border:1px solid #999;cursor:nwse-resize}.upload-btn{display:inline-block;padding:.4rem 1rem;background:#111;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:.85rem;font-weight:500}.upload-btn:hover{background:#333}.upload-btn-small{font-size:.8rem;padding:.3rem .75rem;background:#555}.upload-btn-large{font-size:1rem;padding:.6rem 1.5rem}.export-btn{padding:.4rem 1rem;background:#111;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:.85rem;font-weight:500}.export-btn:hover{background:#333}.field-type-selector{display:flex;gap:2px}.field-type-btn{padding:.3rem .6rem;background:#fff;border:1px solid #ddd;border-radius:4px;cursor:pointer;font-size:.8rem;color:#555}.field-type-btn.active{background:#111;color:#fff;border-color:#111}.field-type-btn:hover:not(.active){background:#f5f5f5}.signer-role-selector{padding:.5rem 1rem;border-bottom:1px solid #e5e5e5;background:#fff}.signer-role-label{font-size:.75rem;color:#888;margin-bottom:.35rem}.signer-role-list{display:flex;gap:.35rem;flex-wrap:wrap;align-items:center}.signer-role-chip{padding:.2rem .6rem;border:1.5px solid;border-radius:4px;cursor:pointer;font-size:.8rem;font-weight:500;background:transparent;transition:all .1s}.signer-role-remove{margin-left:.3rem;cursor:pointer;font-size:1rem}.signer-role-add-btn{padding:.2rem .5rem;border:1px dashed #ccc;border-radius:4px;background:none;cursor:pointer;font-size:.8rem;color:#888}.signer-role-add-input{display:flex;gap:.25rem;align-items:center}.signer-role-add-input input{padding:.2rem .4rem;border:1px solid #ccc;border-radius:4px;font-size:.8rem;width:120px}.signer-role-add-input button{padding:.2rem .5rem;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer;font-size:.8rem}.field-property-panel{display:flex;flex-direction:column;gap:.75rem}.panel-header{display:flex;justify-content:space-between;align-items:center}.panel-header h3{margin:0;font-size:1rem}.panel-delete-btn{padding:.2rem .5rem;background:none;border:1px solid #e44;color:#e44;border-radius:4px;cursor:pointer;font-size:.8rem}.panel-delete-btn:hover{background:#e44;color:#fff}.panel-field label{display:block;font-size:.75rem;color:#888;margin-bottom:.2rem}.panel-field input[type=text],.panel-field input[type=number],.panel-field input[type=email],.panel-field input[type=tel],.panel-field input[type=date],.panel-field select{width:100%;padding:.35rem .5rem;border:1px solid #ddd;border-radius:4px;font-size:.85rem;background:#fff}.panel-field-group{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.panel-checkbox-label{display:flex;align-items:center;gap:.4rem;font-size:.85rem;cursor:pointer}.panel-empty{color:#888;font-size:.85rem;padding:2rem 1rem;text-align:center}.prefill-section{border-top:1px solid #eee;padding-top:.75rem}.prefill-section h4{margin:0 0 .5rem;font-size:.85rem;color:#555}.prefill-input{width:100%;padding:.35rem .5rem;border:1px solid #ddd;border-radius:4px;font-size:.85rem}.field-list{border-top:1px solid #eee;padding-top:.75rem}.field-list h4{margin:0 0 .5rem;font-size:.85rem;color:#555}.field-list-item{display:flex;align-items:center;gap:.4rem;padding:.35rem .5rem;border-radius:4px;cursor:pointer;font-size:.8rem}.field-list-item:hover{background:#f5f5f5}.field-list-item.active{background:#eee}.field-list-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.field-list-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-list-page{color:#aaa;font-size:.75rem}.field-list-required{color:#e44;font-weight:700}.signer-field-input h3{margin:0 0 .5rem;font-size:1rem}.required-badge{display:inline-block;font-size:.7rem;background:#fee;color:#c33;padding:1px 6px;border-radius:3px;margin-bottom:.5rem}.signer-text-input{width:100%;padding:.4rem .5rem;border:1px solid #ddd;border-radius:4px;font-size:.9rem}.signer-checkbox-label{display:flex;align-items:center;gap:.5rem;font-size:.9rem;cursor:pointer}.signer-date-display{font-size:.9rem;color:#555;padding:.4rem 0}.signer-field-readonly{color:#888;font-size:.85rem}.signer-field-readonly h3{margin:0 0 .25rem;font-size:1rem;color:#555}.signature-canvas-wrapper{display:flex;flex-direction:column;gap:.35rem}.signature-canvas{border:1px solid #ddd;border-radius:4px;background:#fff;cursor:crosshair}.signature-canvas-actions{display:flex;align-items:center;justify-content:space-between}.signature-clear-btn{padding:.2rem .5rem;background:none;border:1px solid #ccc;border-radius:4px;cursor:pointer;font-size:.8rem;color:#888}.signature-hint{font-size:.75rem;color:#aaa}.field-navigator{margin-top:auto;border-top:1px solid #eee;padding-top:.75rem;display:flex;flex-direction:column;gap:.5rem}.field-navigator-progress{font-size:.8rem;color:#888;text-align:center}.field-navigator-controls{display:flex;align-items:center;justify-content:center;gap:1rem}.nav-btn{padding:.35rem 1rem;border:1px solid #ddd;border-radius:4px;background:#fff;cursor:pointer;font-size:.85rem}.nav-btn:hover:not(:disabled){background:#f5f5f5}.nav-btn:disabled{opacity:.4;cursor:default}.field-navigator-position{font-size:.85rem;color:#888;min-width:50px;text-align:center}.submit-btn{padding:.5rem;background:#111;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:.9rem;font-weight:500}.submit-btn:hover:not(:disabled){background:#333}.submit-btn:disabled{opacity:.4;cursor:default}.loading-indicator{text-align:center;padding:2rem;color:#888;font-size:.9rem}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:.75rem;color:#888}.empty-state h2{margin:0;font-size:1.25rem;color:#555}.empty-state p{margin:0;font-size:.9rem}.powered-by{text-align:center;padding:.4rem;font-size:.7rem;color:#aaa;border-top:1px solid #eee;background:#fff}.powered-by a{color:#888;text-decoration:none}.powered-by a:hover{text-decoration:underline}