@zanecoder/zc-sign-editor
v0.1.11
Published
Embeddable ZC Sign field editor and send shell for host apps (e.g. Retail Origination)
Maintainers
Readme
@zanecoder/zc-sign-editor
Embeddable Vue Send Document UI for host apps (Retail Origination, etc.). Upload a PDF, add recipients, place signature / initials / date fields, and prepare a send payload — without an iframe.
UI matches zc-sign’s send page. Creating a signing session still goes through the zc-sign backend. This package only prepares the document; your host BFF talks to zc-sign.
Source: github.com/zaneCoder/zc-sign-fe → packages/zc-sign-editor
Changelog (recent)
0.1.10
- Bug fix: PDF field placement stays aligned after scroll/zoom (fields stored in base viewport coords).
- Bug fix: Adjust Field Sizes resizes placed fields; removed hidden second PDF load that caused OOM on large docs.
0.1.9
- Multi-signer / sequential: Only the active signer’s fields can be dragged (other signers’ fields stay red / click-to-reassign). New drops follow Assign new fields to. Preview
pageLayoutssync into prepare so metadata ownership matches/send.
0.1.8
- Feature: Click a placed field on the PDF → Selected field assignee appears in the sidebar so you can move that field to another signer (was broken: selection lived only inside the nested editor).
0.1.7
- Bug fix: Dragging Signature / Initials / Date onto the PDF no longer hangs the browser tab (“Aw, Snap!”). Field (and recipient)
v-modelsync now uses equality guards so Vue cannot infinite-loop updates betweenZcSignSendandZcSignEditor.
0.1.6
- Optional host
theme(forwarded into editor empty state + chrome) - Sharper HiDPI PDF preview without shifting field coordinates
Do I need integration keys?
Yes for the overall flow — but not inside this SDK / not in the browser.
| | Needs keys? | Where |
| --- | --- | --- |
| This package (ZcSignSend) | No | Browser Vue UI only |
| Your host BFF / server | Yes | Server env only |
| zc-sign API | Validates keys | POST /api/integration/send-document |
Host server env:
| Env | Used for |
| --- | --- |
| ZC_SIGN_PUBLIC_KEY | Header ZC-SIGN-PUB-KEY |
| ZC_SIGN_SECRET_KEY | Header ZC-SIGN-SECRET-KEY |
| ZC_SIGN_API_URL | zc-sign API base URL |
| ZC_SIGN_FRONTEND_URL | Signer links |
Never put pub/secret keys in this package, Vue client code, or NUXT_PUBLIC_* / Vite VITE_* vars.
How it works
Host UI (Retail, etc.)
└─ <ZcSignSend /> ← this package
• pick PDF, recipients, fields
• tokenize PDF (@signature#id, …)
• build metadata JSON
└─ submit(payload) ← you provide this callback
└─ Host BFF ← auth + server keys
• upload PDF
• POST zc-sign /api/integration/send-document
headers: ZC-SIGN-PUB-KEY, ZC-SIGN-SECRET-KEY
└─ session uuid
Signers open zc-sign FE
/signature-signing?session=…&email=…
Webhook → your host (e.g. /api/webhooks/zc-sign)| Layer | Responsibility |
| --- | --- |
| SDK (ZcSignSend) | UI, place fields, prepare PDF + metadata, call submit |
| Host BFF | Auth, storage, call zc-sign with server keys, save session |
| zc-sign BE | Session, emails, signing order, completed PDF |
| zc-sign FE | Actual signing experience (not this package) |
What this SDK prepares
- Tokenized PDF (
@signature#id,@initials#id,@date#idbaked in) - Recipient list + field layout / size metadata (integration FormData shape)
What stays on zc-sign
- Session UUID, signing links, email queue, sequential / parallel delivery
- Signer UI, completed PDF, webhooks back to your host
If you omit the submit prop, the SDK only emits a prepared event — still no keys; your app must send to the BFF afterward.
Install
npm install @zanecoder/zc-sign-editorPeer dependency: Vue ^3.4.
import { ZcSignSend, ZcSignEditor } from '@zanecoder/zc-sign-editor'
import '@zanecoder/zc-sign-editor/style.css'Quick usage
<script setup lang="ts">
import { ref } from 'vue'
import { ZcSignSend, createRecipient } from '@zanecoder/zc-sign-editor'
import type { ZcSignPreparedSend, ZcSignRecipient, ZcSignSendResult } from '@zanecoder/zc-sign-editor'
import '@zanecoder/zc-sign-editor/style.css'
const recipients = ref<ZcSignRecipient[]>([createRecipient(1)])
async function submit(payload: ZcSignPreparedSend): Promise<ZcSignSendResult> {
// Browser → your BFF only (never call zc-sign with secret from the client)
const form = new FormData()
form.append('file', payload.pdf, payload.documentName || 'document.pdf')
form.append('metadata', JSON.stringify(payload.metadata))
form.append('documentName', payload.documentName)
form.append('subject', payload.subject)
form.append('message', payload.message)
form.append('signingMode', payload.signingMode)
const res = await fetch('/api/your-create-signing-session', {
method: 'POST',
body: form,
})
return res.json()
}
</script>
<template>
<ZcSignSend v-model:recipients="recipients" :submit="submit" />
</template>ZcSignSend props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| recipients | ZcSignRecipient[] | required | Signers / viewers (use v-model:recipients) |
| submit | (payload) => Promise<ZcSignSendResult> | — | Host callback; if omitted, only prepared is emitted |
| file | File \| null | null | Optional preloaded PDF |
| documentName | string | '' | Document title |
| subject | string | '' | Email subject |
| message | string | '' | Email message |
| signingMode | 'default' \| 'sequential' \| 'parallel' | 'sequential' | Signing order mode |
| initialFields | ZcSignField[] | [] | Pre-placed fields |
Also supports v-model:documentName, v-model:message, v-model:signingMode, v-model:fields.
ZcSignSend events
| Event | Payload | When |
| --- | --- | --- |
| prepared | ZcSignPreparedSend | PDF + metadata ready (always before/alongside send) |
| sent | ZcSignSendResult \| ZcSignPreparedSend | After submit resolves, or prepared-only flow |
| cancel | — | User cancels |
| error | { code: string; message: string } | Prepare / submit failure |
| update:recipients | ZcSignRecipient[] | Recipients changed |
| update:fields | ZcSignField[] | Fields changed |
| update:documentName | string | Name changed |
| update:message | string | Message changed |
| update:signingMode | ZcSignSigningMode | Mode changed |
ZcSignPreparedSend shape
What submit / prepared receive:
interface ZcSignPreparedSend {
pdf: Blob // tokenized PDF (not the original upload alone)
documentName: string
subject: string
message: string
signingMode: 'default' | 'sequential' | 'parallel'
recipients: ZcSignRecipient[]
metadata: ZcSignMetadataItem[] // per-recipient field layouts for zc-sign
fields: ZcSignField[] // editor field list
}
interface ZcSignRecipient {
id: string
name: string
email: string
signeeType: 'signer' | 'viewer' | 'approver' | 'client'
}
interface ZcSignMetadataItem {
name: string
email: string
signee_type: ZcSignRecipient['signeeType']
signing_order: number
field_sizes: Record<'signature' | 'initials' | 'date', { width: number; height: number }>
field_ids: string[]
field_layouts: ZcSignFieldLayout[]
signatory_variable?: '@signature'
initial_variable?: '@initials'
date_variable?: '@date'
}Helper: appendZcSignMetadata(formData, payload.metadata) appends the metadata field your BFF / zc-sign expect.
Host BFF checklist
Use this on the server (Retail, etc.):
- Auth the logged-in user before accepting the upload
- Accept PDF (
payload.pdf) + metadata JSON (+ name / subject / message / mode) - Store the PDF (your storage / bucket) if required by your app
- Call zc-sign:
POST {ZC_SIGN_API_URL}/api/integration/send-document
ZC-SIGN-PUB-KEY: {ZC_SIGN_PUBLIC_KEY}
ZC-SIGN-SECRET-KEY: {ZC_SIGN_SECRET_KEY}
Content-Type: multipart/form-data- Persist returned session uuid / signing links in your DB
- Return
{ sessionUuid }(or your shape) to the SDKsubmitpromise - Handle webhooks from zc-sign when signing completes
| Do | Don't | | --- | --- | | Keep keys in server env / secrets manager | Put keys in the Vue app or this SDK | | Send FormData from browser → your BFF only | Call zc-sign directly from the browser with the secret | | Validate recipients + PDF size / type on BFF | Trust client-only checks |
Exports
| Export | Role |
| --- | --- |
| ZcSignSend | Full send shell (recipients + field placement) |
| ZcSignEditor | Field editor surface alone |
| useZcSignSend / useZcSignEditor | Composables for custom UIs |
| createRecipient | Build a default recipient row |
| prepareZcSignPdf | Bake field tokens into the PDF |
| buildZcSignMetadata / appendZcSignMetadata | Build / attach integration metadata |
| configurePdfJsWorker | Optional PDF.js worker override |
| resolveZcSignThemeStyle / ZcSignTheme | Optional host brand colors for Send / Editor |
Styles: @zanecoder/zc-sign-editor/style.css
Theme (optional)
Pass any valid CSS colors (including host tokens like hsl(var(--company-primary))):
<ZcSignSend
:recipients="recipients"
:theme="{
primary: 'hsl(var(--company-primary))',
primaryHover: 'hsl(var(--company-secondary))',
background: 'hsl(var(--company-background))',
border: 'hsl(var(--company-border-soft) / 0.35)',
contrast: 'hsl(var(--company-primary-contrast))',
}"
:submit="submitPrepared"
/>Keys: primary, primaryHover, accent, background, border, contrast.
If theme is omitted (or a key is blank), the built-in zc-sign palette is used (ZC_SIGN_DEFAULT_THEME).ZcSignSend forwards theme into the nested ZcSignEditor (empty state + document preview chrome).
FAQ
| Question | Answer |
| --- | --- |
| Does create-session go through zc-sign? | Yes — via your host BFF |
| Does the SDK need integration keys? | No — only your server does |
| Keys in the browser? | Never |
| Iframe? | No — native Vue components |
| Where do signers sign? | zc-sign FE, not inside this package |
| Can I use helpers without the UI? | Yes — prepareZcSignPdf, metadata utils, composables |
| PDF preview “Cannot read from private field”? | Fixed in 0.1.4+ — single host pdfjs-dist + non-reactive PDF.js handles; upgrade the package |
| Worker “Failed to fetch … jsdelivr …”? | Use a same-origin worker (?url or /pdfjs/pdf.worker.min.mjs); fixed default in 0.1.5+ |
| PDF preview looks blurry on Retina? | Preview paints at capped devicePixelRatio; field coords stay CSS pixels — rebuild/upgrade after HiDPI canvas fix |
| Tab hangs / “Aw, Snap!” when dragging Signature/Date fields? | Fixed in 0.1.7+ — field (and recipient) prop sync used to infinite-loop Vue updates; upgrade and pin ^0.1.7 |
| Can’t change which signer owns a placed field? | Fixed in 0.1.8+ — click the field, then use Selected field assignee in the sidebar |
| Signer 1 can place/move Signer 2’s fields? | Fixed in 0.1.9+ — use Assign new fields to; other signers’ fields are locked (click to reassign only) |
PDF.js worker
Under Nuxt, prefer a same-origin Nitro/static worker URL via configurePdfJsWorker('/api/…/pdf-worker'). Vite ?worker is often broken by dep optimization (missing default export); CDN and workerSrc pointing at node_modules / @fs often fail too.
Retail: app/utils/signing/setupZcSignPdfJsWorker.ts (called from CreateSigningSessionPanel).
import * as pdfjsLib from 'pdfjs-dist'
import PdfWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?worker'
import { configurePdfJsWorker } from '@zanecoder/zc-sign-editor'
pdfjsLib.GlobalWorkerOptions.workerPort = new PdfWorker()
configurePdfJsWorker()Call this on the client before mounting ZcSignSend.
Links
| | | | --- | --- | | npm | https://www.npmjs.com/package/@zanecoder/zc-sign-editor | | GitHub | https://github.com/zaneCoder/zc-sign-fe/tree/developv3/packages/zc-sign-editor | | How it works (detail) | docs/how-it-works.md | | SDK overview | docs/zc-sign-send-sdk.md | | Publish / versioning | docs/publish.md |
License
UNLICENSED — internal / proprietary use under Zanecoder.
