@uurtech/jdf 0.1.14 → 0.1.17
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/jdfjs.cjs +3 -3
- package/dist/jdfjs.cjs.map +1 -1
- package/dist/jdfjs.css +135 -0
- package/dist/jdfjs.d.cts +69 -5
- package/dist/jdfjs.d.ts +69 -5
- package/dist/jdfjs.js +3 -3
- package/dist/jdfjs.js.map +1 -1
- package/package.json +1 -1
- package/src/auto-init.ts +82 -9
- package/src/jdfjs.css +135 -0
- package/src/jdfx.ts +20 -0
- package/src/renderers/element.ts +194 -0
- package/src/utils/link.ts +4 -1
- package/src/viewer.ts +222 -20
package/dist/jdfjs.css
CHANGED
|
@@ -223,3 +223,138 @@ jdf {
|
|
|
223
223
|
width: 100%;
|
|
224
224
|
min-height: 600px;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/* === Form elements ====================================================== */
|
|
228
|
+
.jdfjs-form-field {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
gap: 4px;
|
|
232
|
+
font-family: "Inter", -apple-system, sans-serif;
|
|
233
|
+
font-size: 12px;
|
|
234
|
+
color: var(--jdfjs-text);
|
|
235
|
+
width: 100%;
|
|
236
|
+
height: 100%;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.jdfjs-form-label {
|
|
240
|
+
font-size: 11px;
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
color: var(--jdfjs-text-soft);
|
|
243
|
+
letter-spacing: 0.02em;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.jdfjs-form-control {
|
|
247
|
+
display: block;
|
|
248
|
+
width: 100%;
|
|
249
|
+
padding: 6px 10px;
|
|
250
|
+
font: inherit;
|
|
251
|
+
color: var(--jdfjs-text);
|
|
252
|
+
background: var(--jdfjs-bg);
|
|
253
|
+
border: 1px solid var(--jdfjs-border);
|
|
254
|
+
border-radius: 6px;
|
|
255
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
256
|
+
box-sizing: border-box;
|
|
257
|
+
}
|
|
258
|
+
.jdfjs-form-control:focus {
|
|
259
|
+
outline: none;
|
|
260
|
+
border-color: var(--jdfjs-brand);
|
|
261
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.16);
|
|
262
|
+
}
|
|
263
|
+
.jdfjs-form-control[readonly],
|
|
264
|
+
.jdfjs-form-control:disabled {
|
|
265
|
+
background: var(--jdfjs-bg-soft, #f3f4f6);
|
|
266
|
+
color: var(--jdfjs-text-soft);
|
|
267
|
+
cursor: not-allowed;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
textarea.jdfjs-form-control {
|
|
271
|
+
resize: vertical;
|
|
272
|
+
min-height: 60px;
|
|
273
|
+
line-height: 1.4;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.jdfjs-form-checkbox {
|
|
277
|
+
flex-direction: row;
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: 8px;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
}
|
|
282
|
+
.jdfjs-form-checkbox input[type="checkbox"] {
|
|
283
|
+
width: 16px; height: 16px;
|
|
284
|
+
margin: 0;
|
|
285
|
+
cursor: pointer;
|
|
286
|
+
accent-color: var(--jdfjs-brand);
|
|
287
|
+
}
|
|
288
|
+
.jdfjs-form-checkbox-label {
|
|
289
|
+
font-size: 13px;
|
|
290
|
+
color: var(--jdfjs-text);
|
|
291
|
+
user-select: none;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
select.jdfjs-form-control {
|
|
295
|
+
appearance: none;
|
|
296
|
+
-webkit-appearance: none;
|
|
297
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--jdfjs-text-soft) 50%),
|
|
298
|
+
linear-gradient(135deg, var(--jdfjs-text-soft) 50%, transparent 50%);
|
|
299
|
+
background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
|
|
300
|
+
background-size: 5px 5px, 5px 5px;
|
|
301
|
+
background-repeat: no-repeat;
|
|
302
|
+
padding-right: 28px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.jdfjs-form-signature-canvas {
|
|
306
|
+
border: 1px dashed var(--jdfjs-border);
|
|
307
|
+
border-radius: 6px;
|
|
308
|
+
background: var(--jdfjs-bg);
|
|
309
|
+
cursor: crosshair;
|
|
310
|
+
touch-action: none;
|
|
311
|
+
}
|
|
312
|
+
.jdfjs-form-signature-clear {
|
|
313
|
+
align-self: flex-start;
|
|
314
|
+
margin-top: 4px;
|
|
315
|
+
padding: 2px 10px;
|
|
316
|
+
font-size: 11px;
|
|
317
|
+
color: var(--jdfjs-text-soft);
|
|
318
|
+
background: transparent;
|
|
319
|
+
border: 1px solid var(--jdfjs-border);
|
|
320
|
+
border-radius: 4px;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
}
|
|
323
|
+
.jdfjs-form-signature-clear:hover {
|
|
324
|
+
color: var(--jdfjs-text);
|
|
325
|
+
border-color: var(--jdfjs-text-soft);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* === Save button (auto-init's `save-button` attribute) ================= */
|
|
329
|
+
.jdfjs-save-button {
|
|
330
|
+
position: absolute;
|
|
331
|
+
bottom: 16px;
|
|
332
|
+
right: 16px;
|
|
333
|
+
z-index: 5;
|
|
334
|
+
padding: 8px 16px;
|
|
335
|
+
font-family: "Inter", -apple-system, sans-serif;
|
|
336
|
+
font-size: 13px;
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
color: #fff;
|
|
339
|
+
background: var(--jdfjs-brand);
|
|
340
|
+
border: 0;
|
|
341
|
+
border-radius: 8px;
|
|
342
|
+
box-shadow: 0 4px 14px rgba(37, 99, 235, 0.25);
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease;
|
|
345
|
+
}
|
|
346
|
+
.jdfjs-save-button:hover {
|
|
347
|
+
transform: translateY(-1px);
|
|
348
|
+
box-shadow: 0 6px 18px rgba(37, 99, 235, 0.35);
|
|
349
|
+
}
|
|
350
|
+
.jdfjs-save-button:active {
|
|
351
|
+
transform: translateY(0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.jdfjs-dark .jdfjs-save-button {
|
|
355
|
+
background: #3b82f6;
|
|
356
|
+
}
|
|
357
|
+
.jdfjs-dark .jdfjs-form-control[readonly],
|
|
358
|
+
.jdfjs-dark .jdfjs-form-control:disabled {
|
|
359
|
+
background: #1f2937;
|
|
360
|
+
}
|
package/dist/jdfjs.d.cts
CHANGED
|
@@ -45,11 +45,21 @@ interface JDFViewerOptions {
|
|
|
45
45
|
onLoad?: (doc: JdfDocument) => void;
|
|
46
46
|
/** Called on any rendering error */
|
|
47
47
|
onError?: (err: Error) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Called when a form field's value changes. Useful for live previews,
|
|
50
|
+
* dirty-state tracking, or auto-saving filled forms to a backend instead
|
|
51
|
+
* of triggering a manual download.
|
|
52
|
+
*/
|
|
53
|
+
onFormChange?: (doc: JdfDocument, change: {
|
|
54
|
+
path: (string | number)[];
|
|
55
|
+
field: string;
|
|
56
|
+
value: unknown;
|
|
57
|
+
}) => void;
|
|
48
58
|
}
|
|
49
59
|
interface JDFViewerInstance {
|
|
50
60
|
/** The container element */
|
|
51
61
|
container: HTMLElement;
|
|
52
|
-
/** The current document */
|
|
62
|
+
/** The current document — reflects user form input as the user types. */
|
|
53
63
|
document: JdfDocument;
|
|
54
64
|
/** Get / set zoom (1 = 100%) */
|
|
55
65
|
setZoom: (zoom: number) => void;
|
|
@@ -61,11 +71,30 @@ interface JDFViewerInstance {
|
|
|
61
71
|
setDocument: (doc: JdfDocument) => void;
|
|
62
72
|
/** Tear down — removes DOM and event listeners */
|
|
63
73
|
destroy: () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Return the current document as a Blob — ready to attach to a form-data
|
|
76
|
+
* upload, save through `URL.createObjectURL`, or hand to a Worker. The
|
|
77
|
+
* blob reflects user form input (whatever the user has typed / ticked /
|
|
78
|
+
* selected). Pass `{ pretty: false }` for a compact JSON.
|
|
79
|
+
*/
|
|
80
|
+
exportJdf: (options?: {
|
|
81
|
+
pretty?: boolean;
|
|
82
|
+
}) => Blob;
|
|
83
|
+
/** Return the current document as a JSON string (form-filled state). */
|
|
84
|
+
toJSON: (options?: {
|
|
85
|
+
pretty?: boolean;
|
|
86
|
+
}) => string;
|
|
87
|
+
/**
|
|
88
|
+
* Trigger a browser download of the current document. The host page's
|
|
89
|
+
* "Save" button can call this directly: `viewer.downloadJdf("form.jdf")`.
|
|
90
|
+
* Default filename is `<title>.jdf` from `meta.title`.
|
|
91
|
+
*/
|
|
92
|
+
downloadJdf: (filename?: string) => void;
|
|
93
|
+
/** Read the value of a single form field by `name`. */
|
|
94
|
+
getFormValue: (name: string) => unknown;
|
|
95
|
+
/** Read every form field's value as a flat `{ [name]: value }` map. */
|
|
96
|
+
getFormValues: () => Record<string, unknown>;
|
|
64
97
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Embed a JDF document into a container by URL.
|
|
67
|
-
* The simplest "PDF.js-like" usage.
|
|
68
|
-
*/
|
|
69
98
|
declare function embed(container: HTMLElement | string, url: string, options?: JDFViewerOptions): Promise<JDFViewerInstance>;
|
|
70
99
|
/**
|
|
71
100
|
* Render a JDF document directly into a container. No fetch.
|
|
@@ -86,10 +115,20 @@ declare class JDFViewer {
|
|
|
86
115
|
private sidebarEl;
|
|
87
116
|
private root;
|
|
88
117
|
private observer;
|
|
118
|
+
private darkModeMql;
|
|
119
|
+
private darkModeListener;
|
|
120
|
+
private windowResizeListener;
|
|
89
121
|
constructor(container: HTMLElement, doc: JdfDocument, options?: JDFViewerOptions);
|
|
90
122
|
private applyContainerSize;
|
|
91
123
|
private mount;
|
|
92
124
|
private setupResizeObserver;
|
|
125
|
+
/**
|
|
126
|
+
* Apply the current `darkMode` option to the container. For `auto`, also
|
|
127
|
+
* subscribe to system colour-scheme changes so the embed flips when the
|
|
128
|
+
* user toggles their OS theme. Replaces a one-shot read at mount that
|
|
129
|
+
* froze the embed on its boot-time value.
|
|
130
|
+
*/
|
|
131
|
+
private applyDarkMode;
|
|
93
132
|
/** Auto-zoom for fit modes. */
|
|
94
133
|
private applyFit;
|
|
95
134
|
private buildToolbar;
|
|
@@ -106,6 +145,31 @@ declare class JDFViewer {
|
|
|
106
145
|
goToPage(idx: number): void;
|
|
107
146
|
getCurrentPage(): number;
|
|
108
147
|
setDocument(doc: JdfDocument): void;
|
|
148
|
+
/**
|
|
149
|
+
* Apply a form-field value mutation to the in-memory document. Called by
|
|
150
|
+
* every form renderer on every keystroke / toggle / selection — the
|
|
151
|
+
* document carries the user's current state so `exportJdf()` returns a
|
|
152
|
+
* filled JDF that's identical in shape to the source, just with values.
|
|
153
|
+
*
|
|
154
|
+
* No re-render: the DOM input already shows what the user typed, and a
|
|
155
|
+
* full re-render mid-typing would lose focus. The mutation only matters
|
|
156
|
+
* at export time.
|
|
157
|
+
*/
|
|
158
|
+
private handleFormChange;
|
|
159
|
+
/**
|
|
160
|
+
* Walk every page's elements and yield each form element with its
|
|
161
|
+
* resolved name. Used by getFormValues / downloadJdf consumers.
|
|
162
|
+
*/
|
|
163
|
+
private iterFormFields;
|
|
164
|
+
toJSON(options?: {
|
|
165
|
+
pretty?: boolean;
|
|
166
|
+
}): string;
|
|
167
|
+
exportJdf(options?: {
|
|
168
|
+
pretty?: boolean;
|
|
169
|
+
}): Blob;
|
|
170
|
+
downloadJdf(filename?: string): void;
|
|
171
|
+
getFormValue(name: string): unknown;
|
|
172
|
+
getFormValues(): Record<string, unknown>;
|
|
109
173
|
destroy(): void;
|
|
110
174
|
getInstance(): JDFViewerInstance;
|
|
111
175
|
}
|
package/dist/jdfjs.d.ts
CHANGED
|
@@ -45,11 +45,21 @@ interface JDFViewerOptions {
|
|
|
45
45
|
onLoad?: (doc: JdfDocument) => void;
|
|
46
46
|
/** Called on any rendering error */
|
|
47
47
|
onError?: (err: Error) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Called when a form field's value changes. Useful for live previews,
|
|
50
|
+
* dirty-state tracking, or auto-saving filled forms to a backend instead
|
|
51
|
+
* of triggering a manual download.
|
|
52
|
+
*/
|
|
53
|
+
onFormChange?: (doc: JdfDocument, change: {
|
|
54
|
+
path: (string | number)[];
|
|
55
|
+
field: string;
|
|
56
|
+
value: unknown;
|
|
57
|
+
}) => void;
|
|
48
58
|
}
|
|
49
59
|
interface JDFViewerInstance {
|
|
50
60
|
/** The container element */
|
|
51
61
|
container: HTMLElement;
|
|
52
|
-
/** The current document */
|
|
62
|
+
/** The current document — reflects user form input as the user types. */
|
|
53
63
|
document: JdfDocument;
|
|
54
64
|
/** Get / set zoom (1 = 100%) */
|
|
55
65
|
setZoom: (zoom: number) => void;
|
|
@@ -61,11 +71,30 @@ interface JDFViewerInstance {
|
|
|
61
71
|
setDocument: (doc: JdfDocument) => void;
|
|
62
72
|
/** Tear down — removes DOM and event listeners */
|
|
63
73
|
destroy: () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Return the current document as a Blob — ready to attach to a form-data
|
|
76
|
+
* upload, save through `URL.createObjectURL`, or hand to a Worker. The
|
|
77
|
+
* blob reflects user form input (whatever the user has typed / ticked /
|
|
78
|
+
* selected). Pass `{ pretty: false }` for a compact JSON.
|
|
79
|
+
*/
|
|
80
|
+
exportJdf: (options?: {
|
|
81
|
+
pretty?: boolean;
|
|
82
|
+
}) => Blob;
|
|
83
|
+
/** Return the current document as a JSON string (form-filled state). */
|
|
84
|
+
toJSON: (options?: {
|
|
85
|
+
pretty?: boolean;
|
|
86
|
+
}) => string;
|
|
87
|
+
/**
|
|
88
|
+
* Trigger a browser download of the current document. The host page's
|
|
89
|
+
* "Save" button can call this directly: `viewer.downloadJdf("form.jdf")`.
|
|
90
|
+
* Default filename is `<title>.jdf` from `meta.title`.
|
|
91
|
+
*/
|
|
92
|
+
downloadJdf: (filename?: string) => void;
|
|
93
|
+
/** Read the value of a single form field by `name`. */
|
|
94
|
+
getFormValue: (name: string) => unknown;
|
|
95
|
+
/** Read every form field's value as a flat `{ [name]: value }` map. */
|
|
96
|
+
getFormValues: () => Record<string, unknown>;
|
|
64
97
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Embed a JDF document into a container by URL.
|
|
67
|
-
* The simplest "PDF.js-like" usage.
|
|
68
|
-
*/
|
|
69
98
|
declare function embed(container: HTMLElement | string, url: string, options?: JDFViewerOptions): Promise<JDFViewerInstance>;
|
|
70
99
|
/**
|
|
71
100
|
* Render a JDF document directly into a container. No fetch.
|
|
@@ -86,10 +115,20 @@ declare class JDFViewer {
|
|
|
86
115
|
private sidebarEl;
|
|
87
116
|
private root;
|
|
88
117
|
private observer;
|
|
118
|
+
private darkModeMql;
|
|
119
|
+
private darkModeListener;
|
|
120
|
+
private windowResizeListener;
|
|
89
121
|
constructor(container: HTMLElement, doc: JdfDocument, options?: JDFViewerOptions);
|
|
90
122
|
private applyContainerSize;
|
|
91
123
|
private mount;
|
|
92
124
|
private setupResizeObserver;
|
|
125
|
+
/**
|
|
126
|
+
* Apply the current `darkMode` option to the container. For `auto`, also
|
|
127
|
+
* subscribe to system colour-scheme changes so the embed flips when the
|
|
128
|
+
* user toggles their OS theme. Replaces a one-shot read at mount that
|
|
129
|
+
* froze the embed on its boot-time value.
|
|
130
|
+
*/
|
|
131
|
+
private applyDarkMode;
|
|
93
132
|
/** Auto-zoom for fit modes. */
|
|
94
133
|
private applyFit;
|
|
95
134
|
private buildToolbar;
|
|
@@ -106,6 +145,31 @@ declare class JDFViewer {
|
|
|
106
145
|
goToPage(idx: number): void;
|
|
107
146
|
getCurrentPage(): number;
|
|
108
147
|
setDocument(doc: JdfDocument): void;
|
|
148
|
+
/**
|
|
149
|
+
* Apply a form-field value mutation to the in-memory document. Called by
|
|
150
|
+
* every form renderer on every keystroke / toggle / selection — the
|
|
151
|
+
* document carries the user's current state so `exportJdf()` returns a
|
|
152
|
+
* filled JDF that's identical in shape to the source, just with values.
|
|
153
|
+
*
|
|
154
|
+
* No re-render: the DOM input already shows what the user typed, and a
|
|
155
|
+
* full re-render mid-typing would lose focus. The mutation only matters
|
|
156
|
+
* at export time.
|
|
157
|
+
*/
|
|
158
|
+
private handleFormChange;
|
|
159
|
+
/**
|
|
160
|
+
* Walk every page's elements and yield each form element with its
|
|
161
|
+
* resolved name. Used by getFormValues / downloadJdf consumers.
|
|
162
|
+
*/
|
|
163
|
+
private iterFormFields;
|
|
164
|
+
toJSON(options?: {
|
|
165
|
+
pretty?: boolean;
|
|
166
|
+
}): string;
|
|
167
|
+
exportJdf(options?: {
|
|
168
|
+
pretty?: boolean;
|
|
169
|
+
}): Blob;
|
|
170
|
+
downloadJdf(filename?: string): void;
|
|
171
|
+
getFormValue(name: string): unknown;
|
|
172
|
+
getFormValues(): Record<string, unknown>;
|
|
109
173
|
destroy(): void;
|
|
110
174
|
getInstance(): JDFViewerInstance;
|
|
111
175
|
}
|