@embedpdf/plugin-form 3.0.0-next.10 → 3.0.0-next.11
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/contract/host.cjs +3 -0
- package/dist/contract/host.d.cts +2 -0
- package/dist/contract/host.d.ts +2 -0
- package/dist/contract/host.js +2 -0
- package/dist/contract.cjs +7 -0
- package/dist/contract.cjs.map +1 -0
- package/dist/contract.d.cts +8 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +7 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.cjs +401 -557
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -247
- package/dist/index.d.ts +10 -247
- package/dist/index.js +389 -545
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +4 -0
- package/dist/internal.d.cts +3 -0
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +3 -0
- package/dist/scripting.cjs +537 -0
- package/dist/scripting.cjs.map +1 -0
- package/dist/scripting.d.cts +60 -0
- package/dist/scripting.d.ts +60 -0
- package/dist/scripting.js +534 -0
- package/dist/scripting.js.map +1 -0
- package/dist/types-BlXdnNRf.d.ts +277 -0
- package/dist/types-Blr4QQM5.d.cts +277 -0
- package/dist/types-C1uIIYfn.cjs +16 -0
- package/dist/types-C1uIIYfn.cjs.map +1 -0
- package/dist/types-DkQmmcTG.js +12 -0
- package/dist/types-DkQmmcTG.js.map +1 -0
- package/package.json +25 -8
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,105 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_scripting = require("./scripting.cjs");
|
|
3
|
+
const require_types = require("./types-C1uIIYfn.cjs");
|
|
4
|
+
const require_contract = require("./contract.cjs");
|
|
2
5
|
let _embedpdf_core = require("@embedpdf/core");
|
|
3
|
-
let
|
|
4
|
-
let
|
|
5
|
-
let
|
|
6
|
+
let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
|
|
7
|
+
let _embedpdf_plugin_actions_contract_host = require("@embedpdf/plugin-actions/contract/host");
|
|
8
|
+
let _embedpdf_plugin_annotation_contract = require("@embedpdf/plugin-annotation/contract");
|
|
9
|
+
let _embedpdf_plugin_annotation_contract_host = require("@embedpdf/plugin-annotation/contract/host");
|
|
10
|
+
let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
|
|
6
11
|
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
7
|
-
let
|
|
12
|
+
let _embedpdf_plugin_annotation_authoring = require("@embedpdf/plugin-annotation/authoring");
|
|
13
|
+
//#region src/field-selection.ts
|
|
14
|
+
/**
|
|
15
|
+
* ISO field selection, written ONCE for ResetForm (Tables 241/242) and
|
|
16
|
+
* SubmitForm (Tables 239/240): a NAME target selects the field AND its
|
|
17
|
+
* descendants (FQN dot-prefix — `parent` selects `parent.c1`, the rule the
|
|
18
|
+
* old exact-match resolution violated); an objectNumber target selects that
|
|
19
|
+
* field dictionary. Include mode keeps the selection; exclude mode keeps
|
|
20
|
+
* the complement (a name excludes its subtree the same way it includes
|
|
21
|
+
* it). `targets === null` (the key ABSENT) selects everything and the flag
|
|
22
|
+
* is ignored; `[]` + include selects NOTHING while `[]` + exclude selects
|
|
23
|
+
* everything — presence and emptiness are different states.
|
|
24
|
+
*
|
|
25
|
+
* `listed` reports whether a field was ADDRESSED by the target list
|
|
26
|
+
* (directly or through its subtree) — the "explicitly listed" predicate the
|
|
27
|
+
* submit honesty diagnostics key on.
|
|
28
|
+
*/
|
|
29
|
+
const resolveFieldSelection = (all, targets, exclude) => {
|
|
30
|
+
if (targets === null) return {
|
|
31
|
+
selected: [...all],
|
|
32
|
+
listed: () => false
|
|
33
|
+
};
|
|
34
|
+
const names = targets.filter((t) => t.kind === "name").map((t) => t.name);
|
|
35
|
+
const objectNumbers = new Set(targets.filter((t) => t.kind === "objectNumber").map((t) => t.objectNumber));
|
|
36
|
+
const listed = (field) => field.ref.kind === "objectNumber" && objectNumbers.has(field.ref.fieldObjectNumber) || names.some((name) => field.name === name || field.name.startsWith(`${name}.`));
|
|
37
|
+
return {
|
|
38
|
+
selected: exclude ? all.filter((f) => !listed(f)) : all.filter(listed),
|
|
39
|
+
listed
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Build the resolved submission dataset from a live field list (Tables
|
|
44
|
+
* 239/240 semantics; pure — the capability supplies a FRESH engine read so
|
|
45
|
+
* no model-lag class exists):
|
|
46
|
+
*
|
|
47
|
+
* - the unconditional **NoExport veto** — even an explicit include cannot
|
|
48
|
+
* override it (diagnosed when explicitly listed, so the veto is
|
|
49
|
+
* observable, silent when swept in implicitly);
|
|
50
|
+
* - **push-buttons and signatures** never contribute entries in v1 —
|
|
51
|
+
* ISO's explicit-pushbutton-`/AP`-in-FDF rule is unrepresentable in the
|
|
52
|
+
* current DTO, so an EXPLICITLY listed one is diagnosed
|
|
53
|
+
* (`submit-entry-unsupported`), never silently dropped;
|
|
54
|
+
* - an **unsupported `/V` shape** is always diagnosed (silent omission
|
|
55
|
+
* would be data loss);
|
|
56
|
+
* - a valueless field becomes a NAME-ONLY entry (`value: null`) only under
|
|
57
|
+
* IncludeNoValueFields, else it is skipped (the ISO default);
|
|
58
|
+
* - values are entry-faithful: scalar → string, array (multi-select list
|
|
59
|
+
* box) → string[]; the submitted name is the fully qualified name.
|
|
60
|
+
*/
|
|
61
|
+
const buildSubmitEntries = (fields, intent, diagnose) => {
|
|
62
|
+
const { selected, listed } = resolveFieldSelection(fields, intent.fields, intent.exclude);
|
|
63
|
+
const explicitInclude = intent.fields !== null && !intent.exclude;
|
|
64
|
+
const entries = [];
|
|
65
|
+
for (const field of selected) {
|
|
66
|
+
const explicit = explicitInclude && listed(field);
|
|
67
|
+
if (field.family === "pushbutton" || field.family === "signature") {
|
|
68
|
+
if (explicit) diagnose({
|
|
69
|
+
code: "submit-entry-unsupported",
|
|
70
|
+
message: `submit: '${field.name}' (${field.family}) has no representable submission value in v1 — skipped`
|
|
71
|
+
});
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (field.flags.noExport) {
|
|
75
|
+
if (explicit) diagnose({
|
|
76
|
+
code: "submit-entry-unsupported",
|
|
77
|
+
message: `submit: '${field.name}' carries the NoExport flag — the veto is unconditional (ISO 12.7.6.2), skipped`
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const entry = field.valueEntry;
|
|
82
|
+
if (entry.kind === "scalar") entries.push({
|
|
83
|
+
name: field.name,
|
|
84
|
+
value: entry.value
|
|
85
|
+
});
|
|
86
|
+
else if (entry.kind === "array") entries.push({
|
|
87
|
+
name: field.name,
|
|
88
|
+
value: entry.values
|
|
89
|
+
});
|
|
90
|
+
else if (entry.kind === "none") {
|
|
91
|
+
if (intent.includeNoValueFields) entries.push({
|
|
92
|
+
name: field.name,
|
|
93
|
+
value: null
|
|
94
|
+
});
|
|
95
|
+
} else diagnose({
|
|
96
|
+
code: "submit-entry-unsupported",
|
|
97
|
+
message: `submit: '${field.name}' has an unsupported /V shape — skipped (would otherwise be silent data loss)`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return entries;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
8
103
|
//#region src/core/model.ts
|
|
9
104
|
const fieldKeyOf = (field) => field.fieldObjectNumber > 0 ? `obj:${field.fieldObjectNumber}` : `fqn:${field.name}`;
|
|
10
105
|
const initialModel = () => ({
|
|
@@ -218,507 +313,8 @@ function fillItemForWidget(model, annotObjectNumber) {
|
|
|
218
313
|
}
|
|
219
314
|
//#endregion
|
|
220
315
|
//#region src/mutationQueue.ts
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
-
let tail = Promise.resolve();
|
|
224
|
-
return (operation) => {
|
|
225
|
-
const result = tail.then(operation, operation);
|
|
226
|
-
tail = result.then(() => void 0, () => void 0);
|
|
227
|
-
return result;
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
//#endregion
|
|
231
|
-
//#region src/scripting.ts
|
|
232
|
-
const refKey = (ref) => ref.kind === "objectNumber" ? `obj:${ref.fieldObjectNumber}` : `fqn:${ref.name}`;
|
|
233
|
-
const sameRef = (left, right) => refKey(left) === refKey(right);
|
|
234
|
-
const cloneValue = (value) => Array.isArray(value) ? [...value] : value;
|
|
235
|
-
const sameValue = (left, right) => Array.isArray(left) && Array.isArray(right) ? left.length === right.length && left.every((value, index) => value === right[index]) : left === right;
|
|
236
|
-
function cloneFields(fields) {
|
|
237
|
-
return fields.map((field) => ({
|
|
238
|
-
...field,
|
|
239
|
-
ref: { ...field.ref },
|
|
240
|
-
value: cloneValue(field.value),
|
|
241
|
-
defaultValue: cloneValue(field.defaultValue),
|
|
242
|
-
...field.options ? { options: field.options.map((option) => ({ ...option })) } : {}
|
|
243
|
-
}));
|
|
244
|
-
}
|
|
245
|
-
function fieldByRef(fields, ref) {
|
|
246
|
-
return fields.find((field) => sameRef(field.ref, ref));
|
|
247
|
-
}
|
|
248
|
-
function snapshotField(snapshot, ref) {
|
|
249
|
-
return snapshot.fields.find((field) => sameRef(field.ref, ref));
|
|
250
|
-
}
|
|
251
|
-
function scriptValueFromFormValue(field, value) {
|
|
252
|
-
if (field.family === "text" && value.type === "text") return value.value;
|
|
253
|
-
if ((field.family === "checkbox" || field.family === "radio") && value.type === "toggle") return value.state ?? "Off";
|
|
254
|
-
if (field.family === "combobox" && value.type === "choice") return value.values[0] ?? "";
|
|
255
|
-
if (field.family === "listbox" && value.type === "choice") return [...value.values];
|
|
256
|
-
throw new Error(`Form value type '${value.type}' does not match field family '${field.family}'`);
|
|
257
|
-
}
|
|
258
|
-
function formValueFromScriptValue(field) {
|
|
259
|
-
switch (field.family) {
|
|
260
|
-
case "text": return {
|
|
261
|
-
type: "text",
|
|
262
|
-
value: String(field.value ?? "")
|
|
263
|
-
};
|
|
264
|
-
case "checkbox":
|
|
265
|
-
case "radio": return {
|
|
266
|
-
type: "toggle",
|
|
267
|
-
state: field.value === null || field.value === void 0 || String(field.value) === "Off" ? null : String(field.value)
|
|
268
|
-
};
|
|
269
|
-
case "combobox": return {
|
|
270
|
-
type: "choice",
|
|
271
|
-
values: Array.isArray(field.value) ? field.value.slice(0, 1) : [String(field.value ?? "")]
|
|
272
|
-
};
|
|
273
|
-
case "listbox": return {
|
|
274
|
-
type: "choice",
|
|
275
|
-
values: Array.isArray(field.value) ? [...field.value] : [String(field.value ?? "")]
|
|
276
|
-
};
|
|
277
|
-
default: return null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function applyEffects(overlay, effects) {
|
|
281
|
-
for (const effect of effects) {
|
|
282
|
-
if (effect.kind === "reset") {
|
|
283
|
-
for (const ref of effect.refs) {
|
|
284
|
-
const field = fieldByRef(overlay.fields, ref);
|
|
285
|
-
if (!field) continue;
|
|
286
|
-
field.value = cloneValue(field.defaultValue);
|
|
287
|
-
overlay.resetKeys.add(refKey(ref));
|
|
288
|
-
}
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
const field = fieldByRef(overlay.fields, effect.ref);
|
|
292
|
-
if (!field) continue;
|
|
293
|
-
const key = refKey(effect.ref);
|
|
294
|
-
if (effect.kind === "setValue") {
|
|
295
|
-
field.value = scriptValueFromFormValue(field, effect.value);
|
|
296
|
-
overlay.resetKeys.delete(key);
|
|
297
|
-
} else if (effect.kind === "setDisplay") field.display = effect.display;
|
|
298
|
-
else overlay.appearances.set(key, {
|
|
299
|
-
ref: effect.ref,
|
|
300
|
-
text: effect.text
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function canonicalEffects(overlay) {
|
|
305
|
-
const effects = [];
|
|
306
|
-
const resets = [];
|
|
307
|
-
for (const field of overlay.fields) {
|
|
308
|
-
const original = fieldByRef(overlay.original, field.ref);
|
|
309
|
-
if (!original) continue;
|
|
310
|
-
if (!sameValue(original.value, field.value)) {
|
|
311
|
-
const key = refKey(field.ref);
|
|
312
|
-
if (overlay.resetKeys.has(key) && sameValue(field.value, field.defaultValue)) resets.push(field.ref);
|
|
313
|
-
else {
|
|
314
|
-
const value = formValueFromScriptValue(field);
|
|
315
|
-
if (value) effects.push({
|
|
316
|
-
kind: "setValue",
|
|
317
|
-
ref: field.ref,
|
|
318
|
-
value
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (original.display !== field.display) effects.push({
|
|
323
|
-
kind: "setDisplay",
|
|
324
|
-
ref: field.ref,
|
|
325
|
-
display: field.display
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
if (resets.length > 0) effects.unshift({
|
|
329
|
-
kind: "reset",
|
|
330
|
-
refs: resets
|
|
331
|
-
});
|
|
332
|
-
effects.push(...Array.from(overlay.appearances.values(), ({ ref, text }) => ({
|
|
333
|
-
kind: "setAppearanceText",
|
|
334
|
-
ref,
|
|
335
|
-
text
|
|
336
|
-
})));
|
|
337
|
-
return effects;
|
|
338
|
-
}
|
|
339
|
-
function scriptError(message) {
|
|
340
|
-
return {
|
|
341
|
-
kind: "invalid-output",
|
|
342
|
-
message
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
function utf8Length(value) {
|
|
346
|
-
let length = 0;
|
|
347
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
348
|
-
const code = value.charCodeAt(index);
|
|
349
|
-
if (code < 128) length += 1;
|
|
350
|
-
else if (code < 2048) length += 2;
|
|
351
|
-
else if (code >= 55296 && code <= 56319 && index + 1 < value.length) {
|
|
352
|
-
length += 4;
|
|
353
|
-
index += 1;
|
|
354
|
-
} else length += 3;
|
|
355
|
-
}
|
|
356
|
-
return length;
|
|
357
|
-
}
|
|
358
|
-
function seedFrom(documentId, sequence) {
|
|
359
|
-
let hash = 2166136261;
|
|
360
|
-
for (const char of `${documentId}:${sequence}`) {
|
|
361
|
-
hash ^= char.charCodeAt(0);
|
|
362
|
-
hash = Math.imul(hash, 16777619);
|
|
363
|
-
}
|
|
364
|
-
return hash >>> 0;
|
|
365
|
-
}
|
|
366
|
-
function resolvedIdentity(doc, config) {
|
|
367
|
-
const claims = doc.security.identity;
|
|
368
|
-
const supplied = typeof config.identity === "function" ? config.identity() : config.identity ?? {};
|
|
369
|
-
return {
|
|
370
|
-
name: supplied.name ?? claims?.display_name ?? claims?.user_id ?? "",
|
|
371
|
-
loginName: supplied.loginName ?? claims?.user_id ?? "",
|
|
372
|
-
corporation: supplied.corporation ?? claims?.group_id ?? "",
|
|
373
|
-
email: supplied.email ?? ""
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
function pageNumberFor(meta, field) {
|
|
377
|
-
const pon = field.widgets.find((widget) => widget.pageObjectNumber > 0)?.pageObjectNumber;
|
|
378
|
-
if (!meta || pon === void 0) return 0;
|
|
379
|
-
const index = meta.pages.findIndex((page) => page.pageObjectNumber === pon);
|
|
380
|
-
return Math.max(0, index);
|
|
381
|
-
}
|
|
382
|
-
function statusFromEffects(result) {
|
|
383
|
-
if (result.results.some(({ status }) => status === "failed" || status === "skipped")) return "failed";
|
|
384
|
-
if (result.results.some(({ status }) => status === "rejected")) return "failed";
|
|
385
|
-
return result.results.some(({ status }) => status === "applied") ? "applied" : "unchanged";
|
|
386
|
-
}
|
|
387
|
-
var FormScriptingController = class {
|
|
388
|
-
constructor(options) {
|
|
389
|
-
this.options = options;
|
|
390
|
-
this.sandboxPromise = null;
|
|
391
|
-
this.booted = false;
|
|
392
|
-
this.disposed = false;
|
|
393
|
-
this.sequence = 0;
|
|
394
|
-
}
|
|
395
|
-
dispose() {
|
|
396
|
-
if (this.disposed) return;
|
|
397
|
-
this.disposed = true;
|
|
398
|
-
this.sandboxPromise?.then((sandbox) => sandbox.dispose()).catch(() => void 0);
|
|
399
|
-
}
|
|
400
|
-
async commit(snapshot, ref, proposed) {
|
|
401
|
-
return this.transact(snapshot, ref, proposed);
|
|
402
|
-
}
|
|
403
|
-
/** Execute one originating widget's activation action (`/A`, including `/Next`). */
|
|
404
|
-
async activate(snapshot, ref, action) {
|
|
405
|
-
return this.transact(snapshot, ref, void 0, action);
|
|
406
|
-
}
|
|
407
|
-
/** Run lazy document boot plus the `/CO` chain without a user field change. */
|
|
408
|
-
async recalculate(snapshot) {
|
|
409
|
-
const target = snapshot.calculationOrder.find((ref) => ref !== null && snapshotField(snapshot, ref) !== void 0) ?? snapshot.fields[0]?.ref;
|
|
410
|
-
if (!target) return {
|
|
411
|
-
status: "unchanged",
|
|
412
|
-
scripted: true,
|
|
413
|
-
effectsResult: null,
|
|
414
|
-
uiEffects: [],
|
|
415
|
-
diagnostics: []
|
|
416
|
-
};
|
|
417
|
-
return this.transact(snapshot, target);
|
|
418
|
-
}
|
|
419
|
-
async transact(snapshot, ref, proposed, activation) {
|
|
420
|
-
if (this.disposed) throw new Error("Form scripting controller is disposed");
|
|
421
|
-
const target = snapshotField(snapshot, ref);
|
|
422
|
-
if (!target) return this.failed([], [], scriptError(`Form field '${refKey(ref)}' no longer exists`));
|
|
423
|
-
const original = (0, _embedpdf_core_acrojs.scriptFieldsFromSnapshot)(snapshot);
|
|
424
|
-
const overlay = {
|
|
425
|
-
original,
|
|
426
|
-
fields: cloneFields(original),
|
|
427
|
-
resetKeys: /* @__PURE__ */ new Set(),
|
|
428
|
-
appearances: /* @__PURE__ */ new Map()
|
|
429
|
-
};
|
|
430
|
-
const targetInput = fieldByRef(overlay.fields, ref);
|
|
431
|
-
if (!targetInput) return this.failed([], [], scriptError(`Form field '${refKey(ref)}' has no script view`));
|
|
432
|
-
const hasProposedValue = proposed !== void 0;
|
|
433
|
-
let proposedValue = cloneValue(targetInput.value);
|
|
434
|
-
if (proposed) try {
|
|
435
|
-
proposedValue = scriptValueFromFormValue(targetInput, proposed);
|
|
436
|
-
} catch (error) {
|
|
437
|
-
return this.failed([], [], scriptError(error instanceof Error ? error.message : String(error)));
|
|
438
|
-
}
|
|
439
|
-
const uiEffects = [];
|
|
440
|
-
const diagnostics = [];
|
|
441
|
-
const meta = this.options.document();
|
|
442
|
-
const nowMs = this.options.config.now?.() ?? Date.now();
|
|
443
|
-
const inputBase = {
|
|
444
|
-
document: {
|
|
445
|
-
id: this.options.doc.id,
|
|
446
|
-
fileName: this.options.config.fileName?.() ?? meta?.name ?? this.options.doc.id,
|
|
447
|
-
pageCount: meta?.pageCount ?? 0,
|
|
448
|
-
pageNumber: pageNumberFor(meta, target)
|
|
449
|
-
},
|
|
450
|
-
identity: resolvedIdentity(this.options.doc, this.options.config),
|
|
451
|
-
environment: {
|
|
452
|
-
nowMs,
|
|
453
|
-
utcOffsetMinutes: this.options.config.utcOffsetMinutes?.() ?? -new Date(nowMs).getTimezoneOffset(),
|
|
454
|
-
randomSeed: this.options.config.randomSeed?.() ?? seedFrom(this.options.doc.id, this.sequence++)
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
const budget = this.options.config.budget ?? _embedpdf_core_acrojs.DEFAULT_SCRIPT_BUDGET;
|
|
458
|
-
let executionMs = 0;
|
|
459
|
-
let aggregateOutputSize = 0;
|
|
460
|
-
const consume = (output, phase) => {
|
|
461
|
-
aggregateOutputSize += utf8Length(JSON.stringify(output));
|
|
462
|
-
uiEffects.push(...output.uiEffects.map((effect) => ({
|
|
463
|
-
...effect,
|
|
464
|
-
phase
|
|
465
|
-
})));
|
|
466
|
-
diagnostics.push(...output.diagnostics);
|
|
467
|
-
if (aggregateOutputSize > budget.maxOutputBytes) return {
|
|
468
|
-
kind: "budget",
|
|
469
|
-
message: `Form script transaction output exceeded ${budget.maxOutputBytes} bytes`
|
|
470
|
-
};
|
|
471
|
-
return output.error ?? null;
|
|
472
|
-
};
|
|
473
|
-
const remainingBudget = () => {
|
|
474
|
-
const remaining = budget.maxExecutionMs - executionMs;
|
|
475
|
-
return remaining <= 0 ? null : {
|
|
476
|
-
...budget,
|
|
477
|
-
maxExecutionMs: remaining
|
|
478
|
-
};
|
|
479
|
-
};
|
|
480
|
-
const run = async (kind, source, input) => {
|
|
481
|
-
const remaining = remainingBudget();
|
|
482
|
-
if (!remaining) return { error: {
|
|
483
|
-
kind: "budget",
|
|
484
|
-
message: "Form script transaction timed out"
|
|
485
|
-
} };
|
|
486
|
-
let output;
|
|
487
|
-
try {
|
|
488
|
-
const sandbox = await this.sandbox();
|
|
489
|
-
const startedAt = Date.now();
|
|
490
|
-
output = kind === "boot" ? sandbox.boot(source, input, remaining) : sandbox.run(source, input, remaining);
|
|
491
|
-
executionMs += Date.now() - startedAt;
|
|
492
|
-
} catch (error) {
|
|
493
|
-
return { error: scriptError(error instanceof Error ? error.message : String(error)) };
|
|
494
|
-
}
|
|
495
|
-
const error = consume(output, kind === "boot" ? "boot" : "user");
|
|
496
|
-
return error ? { error } : { output };
|
|
497
|
-
};
|
|
498
|
-
if (!this.booted) {
|
|
499
|
-
this.booted = true;
|
|
500
|
-
let sources = [];
|
|
501
|
-
let bootError = null;
|
|
502
|
-
try {
|
|
503
|
-
sources = (this.options.doc.actions ? await this.options.doc.actions.read() : null)?.nameTreeScripts.map(({ action }) => (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(action)) ?? [];
|
|
504
|
-
} catch (error) {
|
|
505
|
-
bootError = scriptError(error instanceof Error ? error.message : String(error));
|
|
506
|
-
}
|
|
507
|
-
if (!bootError && sources.length > 0) {
|
|
508
|
-
const boot = await run("boot", sources, {
|
|
509
|
-
...inputBase,
|
|
510
|
-
fields: overlay.fields,
|
|
511
|
-
event: { kind: "name-tree-boot" }
|
|
512
|
-
});
|
|
513
|
-
if (boot.error) bootError = boot.error;
|
|
514
|
-
else applyEffects(overlay, boot.output.formEffects);
|
|
515
|
-
}
|
|
516
|
-
if (bootError) diagnostics.push({
|
|
517
|
-
code: "script-error",
|
|
518
|
-
message: `Document boot script failed (continuing without it): ${bootError.message}`
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
const bootEffects = canonicalEffects(overlay);
|
|
522
|
-
if (activation) {
|
|
523
|
-
let program;
|
|
524
|
-
try {
|
|
525
|
-
program = (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(activation);
|
|
526
|
-
} catch (error) {
|
|
527
|
-
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
528
|
-
}
|
|
529
|
-
if (program) {
|
|
530
|
-
const activated = await run("run", program, {
|
|
531
|
-
...inputBase,
|
|
532
|
-
fields: overlay.fields,
|
|
533
|
-
event: {
|
|
534
|
-
kind: "widget-activate",
|
|
535
|
-
target: ref,
|
|
536
|
-
source: ref,
|
|
537
|
-
value: targetInput.value
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
if (activated.error) return this.failed(uiEffects, diagnostics, activated.error);
|
|
541
|
-
applyEffects(overlay, activated.output.formEffects);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (!activation && hasProposedValue && target.actions?.keystroke && target.family === "text") {
|
|
545
|
-
let program;
|
|
546
|
-
try {
|
|
547
|
-
program = (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(target.actions.keystroke);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
550
|
-
}
|
|
551
|
-
if (program) {
|
|
552
|
-
const oldValue = String(targetInput.value ?? "");
|
|
553
|
-
const key = await run("run", program, {
|
|
554
|
-
...inputBase,
|
|
555
|
-
fields: overlay.fields,
|
|
556
|
-
event: {
|
|
557
|
-
kind: "field-keystroke",
|
|
558
|
-
target: ref,
|
|
559
|
-
source: ref,
|
|
560
|
-
value: oldValue,
|
|
561
|
-
change: String(proposedValue ?? ""),
|
|
562
|
-
selStart: 0,
|
|
563
|
-
selEnd: oldValue.length,
|
|
564
|
-
willCommit: true
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
if (key.error) return this.failed(uiEffects, diagnostics, key.error);
|
|
568
|
-
if (!key.output.event.rc) return this.finishRejected(bootEffects, uiEffects, diagnostics);
|
|
569
|
-
applyEffects(overlay, key.output.formEffects);
|
|
570
|
-
const event = key.output.event;
|
|
571
|
-
const base = String(event.value ?? "");
|
|
572
|
-
proposedValue = base.slice(0, event.selStart) + event.change + base.slice(Math.max(event.selStart, event.selEnd));
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (!activation && hasProposedValue) {
|
|
576
|
-
targetInput.value = cloneValue(proposedValue);
|
|
577
|
-
overlay.resetKeys.delete(refKey(ref));
|
|
578
|
-
}
|
|
579
|
-
if (!activation && hasProposedValue && target.actions?.validate) {
|
|
580
|
-
let program;
|
|
581
|
-
try {
|
|
582
|
-
program = (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(target.actions.validate);
|
|
583
|
-
} catch (error) {
|
|
584
|
-
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
585
|
-
}
|
|
586
|
-
if (program) {
|
|
587
|
-
const validation = await run("run", program, {
|
|
588
|
-
...inputBase,
|
|
589
|
-
fields: overlay.fields,
|
|
590
|
-
event: {
|
|
591
|
-
kind: "field-validate",
|
|
592
|
-
target: ref,
|
|
593
|
-
source: ref,
|
|
594
|
-
value: proposedValue,
|
|
595
|
-
willCommit: true
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
if (validation.error) return this.failed(uiEffects, diagnostics, validation.error);
|
|
599
|
-
if (!validation.output.event.rc) return this.finishRejected(bootEffects, uiEffects, diagnostics);
|
|
600
|
-
targetInput.value = cloneValue(validation.output.event.value);
|
|
601
|
-
applyEffects(overlay, validation.output.formEffects);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
const formatRefs = !activation && hasProposedValue ? [ref] : [];
|
|
605
|
-
for (const calculationRef of activation ? [] : snapshot.calculationOrder) {
|
|
606
|
-
if (!calculationRef) continue;
|
|
607
|
-
const calculated = snapshotField(snapshot, calculationRef);
|
|
608
|
-
const tree = calculated?.actions?.calculate;
|
|
609
|
-
if (!calculated || !tree) continue;
|
|
610
|
-
let program;
|
|
611
|
-
try {
|
|
612
|
-
program = (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(tree);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
615
|
-
}
|
|
616
|
-
const calculatedInput = fieldByRef(overlay.fields, calculationRef);
|
|
617
|
-
if (program && calculatedInput) {
|
|
618
|
-
const calculation = await run("run", program, {
|
|
619
|
-
...inputBase,
|
|
620
|
-
fields: overlay.fields,
|
|
621
|
-
event: {
|
|
622
|
-
kind: "field-calculate",
|
|
623
|
-
target: calculationRef,
|
|
624
|
-
...hasProposedValue ? { source: ref } : {},
|
|
625
|
-
value: calculatedInput.value
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
if (calculation.error) return this.failed(uiEffects, diagnostics, calculation.error);
|
|
629
|
-
applyEffects(overlay, calculation.output.formEffects);
|
|
630
|
-
}
|
|
631
|
-
if (!formatRefs.some((candidate) => sameRef(candidate, calculationRef))) formatRefs.push(calculationRef);
|
|
632
|
-
}
|
|
633
|
-
for (const formatRef of formatRefs) {
|
|
634
|
-
const field = snapshotField(snapshot, formatRef);
|
|
635
|
-
const tree = field?.actions?.format;
|
|
636
|
-
const overlayField = fieldByRef(overlay.fields, formatRef);
|
|
637
|
-
if (!field || !tree || !overlayField) continue;
|
|
638
|
-
let program;
|
|
639
|
-
try {
|
|
640
|
-
program = (0, _embedpdf_core_acrojs.javaScriptProgramFromActionTree)(tree);
|
|
641
|
-
} catch (error) {
|
|
642
|
-
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
643
|
-
}
|
|
644
|
-
if (!program) continue;
|
|
645
|
-
const format = await run("run", program, {
|
|
646
|
-
...inputBase,
|
|
647
|
-
fields: overlay.fields,
|
|
648
|
-
event: {
|
|
649
|
-
kind: "field-format",
|
|
650
|
-
target: formatRef,
|
|
651
|
-
...hasProposedValue ? { source: ref } : {},
|
|
652
|
-
value: overlayField.value
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
if (format.error) return this.failed(uiEffects, diagnostics, format.error);
|
|
656
|
-
applyEffects(overlay, format.output.formEffects);
|
|
657
|
-
}
|
|
658
|
-
const effects = canonicalEffects(overlay);
|
|
659
|
-
if (effects.length + uiEffects.length > budget.maxEffects) return this.failed(uiEffects, diagnostics, {
|
|
660
|
-
kind: "budget",
|
|
661
|
-
message: `Form script transaction exceeded ${budget.maxEffects} effects`
|
|
662
|
-
});
|
|
663
|
-
if (effects.length === 0) return {
|
|
664
|
-
status: "unchanged",
|
|
665
|
-
scripted: true,
|
|
666
|
-
effectsResult: null,
|
|
667
|
-
uiEffects,
|
|
668
|
-
diagnostics
|
|
669
|
-
};
|
|
670
|
-
if (!this.options.doc.forms.applyEffects) return this.failed(uiEffects, diagnostics, scriptError("This engine does not support batched form effects"));
|
|
671
|
-
const effectsResult = await this.options.doc.forms.applyEffects(effects);
|
|
672
|
-
const status = statusFromEffects(effectsResult);
|
|
673
|
-
return {
|
|
674
|
-
status,
|
|
675
|
-
scripted: true,
|
|
676
|
-
effectsResult,
|
|
677
|
-
uiEffects,
|
|
678
|
-
diagnostics,
|
|
679
|
-
...status === "failed" ? { error: scriptError("One or more native form effects failed") } : {}
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
async sandbox() {
|
|
683
|
-
this.sandboxPromise ?? (this.sandboxPromise = this.options.sandboxFactory().then((sandbox) => {
|
|
684
|
-
if (this.disposed) sandbox.dispose();
|
|
685
|
-
return sandbox;
|
|
686
|
-
}));
|
|
687
|
-
return this.sandboxPromise;
|
|
688
|
-
}
|
|
689
|
-
failed(uiEffects, diagnostics, error) {
|
|
690
|
-
return {
|
|
691
|
-
status: "failed",
|
|
692
|
-
scripted: true,
|
|
693
|
-
effectsResult: null,
|
|
694
|
-
uiEffects,
|
|
695
|
-
diagnostics,
|
|
696
|
-
error
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
async finishRejected(bootEffects, uiEffects, diagnostics) {
|
|
700
|
-
if (bootEffects.length === 0) return {
|
|
701
|
-
status: "rejected",
|
|
702
|
-
scripted: true,
|
|
703
|
-
effectsResult: null,
|
|
704
|
-
uiEffects,
|
|
705
|
-
diagnostics
|
|
706
|
-
};
|
|
707
|
-
if (!this.options.doc.forms.applyEffects) return this.failed(uiEffects, diagnostics, scriptError("Batched form effects are unavailable"));
|
|
708
|
-
const effectsResult = await this.options.doc.forms.applyEffects(bootEffects);
|
|
709
|
-
if (statusFromEffects(effectsResult) === "failed") return this.failed(uiEffects, diagnostics, scriptError("A name-tree boot effect failed"));
|
|
710
|
-
return {
|
|
711
|
-
status: "rejected",
|
|
712
|
-
scripted: true,
|
|
713
|
-
effectsResult,
|
|
714
|
-
uiEffects,
|
|
715
|
-
diagnostics
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
function createFormScriptingController(options) {
|
|
720
|
-
return new FormScriptingController(options);
|
|
721
|
-
}
|
|
316
|
+
/** The core serial queue under its historical form-plugin name. */
|
|
317
|
+
const createSerialMutationQueue = _embedpdf_core.createSerialQueue;
|
|
722
318
|
//#endregion
|
|
723
319
|
//#region src/capability.ts
|
|
724
320
|
/** PDF user-space rect (y-up) → content-space box (y-down, crop-relative). */
|
|
@@ -758,29 +354,29 @@ function createFormCapability(ctx, options = {}) {
|
|
|
758
354
|
};
|
|
759
355
|
};
|
|
760
356
|
const enqueueMutation = createSerialMutationQueue();
|
|
761
|
-
const
|
|
357
|
+
const actionsHost = ctx.tryGet(_embedpdf_plugin_actions_contract_host.ActionsToken);
|
|
358
|
+
const scriptPort = actionsHost?.scriptTransaction?.bind(actionsHost) ?? null;
|
|
359
|
+
const scripting = scriptPort && ctx.doc ? require_scripting.createFormScriptingController({
|
|
762
360
|
doc: ctx.doc,
|
|
763
361
|
document: () => ctx.document(),
|
|
764
|
-
|
|
765
|
-
sandboxFactory: options.scripting.sandboxFactory ?? (() => import("@embedpdf/core-js-sandbox").then(({ createQuickJsSandbox }) => createQuickJsSandbox()))
|
|
362
|
+
transaction: scriptPort
|
|
766
363
|
}) : null;
|
|
767
364
|
if (scripting) ctx.cleanup(() => scripting.dispose());
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
365
|
+
/** Every script surface (UI effects, diagnostics, errors) flows through
|
|
366
|
+
* the actions plugin's ONE port — origin/phase attached (D9). */
|
|
367
|
+
const surfaceViaActions = (result, origin) => {
|
|
368
|
+
if (!actionsHost) return;
|
|
369
|
+
for (const phase of ["boot", "user"]) {
|
|
370
|
+
const uiEffects = result.uiEffects.filter((effect) => effect.phase === phase);
|
|
371
|
+
if (uiEffects.length === 0 && phase === "boot") continue;
|
|
372
|
+
actionsHost.surfaceScriptResult({
|
|
373
|
+
uiEffects,
|
|
374
|
+
diagnostics: phase === "user" ? result.diagnostics : [],
|
|
375
|
+
...phase === "user" && result.error ? { error: result.error } : {},
|
|
376
|
+
origin,
|
|
377
|
+
phase
|
|
378
|
+
});
|
|
781
379
|
}
|
|
782
|
-
for (const diagnostic of result.diagnostics) observe(options.scripting?.onDiagnostic ? () => options.scripting.onDiagnostic(diagnostic) : void 0);
|
|
783
|
-
if (result.error) observe(options.scripting?.onError ? () => options.scripting.onError(result.error) : void 0);
|
|
784
380
|
};
|
|
785
381
|
const can = (cap) => ctx.doc?.security.allows(cap) ?? false;
|
|
786
382
|
let refreshPromise = null;
|
|
@@ -861,8 +457,8 @@ function createFormCapability(ctx, options = {}) {
|
|
|
861
457
|
const doc = ctx.doc;
|
|
862
458
|
if (!doc) throw new Error("no document");
|
|
863
459
|
if (scripting) {
|
|
864
|
-
const result = await scripting.commit(
|
|
865
|
-
|
|
460
|
+
const result = await scripting.commit(ref, value);
|
|
461
|
+
surfaceViaActions(result, "user");
|
|
866
462
|
if (result.effectsResult !== null) await refresh(true);
|
|
867
463
|
return result;
|
|
868
464
|
}
|
|
@@ -903,7 +499,7 @@ function createFormCapability(ctx, options = {}) {
|
|
|
903
499
|
}
|
|
904
500
|
});
|
|
905
501
|
};
|
|
906
|
-
const annotationHost = ctx.tryGet(
|
|
502
|
+
const annotationHost = ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
|
|
907
503
|
const annotationActivation = async (ref) => {
|
|
908
504
|
const doc = ctx.doc;
|
|
909
505
|
if (!doc) return null;
|
|
@@ -914,8 +510,7 @@ function createFormCapability(ctx, options = {}) {
|
|
|
914
510
|
return annotation?.subtype === "widget" ? annotation.actions?.activate ?? null : null;
|
|
915
511
|
};
|
|
916
512
|
const activateWidgetNow = async (key, annotationRef) => {
|
|
917
|
-
|
|
918
|
-
if (!doc) throw new Error("no document");
|
|
513
|
+
if (!ctx.doc) throw new Error("no document");
|
|
919
514
|
if (!scripting) return {
|
|
920
515
|
status: "unchanged",
|
|
921
516
|
scripted: false,
|
|
@@ -931,11 +526,118 @@ function createFormCapability(ctx, options = {}) {
|
|
|
931
526
|
uiEffects: [],
|
|
932
527
|
diagnostics: []
|
|
933
528
|
};
|
|
934
|
-
const result = await scripting.activate(
|
|
935
|
-
|
|
529
|
+
const result = await scripting.activate(refKeyOf(key), action);
|
|
530
|
+
surfaceViaActions(result, "user");
|
|
936
531
|
if (result.effectsResult !== null) await refresh(true);
|
|
937
532
|
return result;
|
|
938
533
|
};
|
|
534
|
+
const NOT_SCRIPTED = {
|
|
535
|
+
status: "unchanged",
|
|
536
|
+
scripted: false,
|
|
537
|
+
effectsResult: null,
|
|
538
|
+
uiEffects: [],
|
|
539
|
+
diagnostics: []
|
|
540
|
+
};
|
|
541
|
+
/**
|
|
542
|
+
* The shared reset core: one effects batch → refresh → V/C/F
|
|
543
|
+
* recalculation when the transaction port is present. Ridden by BOTH
|
|
544
|
+
* doors — the /ResetForm executor and the public `reset(key)` — so the
|
|
545
|
+
* two can never diverge again (the "reset() asymmetry" fix). `origin` is
|
|
546
|
+
* PRESERVED through recalculation surfacing: a lifecycle/hover ResetForm
|
|
547
|
+
* can no longer launder its alerts into user origin.
|
|
548
|
+
*/
|
|
549
|
+
const applyResetBatch = async (refs, origin) => {
|
|
550
|
+
const doc = ctx.doc;
|
|
551
|
+
if (!doc) throw new Error("no document");
|
|
552
|
+
if (!doc.forms.applyEffects) {
|
|
553
|
+
const result = {
|
|
554
|
+
...NOT_SCRIPTED,
|
|
555
|
+
diagnostics: [{
|
|
556
|
+
code: "unsupported-api",
|
|
557
|
+
message: "this engine has no form-effects batch door"
|
|
558
|
+
}]
|
|
559
|
+
};
|
|
560
|
+
surfaceViaActions(result, origin);
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
if (refs.length === 0) return NOT_SCRIPTED;
|
|
564
|
+
const effectsResult = await doc.forms.applyEffects([{
|
|
565
|
+
kind: "reset",
|
|
566
|
+
refs
|
|
567
|
+
}]);
|
|
568
|
+
const resetFailed = effectsResult.results.some((entry) => entry.status === "failed" || entry.status === "rejected");
|
|
569
|
+
await refresh(true);
|
|
570
|
+
if (resetFailed) return {
|
|
571
|
+
status: "failed",
|
|
572
|
+
scripted: false,
|
|
573
|
+
effectsResult,
|
|
574
|
+
uiEffects: [],
|
|
575
|
+
diagnostics: []
|
|
576
|
+
};
|
|
577
|
+
let recalc = null;
|
|
578
|
+
if (scripting) {
|
|
579
|
+
recalc = await scripting.recalculate();
|
|
580
|
+
surfaceViaActions(recalc, origin);
|
|
581
|
+
if (recalc.effectsResult !== null) await refresh(true);
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
status: "applied",
|
|
585
|
+
scripted: recalc !== null,
|
|
586
|
+
effectsResult,
|
|
587
|
+
uiEffects: recalc?.uiEffects ?? [],
|
|
588
|
+
diagnostics: recalc?.diagnostics ?? [],
|
|
589
|
+
...recalc?.error ? { error: recalc.error } : {}
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
const resetFormAction = (targets, exclude, origin = "user") => enqueueMutation(async () => {
|
|
593
|
+
const doc = ctx.doc;
|
|
594
|
+
if (!doc) throw new Error("no document");
|
|
595
|
+
const { selected } = resolveFieldSelection((await doc.forms.list()).fields, targets, exclude);
|
|
596
|
+
const resettable = selected.filter((f) => f.family !== "pushbutton" && f.family !== "signature");
|
|
597
|
+
return applyResetBatch(resettable.map((f) => f.ref), origin);
|
|
598
|
+
});
|
|
599
|
+
/**
|
|
600
|
+
* The submit dataset resolver (Phase 4, D7): a FRESH engine read — never
|
|
601
|
+
* the cached model, so no staleness class exists — then the pure ISO
|
|
602
|
+
* builder. Selection/veto/value semantics live in `field-selection.ts`;
|
|
603
|
+
* this door only supplies the live fields and assembles the request.
|
|
604
|
+
*/
|
|
605
|
+
const resolveSubmitDataset = async (intent, actionCtx, diagnose) => {
|
|
606
|
+
const doc = ctx.doc;
|
|
607
|
+
if (!doc) throw new Error("no document");
|
|
608
|
+
const snapshot = await doc.forms.list();
|
|
609
|
+
return {
|
|
610
|
+
url: intent.url,
|
|
611
|
+
method: intent.method,
|
|
612
|
+
format: intent.format,
|
|
613
|
+
flagsRaw: intent.flagsRaw,
|
|
614
|
+
...intent.charSet === void 0 ? {} : { charSet: intent.charSet },
|
|
615
|
+
entries: buildSubmitEntries(snapshot.fields, intent, diagnose),
|
|
616
|
+
origin: actionCtx.origin,
|
|
617
|
+
event: actionCtx.event
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
const widgetSource = (key, annotationRef) => ({
|
|
621
|
+
kind: "widget",
|
|
622
|
+
field: refKeyOf(key),
|
|
623
|
+
annotation: annotationRef,
|
|
624
|
+
pon: annotationRef.pageObjectNumber
|
|
625
|
+
});
|
|
626
|
+
/** One shared hover pump (Exit→Enter as one ordered pair; intermediates
|
|
627
|
+
* skipped), created on first use against the resolved actions capability. */
|
|
628
|
+
let widgetPump = null;
|
|
629
|
+
const widgetHoverPump = (actions) => {
|
|
630
|
+
widgetPump ?? (widgetPump = (0, _embedpdf_plugin_actions_contract.createHoverPump)(actions.dispatch));
|
|
631
|
+
return widgetPump;
|
|
632
|
+
};
|
|
633
|
+
const widgetHoverFlags = (annotationRef) => {
|
|
634
|
+
const loaded = annotationHost?.get(annotationRef);
|
|
635
|
+
if (loaded?.subtype !== "widget") return null;
|
|
636
|
+
return {
|
|
637
|
+
enter: Boolean(loaded.actions?.cursorEnter?.root),
|
|
638
|
+
exit: Boolean(loaded.actions?.cursorExit?.root)
|
|
639
|
+
};
|
|
640
|
+
};
|
|
939
641
|
const nudgeAnnotations = (pons) => {
|
|
940
642
|
if (!annotationHost) return;
|
|
941
643
|
for (const pon of new Set(pons)) annotationHost.reloadPage(pon);
|
|
@@ -1089,12 +791,27 @@ function createFormCapability(ctx, options = {}) {
|
|
|
1089
791
|
key
|
|
1090
792
|
});
|
|
1091
793
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
key
|
|
1096
|
-
field
|
|
1097
|
-
|
|
794
|
+
if (doc.forms.applyEffects) {
|
|
795
|
+
const result = await applyResetBatch([refKeyOf(key)], "user");
|
|
796
|
+
if (result.status === "failed") throw new Error(result.effectsResult?.results.find((entry) => entry.status === "failed" || entry.status === "rejected")?.error?.message ?? "reset failed");
|
|
797
|
+
const field = fieldByKey(model(), key);
|
|
798
|
+
if (field) apply({
|
|
799
|
+
t: "writeDone",
|
|
800
|
+
key,
|
|
801
|
+
field
|
|
802
|
+
});
|
|
803
|
+
else apply({
|
|
804
|
+
t: "writeFailed",
|
|
805
|
+
key
|
|
806
|
+
});
|
|
807
|
+
} else {
|
|
808
|
+
const result = await doc.forms.reset(refKeyOf(key));
|
|
809
|
+
apply({
|
|
810
|
+
t: "writeDone",
|
|
811
|
+
key,
|
|
812
|
+
field: result.field
|
|
813
|
+
});
|
|
814
|
+
}
|
|
1098
815
|
} catch (err) {
|
|
1099
816
|
apply({
|
|
1100
817
|
t: "writeFailed",
|
|
@@ -1105,9 +822,96 @@ function createFormCapability(ctx, options = {}) {
|
|
|
1105
822
|
});
|
|
1106
823
|
},
|
|
1107
824
|
commitValue: (ref, value) => enqueueMutation(() => commitValueNow(ref, value)),
|
|
1108
|
-
activateWidget: (key, annotationRef) =>
|
|
1109
|
-
|
|
1110
|
-
|
|
825
|
+
activateWidget: async (key, annotationRef) => {
|
|
826
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
|
|
827
|
+
if (actions) {
|
|
828
|
+
const result = await actions.dispatch({
|
|
829
|
+
scope: "activate",
|
|
830
|
+
ref: annotationRef,
|
|
831
|
+
pon: annotationRef.pageObjectNumber,
|
|
832
|
+
source: widgetSource(key, annotationRef)
|
|
833
|
+
});
|
|
834
|
+
if (!(result.status === "inert" && result.steps.length === 0 && result.diagnostics.length === 0)) return {
|
|
835
|
+
kind: "dispatched",
|
|
836
|
+
result
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
kind: "form",
|
|
841
|
+
result: await enqueueMutation(() => activateWidgetNow(key, annotationRef))
|
|
842
|
+
};
|
|
843
|
+
},
|
|
844
|
+
notifyWidgetEvent: (key, annotationRef, event) => {
|
|
845
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
|
|
846
|
+
if (!actions) return;
|
|
847
|
+
const source = widgetSource(key, annotationRef);
|
|
848
|
+
if (event === "cursorEnter" || event === "cursorExit") {
|
|
849
|
+
if (event === "cursorExit") {
|
|
850
|
+
widgetHoverPump(actions).hover(null);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const flags = widgetHoverFlags(annotationRef);
|
|
854
|
+
if (flags && !flags.enter && !flags.exit) return;
|
|
855
|
+
widgetHoverPump(actions).hover({
|
|
856
|
+
ref: annotationRef,
|
|
857
|
+
pon: annotationRef.pageObjectNumber,
|
|
858
|
+
source,
|
|
859
|
+
...flags ? { events: flags } : {}
|
|
860
|
+
});
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
actions.dispatch({
|
|
864
|
+
scope: "annotation",
|
|
865
|
+
event,
|
|
866
|
+
ref: annotationRef,
|
|
867
|
+
pon: annotationRef.pageObjectNumber,
|
|
868
|
+
source
|
|
869
|
+
});
|
|
870
|
+
},
|
|
871
|
+
resetFormAction,
|
|
872
|
+
resolveSubmitDataset,
|
|
873
|
+
commitScriptFormEffects: async (effects) => {
|
|
874
|
+
const doc = ctx.doc;
|
|
875
|
+
if (!doc?.forms.applyEffects) return {
|
|
876
|
+
results: effects.map((_, index) => ({
|
|
877
|
+
index,
|
|
878
|
+
status: "failed",
|
|
879
|
+
fields: [],
|
|
880
|
+
changedWidgets: [],
|
|
881
|
+
error: {
|
|
882
|
+
code: "NotSupported",
|
|
883
|
+
message: "no form-effects batch door"
|
|
884
|
+
}
|
|
885
|
+
})),
|
|
886
|
+
changedWidgets: [],
|
|
887
|
+
meta: null
|
|
888
|
+
};
|
|
889
|
+
let result;
|
|
890
|
+
try {
|
|
891
|
+
result = await doc.forms.applyEffects(effects);
|
|
892
|
+
} catch (error) {
|
|
893
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
894
|
+
return {
|
|
895
|
+
results: effects.map((_, index) => ({
|
|
896
|
+
index,
|
|
897
|
+
status: "failed",
|
|
898
|
+
fields: [],
|
|
899
|
+
changedWidgets: [],
|
|
900
|
+
error: {
|
|
901
|
+
code: "Refused",
|
|
902
|
+
message
|
|
903
|
+
}
|
|
904
|
+
})),
|
|
905
|
+
changedWidgets: [],
|
|
906
|
+
meta: null
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
await refresh(true);
|
|
910
|
+
if (annotationHost) {
|
|
911
|
+
const pons = new Set(result.changedWidgets.map((widget) => widget.pageObjectNumber).filter((pon) => pon > 0));
|
|
912
|
+
for (const pon of pons) await annotationHost.reloadPage(pon);
|
|
913
|
+
}
|
|
914
|
+
return result;
|
|
1111
915
|
},
|
|
1112
916
|
setValue: (ref, value) => enqueueMutation(async () => {
|
|
1113
917
|
const doc = ctx.doc;
|
|
@@ -1309,11 +1113,11 @@ function createPlaceHandler(form, interaction, annotation) {
|
|
|
1309
1113
|
},
|
|
1310
1114
|
onMove: (s) => {
|
|
1311
1115
|
if (!origin) return;
|
|
1312
|
-
const point = (0,
|
|
1116
|
+
const point = (0, _embedpdf_plugin_interaction_contract.samplePointOn)(s, origin.pon);
|
|
1313
1117
|
if (!point) return;
|
|
1314
1118
|
origin.last = point;
|
|
1315
1119
|
const box = rectFrom(origin.start, point);
|
|
1316
|
-
if (annotation) if (Math.max(box.width, box.height) >=
|
|
1120
|
+
if (annotation) if (Math.max(box.width, box.height) >= _embedpdf_plugin_annotation_authoring.MIN_DRAG) annotation.setPlacementPreview(interaction.activeToolId(), origin.pon, box);
|
|
1317
1121
|
else annotation.clearPlacementPreview();
|
|
1318
1122
|
},
|
|
1319
1123
|
onUp: (s) => {
|
|
@@ -1324,16 +1128,16 @@ function createPlaceHandler(form, interaction, annotation) {
|
|
|
1324
1128
|
const toolId = interaction.activeToolId();
|
|
1325
1129
|
const tool = FORM_TOOL_BY_ID.get(toolId);
|
|
1326
1130
|
if (!tool) return;
|
|
1327
|
-
const end = (0,
|
|
1131
|
+
const end = (0, _embedpdf_plugin_interaction_contract.samplePointOn)(s, o.pon) ?? o.last;
|
|
1328
1132
|
const dragged = rectFrom(o.start, end);
|
|
1329
|
-
const isClick = dragged.width <
|
|
1133
|
+
const isClick = dragged.width < _embedpdf_plugin_annotation_authoring.MIN_DRAG && dragged.height < _embedpdf_plugin_annotation_authoring.MIN_DRAG;
|
|
1330
1134
|
const pageBox = form.pageBox(o.pon);
|
|
1331
1135
|
const box = isClick ? boxOfClick(o.start, tool.clickCreate, pageBox) : dragged;
|
|
1332
1136
|
form.placeField({
|
|
1333
1137
|
family: tool.family,
|
|
1334
1138
|
pageObjectNumber: o.pon,
|
|
1335
1139
|
box,
|
|
1336
|
-
appearance: (0,
|
|
1140
|
+
appearance: (0, _embedpdf_plugin_annotation_authoring.widgetAppearanceFromProps)(annotation ? annotation.currentDefaults(toolId) : tool.defaults)
|
|
1337
1141
|
}).then((placed) => {
|
|
1338
1142
|
if (!annotation || interaction.activeToolId() !== toolId) return;
|
|
1339
1143
|
const widget = placed.widget;
|
|
@@ -1352,7 +1156,7 @@ function createPlaceHandler(form, interaction, annotation) {
|
|
|
1352
1156
|
/** The click-create box through the SHARED placement layer (fields are boxes;
|
|
1353
1157
|
* the policy anchor + page clamp are exactly the annotation click's). */
|
|
1354
1158
|
function boxOfClick(point, policy, pageBox) {
|
|
1355
|
-
const placement = (0,
|
|
1159
|
+
const placement = (0, _embedpdf_plugin_annotation_authoring.resolveClickPlacement)(point, policy, { pageBox: pageBox ?? void 0 });
|
|
1356
1160
|
return placement.kind === "box" ? placement.rect : {
|
|
1357
1161
|
x: point.x,
|
|
1358
1162
|
y: point.y,
|
|
@@ -1366,9 +1170,6 @@ const initialFormState = () => ({ model: initialModel() });
|
|
|
1366
1170
|
/** Dumb store: the pure core computes the next model; the shell dispatches it. */
|
|
1367
1171
|
const formReducer = (state, action) => action.type === "SET_MODEL" ? { model: action.model } : state;
|
|
1368
1172
|
//#endregion
|
|
1369
|
-
//#region src/types.ts
|
|
1370
|
-
const FormToken = (0, _embedpdf_core.createCapabilityToken)("form");
|
|
1371
|
-
//#endregion
|
|
1372
1173
|
//#region src/form.plugin.ts
|
|
1373
1174
|
/**
|
|
1374
1175
|
* The form plugin: the FIELD plane. Document-scoped; requires the
|
|
@@ -1383,17 +1184,17 @@ const FormToken = (0, _embedpdf_core.createCapabilityToken)("form");
|
|
|
1383
1184
|
*/
|
|
1384
1185
|
const formPlugin = (options = {}) => (0, _embedpdf_core.definePlugin)({
|
|
1385
1186
|
id: "form",
|
|
1386
|
-
token: FormToken,
|
|
1187
|
+
token: require_types.FormToken,
|
|
1387
1188
|
scope: "document",
|
|
1388
|
-
requires: [
|
|
1389
|
-
optional: [
|
|
1189
|
+
requires: [_embedpdf_plugin_interaction_contract.InteractionToken],
|
|
1190
|
+
optional: [_embedpdf_plugin_annotation_contract.AnnotationToken, _embedpdf_plugin_actions_contract.ActionsToken],
|
|
1390
1191
|
initialState: initialFormState,
|
|
1391
1192
|
reduce: formReducer,
|
|
1392
1193
|
capability: (ctx) => createFormCapability(ctx, options),
|
|
1393
1194
|
effects: registerFormEffects,
|
|
1394
1195
|
init: (ctx) => {
|
|
1395
|
-
const interaction = ctx.get(
|
|
1396
|
-
const form = ctx.get(FormToken);
|
|
1196
|
+
const interaction = ctx.get(_embedpdf_plugin_interaction_contract.InteractionToken);
|
|
1197
|
+
const form = ctx.get(require_types.FormToken);
|
|
1397
1198
|
interaction.registerTool({
|
|
1398
1199
|
id: "form-fill",
|
|
1399
1200
|
cursor: "default",
|
|
@@ -1404,7 +1205,7 @@ const formPlugin = (options = {}) => (0, _embedpdf_core.definePlugin)({
|
|
|
1404
1205
|
cursor: "default",
|
|
1405
1206
|
enables: /* @__PURE__ */ new Set(["annotation-edit", "annotation-marquee"])
|
|
1406
1207
|
});
|
|
1407
|
-
const annotation = ctx.tryGet(
|
|
1208
|
+
const annotation = ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
|
|
1408
1209
|
for (const t of FORM_TOOLS) if (annotation) annotation.registerTool({
|
|
1409
1210
|
id: t.id,
|
|
1410
1211
|
subtype: t.visualKind,
|
|
@@ -1424,14 +1225,57 @@ const formPlugin = (options = {}) => (0, _embedpdf_core.definePlugin)({
|
|
|
1424
1225
|
matches: (a) => a.subtype.startsWith("widget"),
|
|
1425
1226
|
engaged: () => interaction.activeTool()?.enables.has("form-fill") ?? false
|
|
1426
1227
|
});
|
|
1228
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract_host.ActionsToken);
|
|
1229
|
+
if (actions) {
|
|
1230
|
+
ctx.cleanup(actions.registerFormCommitSink((effects) => {
|
|
1231
|
+
const formHost = ctx.tryGet(require_types.FormToken);
|
|
1232
|
+
if (!formHost) return Promise.resolve({
|
|
1233
|
+
results: effects.map((_, index) => ({
|
|
1234
|
+
index,
|
|
1235
|
+
status: "failed",
|
|
1236
|
+
fields: [],
|
|
1237
|
+
changedWidgets: []
|
|
1238
|
+
})),
|
|
1239
|
+
changedWidgets: [],
|
|
1240
|
+
meta: null
|
|
1241
|
+
});
|
|
1242
|
+
return formHost.commitScriptFormEffects(effects);
|
|
1243
|
+
}));
|
|
1244
|
+
ctx.cleanup(actions.registerSubmitResolver((intent, actionCtx, diagnose) => {
|
|
1245
|
+
const formHost = ctx.tryGet(require_types.FormToken);
|
|
1246
|
+
if (!formHost) throw new Error("form plugin unavailable");
|
|
1247
|
+
return formHost.resolveSubmitDataset(intent, actionCtx, diagnose);
|
|
1248
|
+
}));
|
|
1249
|
+
ctx.cleanup(actions.registerExecutor("reset-form", async (node, actionCtx) => {
|
|
1250
|
+
if (node.type !== "reset-form") return {
|
|
1251
|
+
status: "inert",
|
|
1252
|
+
reason: "not a reset-form node"
|
|
1253
|
+
};
|
|
1254
|
+
const formHost = ctx.tryGet(require_types.FormToken);
|
|
1255
|
+
if (!formHost) return {
|
|
1256
|
+
status: "inert",
|
|
1257
|
+
reason: "form plugin unavailable"
|
|
1258
|
+
};
|
|
1259
|
+
const result = await formHost.resetFormAction(node.fields, node.exclude, actionCtx.origin);
|
|
1260
|
+
if (result.status === "failed") return {
|
|
1261
|
+
status: "failed",
|
|
1262
|
+
error: result.error?.message ?? "reset failed"
|
|
1263
|
+
};
|
|
1264
|
+
if (result.effectsResult === null) return {
|
|
1265
|
+
status: "inert",
|
|
1266
|
+
reason: result.diagnostics[0]?.message ?? "reset-form resolved no fields"
|
|
1267
|
+
};
|
|
1268
|
+
return { status: "executed" };
|
|
1269
|
+
}));
|
|
1270
|
+
}
|
|
1427
1271
|
}
|
|
1428
1272
|
});
|
|
1429
1273
|
//#endregion
|
|
1430
1274
|
exports.FORM_TOOLS = FORM_TOOLS;
|
|
1431
1275
|
exports.FORM_TOOL_BY_ID = FORM_TOOL_BY_ID;
|
|
1432
|
-
exports.FormScriptingController = FormScriptingController;
|
|
1433
|
-
exports.FormToken = FormToken;
|
|
1434
|
-
exports.createFormScriptingController = createFormScriptingController;
|
|
1276
|
+
exports.FormScriptingController = require_scripting.FormScriptingController;
|
|
1277
|
+
exports.FormToken = require_contract.FormToken;
|
|
1278
|
+
exports.createFormScriptingController = require_scripting.createFormScriptingController;
|
|
1435
1279
|
exports.createSerialMutationQueue = createSerialMutationQueue;
|
|
1436
1280
|
exports.fieldKeyOf = fieldKeyOf;
|
|
1437
1281
|
exports.formPlugin = formPlugin;
|