@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scripting.js","names":[],"sources":["../src/scripting.ts"],"sourcesContent":["import type {\n DocumentHandle,\n FormEffect,\n FormEffectsResult,\n FormFieldDTO,\n FormFieldRef,\n FormFieldValue,\n FormSnapshot,\n PdfActionTree,\n} from '@embedpdf/engine-core/runtime';\nimport {\n createScriptHost,\n DEFAULT_SCRIPT_BUDGET,\n javaScriptProgramFromActionTree,\n resolveScriptIdentity,\n scriptFieldsFromSnapshot,\n seedFrom,\n type ScriptBudget,\n type ScriptDiagnostic,\n type ScriptExecutionError,\n type ScriptFieldInput,\n type ScriptOutput,\n type ScriptTransaction,\n type ScriptUiEffect,\n type ScriptValue,\n type ScriptWorldInput,\n} from '@embedpdf/core-acrojs';\nimport type { DocumentMeta } from '@embedpdf/core';\n\nimport type { FormCommitResult, FormScriptingOptions, FormUiEffect } from './types';\n\ninterface Overlay {\n original: ScriptFieldInput[];\n fields: ScriptFieldInput[];\n resetKeys: Set<string>;\n appearances: Map<string, { ref: FormFieldRef; text: string }>;\n}\n\nexport type FormScriptingControllerOptions = {\n doc: DocumentHandle;\n document(): DocumentMeta | null;\n} & (\n | {\n /** The shared realm's transaction port ({@link ScriptTransaction}) —\n * the actions plugin's per-document host. The K/V/C/F pipeline runs\n * WHOLLY inside one transaction: snapshot fetch, every pass, and the\n * engine commit — the commit-inside-the-boundary law. */\n transaction<T>(body: (txn: ScriptTransaction) => Promise<T>): Promise<T>;\n budget?: ScriptBudget;\n }\n | {\n /** Standalone convenience (stamp's detached documents, direct tests):\n * the controller builds and OWNS its own realm via\n * {@link createFormScriptingHost}; `dispose()` releases it. */\n config: FormScriptingOptions;\n }\n);\n\n/**\n * A STANDALONE realm for one document, configured with the legacy\n * `FormScriptingOptions` vocabulary — stamp's detached stamp-asset documents\n * and direct controller tests construct their own host here; viewer\n * documents get theirs from `actionsPlugin({ javascript })`.\n */\nexport function createFormScriptingHost(options: {\n doc: DocumentHandle;\n document(): DocumentMeta | null;\n config: FormScriptingOptions;\n}): {\n transaction<T>(body: (txn: ScriptTransaction) => Promise<T>): Promise<T>;\n dispose(): void;\n} {\n const { doc, config } = options;\n const host = createScriptHost({\n sandboxFactory:\n config.sandboxFactory ??\n (() =>\n import('@embedpdf/core-js-sandbox').then(({ createQuickJsSandbox }) =>\n createQuickJsSandbox(),\n )),\n document: () => {\n const meta = options.document();\n return {\n id: doc.id,\n fileName: config.fileName?.() ?? meta?.name ?? doc.id,\n pageCount: meta?.pageCount ?? 0,\n pageNumber: 0,\n };\n },\n identity: () => resolveScriptIdentity(doc, config.identity),\n environment: (sequence) => {\n const nowMs = config.now?.() ?? Date.now();\n return {\n nowMs,\n utcOffsetMinutes: config.utcOffsetMinutes?.() ?? -new Date(nowMs).getTimezoneOffset(),\n randomSeed: config.randomSeed?.() ?? seedFrom(doc.id, sequence),\n };\n },\n bootSources: async () => {\n const actions = doc.actions ? await doc.actions.read() : null;\n return (\n actions?.nameTreeScripts.map(({ action }) => javaScriptProgramFromActionTree(action)) ?? []\n );\n },\n ...(config.budget ? { budget: config.budget } : {}),\n });\n return { transaction: host.transaction.bind(host), dispose: () => host.dispose() };\n}\n\nconst refKey = (ref: FormFieldRef): string =>\n ref.kind === 'objectNumber' ? `obj:${ref.fieldObjectNumber}` : `fqn:${ref.name}`;\n\nconst sameRef = (left: FormFieldRef, right: FormFieldRef): boolean =>\n refKey(left) === refKey(right);\n\nconst cloneValue = (value: ScriptValue): ScriptValue => (Array.isArray(value) ? [...value] : value);\n\nconst sameValue = (left: ScriptValue, right: ScriptValue): boolean =>\n Array.isArray(left) && Array.isArray(right)\n ? left.length === right.length && left.every((value, index) => value === right[index])\n : left === right;\n\nfunction cloneFields(fields: ScriptFieldInput[]): ScriptFieldInput[] {\n return fields.map((field) => ({\n ...field,\n ref: { ...field.ref },\n value: cloneValue(field.value),\n defaultValue: cloneValue(field.defaultValue),\n ...(field.options ? { options: field.options.map((option) => ({ ...option })) } : {}),\n }));\n}\n\nfunction fieldByRef<T extends { ref: FormFieldRef }>(\n fields: T[],\n ref: FormFieldRef,\n): T | undefined {\n return fields.find((field) => sameRef(field.ref, ref));\n}\n\nfunction snapshotField(snapshot: FormSnapshot, ref: FormFieldRef): FormFieldDTO | undefined {\n return snapshot.fields.find((field) => sameRef(field.ref, ref));\n}\n\nfunction scriptValueFromFormValue(field: ScriptFieldInput, value: FormFieldValue): ScriptValue {\n if (field.family === 'text' && value.type === 'text') return value.value;\n if ((field.family === 'checkbox' || field.family === 'radio') && value.type === 'toggle') {\n return value.state ?? 'Off';\n }\n if (field.family === 'combobox' && value.type === 'choice') return value.values[0] ?? '';\n if (field.family === 'listbox' && value.type === 'choice') return [...value.values];\n throw new Error(`Form value type '${value.type}' does not match field family '${field.family}'`);\n}\n\nfunction formValueFromScriptValue(field: ScriptFieldInput): FormFieldValue | null {\n switch (field.family) {\n case 'text':\n return { type: 'text', value: String(field.value ?? '') };\n case 'checkbox':\n case 'radio':\n return {\n type: 'toggle',\n state:\n field.value === null || field.value === undefined || String(field.value) === 'Off'\n ? null\n : String(field.value),\n };\n case 'combobox':\n return {\n type: 'choice',\n values: Array.isArray(field.value) ? field.value.slice(0, 1) : [String(field.value ?? '')],\n };\n case 'listbox':\n return {\n type: 'choice',\n values: Array.isArray(field.value) ? [...field.value] : [String(field.value ?? '')],\n };\n default:\n return null;\n }\n}\n\nfunction applyEffects(overlay: Overlay, effects: FormEffect[]): void {\n for (const effect of effects) {\n if (effect.kind === 'reset') {\n for (const ref of effect.refs) {\n const field = fieldByRef(overlay.fields, ref);\n if (!field) continue;\n field.value = cloneValue(field.defaultValue);\n overlay.resetKeys.add(refKey(ref));\n }\n continue;\n }\n\n const field = fieldByRef(overlay.fields, effect.ref);\n if (!field) continue;\n const key = refKey(effect.ref);\n if (effect.kind === 'setValue') {\n field.value = scriptValueFromFormValue(field, effect.value);\n overlay.resetKeys.delete(key);\n } else if (effect.kind === 'setDisplay') {\n field.display = effect.display;\n } else {\n overlay.appearances.set(key, { ref: effect.ref, text: effect.text });\n }\n }\n}\n\nfunction canonicalEffects(overlay: Overlay): FormEffect[] {\n const effects: FormEffect[] = [];\n const resets: FormFieldRef[] = [];\n for (const field of overlay.fields) {\n const original = fieldByRef(overlay.original, field.ref);\n if (!original) continue;\n if (!sameValue(original.value, field.value)) {\n const key = refKey(field.ref);\n if (overlay.resetKeys.has(key) && sameValue(field.value, field.defaultValue)) {\n resets.push(field.ref);\n } else {\n const value = formValueFromScriptValue(field);\n if (value) effects.push({ kind: 'setValue', ref: field.ref, value });\n }\n }\n if (original.display !== field.display) {\n effects.push({ kind: 'setDisplay', ref: field.ref, display: field.display });\n }\n }\n if (resets.length > 0) effects.unshift({ kind: 'reset', refs: resets });\n effects.push(\n ...Array.from(overlay.appearances.values(), ({ ref, text }) => ({\n kind: 'setAppearanceText' as const,\n ref,\n text,\n })),\n );\n return effects;\n}\n\nfunction scriptError(message: string): ScriptExecutionError {\n return { kind: 'invalid-output', message };\n}\n\nfunction utf8Length(value: string): number {\n let length = 0;\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code < 0x80) length += 1;\n else if (code < 0x800) length += 2;\n else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {\n length += 4;\n index += 1;\n } else length += 3;\n }\n return length;\n}\n\nfunction pageNumberFor(meta: DocumentMeta | null, field: FormFieldDTO): number {\n const pon = field.widgets.find((widget) => widget.pageObjectNumber > 0)?.pageObjectNumber;\n if (!meta || pon === undefined) return 0;\n const index = meta.pages.findIndex((page) => page.pageObjectNumber === pon);\n return Math.max(0, index);\n}\n\nfunction statusFromEffects(result: FormEffectsResult): FormCommitResult['status'] {\n if (result.results.some(({ status }) => status === 'failed' || status === 'skipped')) {\n return 'failed';\n }\n if (result.results.some(({ status }) => status === 'rejected')) return 'failed';\n return result.results.some(({ status }) => status === 'applied') ? 'applied' : 'unchanged';\n}\n\nexport class FormScriptingController {\n private disposed = false;\n private readonly transaction: <T>(body: (txn: ScriptTransaction) => Promise<T>) => Promise<T>;\n private readonly transactionBudget: ScriptBudget | undefined;\n private readonly ownedHost: { dispose(): void } | null;\n\n constructor(private readonly options: FormScriptingControllerOptions) {\n if ('transaction' in options) {\n this.transaction = options.transaction.bind(options);\n this.transactionBudget = options.budget;\n this.ownedHost = null;\n } else {\n const host = createFormScriptingHost({\n doc: options.doc,\n document: () => options.document(),\n config: options.config,\n });\n this.transaction = host.transaction;\n this.transactionBudget = options.config.budget;\n this.ownedHost = host;\n }\n }\n\n /** A shared realm belongs to its HOST (the actions plugin) — this only\n * fences further transactions; a standalone-owned realm is released. */\n dispose(): void {\n this.disposed = true;\n this.ownedHost?.dispose();\n }\n\n async commit(ref: FormFieldRef, proposed: FormFieldValue): Promise<FormCommitResult> {\n return this.transact(ref, proposed);\n }\n\n /** Execute one originating widget's activation action (`/A`, including `/Next`). */\n async activate(ref: FormFieldRef, action: PdfActionTree): Promise<FormCommitResult> {\n return this.transact(ref, undefined, action);\n }\n\n /** Run lazy document boot plus the `/CO` chain without a user field change.\n * The anchor field resolves INSIDE the transaction (undefined ref). */\n async recalculate(): Promise<FormCommitResult> {\n return this.transact(undefined);\n }\n\n\n private async transact(\n refInput: FormFieldRef | undefined,\n proposed?: FormFieldValue,\n activation?: PdfActionTree,\n ): Promise<FormCommitResult> {\n if (this.disposed) throw new Error('Form scripting controller is disposed');\n // EVERYTHING inside the realm transaction — snapshot fetch, every pass,\n // the engine commit — so the next transaction reads post-commit truth.\n return this.transaction((txn) => this.transactBody(txn, refInput, proposed, activation));\n }\n\n private async transactBody(\n txn: ScriptTransaction,\n refInput: FormFieldRef | undefined,\n proposed?: FormFieldValue,\n activation?: PdfActionTree,\n ): Promise<FormCommitResult> {\n const snapshot = await this.options.doc.forms.list();\n // Recalculate's anchor resolves HERE (first live /CO field, else the\n // first field); a zero-field document is an honest no-op.\n const ref =\n refInput ??\n snapshot.calculationOrder.find(\n (candidate): candidate is FormFieldRef =>\n candidate !== null && snapshotField(snapshot, candidate) !== undefined,\n ) ??\n snapshot.fields[0]?.ref;\n if (!ref) {\n return {\n status: 'unchanged',\n scripted: true,\n effectsResult: null,\n uiEffects: [],\n diagnostics: [],\n };\n }\n const target = snapshotField(snapshot, ref);\n if (!target) {\n return this.failed([], [], scriptError(`Form field '${refKey(ref)}' no longer exists`));\n }\n\n const original = scriptFieldsFromSnapshot(snapshot);\n const overlay: Overlay = {\n original,\n fields: cloneFields(original),\n resetKeys: new Set(),\n appearances: new Map(),\n };\n const targetInput = fieldByRef(overlay.fields, ref);\n if (!targetInput) {\n return this.failed([], [], scriptError(`Form field '${refKey(ref)}' has no script view`));\n }\n\n const hasProposedValue = proposed !== undefined;\n let proposedValue: ScriptValue = cloneValue(targetInput.value);\n if (proposed) {\n try {\n proposedValue = scriptValueFromFormValue(targetInput, proposed);\n } catch (error) {\n return this.failed(\n [],\n [],\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n const uiEffects: FormUiEffect[] = [];\n const diagnostics: ScriptDiagnostic[] = [];\n const meta = this.options.document();\n // Document/identity/environment are HOST-owned now; the world carries the\n // per-field current page for `this.pageNum`.\n const pageNumber = pageNumberFor(meta, target);\n const budget = this.transactionBudget ?? DEFAULT_SCRIPT_BUDGET;\n let executionMs = 0;\n let aggregateOutputSize = 0;\n\n const consume = (\n output: ScriptOutput,\n phase: FormUiEffect['phase'],\n ): ScriptExecutionError | null => {\n aggregateOutputSize += utf8Length(JSON.stringify(output));\n // Tag every UI request with WHO asked — embedders suppress boot-phase\n // nags (Adobe's version-check alert) but show user-phase validation.\n uiEffects.push(...output.uiEffects.map((effect) => ({ ...effect, phase })));\n diagnostics.push(...output.diagnostics);\n if (aggregateOutputSize > budget.maxOutputBytes) {\n return {\n kind: 'budget',\n message: `Form script transaction output exceeded ${budget.maxOutputBytes} bytes`,\n };\n }\n return output.error ?? null;\n };\n const remainingBudget = (): ScriptBudget | null => {\n const remaining = budget.maxExecutionMs - executionMs;\n return remaining <= 0 ? null : { ...budget, maxExecutionMs: remaining };\n };\n const world = (fields: ScriptFieldInput[], event: ScriptWorldInput['event']): ScriptWorldInput => ({\n fields,\n pageNumber,\n event,\n });\n const run = async (\n source: string,\n input: ScriptWorldInput,\n ): Promise<{ output?: ScriptOutput; error?: ScriptExecutionError }> => {\n const remaining = remainingBudget();\n if (!remaining) {\n return { error: { kind: 'budget', message: 'Form script transaction timed out' } };\n }\n let output: ScriptOutput;\n try {\n const startedAt = Date.now();\n output = await txn.run(source, input, remaining);\n executionMs += Date.now() - startedAt;\n } catch (error) {\n return {\n error: scriptError(error instanceof Error ? error.message : String(error)),\n };\n }\n const error = consume(output, 'user');\n return error ? { error } : { output };\n };\n\n {\n // Boot runs ONCE per realm (host-owned latch) and can only ever\n // degrade, never brick — a failure surfaces a diagnostic and this\n // transaction continues from pristine state.\n let boot: ScriptOutput | null = null;\n try {\n boot = await txn.boot(world(overlay.fields, { kind: 'name-tree-boot' }), budget);\n } catch (error) {\n diagnostics.push({\n code: 'script-error',\n message: `Document boot script failed (continuing without it): ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n if (boot) {\n const bootError = consume(boot, 'boot');\n if (!bootError) applyEffects(overlay, boot.formEffects);\n else {\n diagnostics.push({\n code: 'script-error',\n message: `Document boot script failed (continuing without it): ${bootError.message}`,\n });\n }\n }\n }\n\n const bootEffects = canonicalEffects(overlay);\n\n if (activation) {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(activation);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n const activated = await run(program, world(overlay.fields, {\n kind: 'widget-activate',\n target: ref,\n source: ref,\n value: targetInput.value,\n }));\n if (activated.error) return this.failed(uiEffects, diagnostics, activated.error);\n applyEffects(overlay, activated.output!.formEffects);\n }\n }\n\n if (!activation && hasProposedValue && target.actions?.keystroke && target.family === 'text') {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(target.actions.keystroke);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n // Acrobat fires per-typing keystroke events plus one final commit\n // event. This pipeline compresses typing into ONE paste-shaped\n // replacement (Acrobat's own paste contract: the whole string rides\n // `event.change`, willCommit=false) so transform/filter scripts run,\n // then fires the Acrobat-faithful willCommit pass (the full value on\n // `event.value`, empty `change`) so AF* and commit validators see\n // what Adobe's library expects.\n const oldValue = String(targetInput.value ?? '');\n const typing = await run(program, world(overlay.fields, {\n kind: 'field-keystroke',\n target: ref,\n source: ref,\n value: oldValue,\n change: String(proposedValue ?? ''),\n selStart: 0,\n selEnd: oldValue.length,\n willCommit: false,\n }));\n if (typing.error && typing.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, typing.error);\n }\n if (typing.error) {\n // Fault ladder: an exception is not a rejection. Keep the typed\n // value, surface a diagnostic, and let the transaction continue.\n diagnostics.push({\n code: 'script-error',\n message: `Keystroke script failed (kept the typed value): ${typing.error.message}`,\n });\n } else {\n if (!typing.output!.event.rc) {\n return this.finishRejected(bootEffects, uiEffects, diagnostics);\n }\n applyEffects(overlay, typing.output!.formEffects);\n const event = typing.output!.event;\n const base = String(event.value ?? '');\n proposedValue =\n base.slice(0, event.selStart) +\n event.change +\n base.slice(Math.max(event.selStart, event.selEnd));\n }\n const commit = await run(program, world(overlay.fields, {\n kind: 'field-keystroke',\n target: ref,\n source: ref,\n value: proposedValue,\n change: '',\n selStart: 0,\n selEnd: 0,\n willCommit: true,\n }));\n if (commit.error && commit.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, commit.error);\n }\n if (commit.error) {\n diagnostics.push({\n code: 'script-error',\n message: `Keystroke commit script failed (kept the typed value): ${commit.error.message}`,\n });\n } else {\n if (!commit.output!.event.rc) {\n return this.finishRejected(bootEffects, uiEffects, diagnostics);\n }\n applyEffects(overlay, commit.output!.formEffects);\n proposedValue = cloneValue(commit.output!.event.value);\n }\n }\n }\n\n if (!activation && hasProposedValue) {\n targetInput.value = cloneValue(proposedValue);\n overlay.resetKeys.delete(refKey(ref));\n }\n\n if (!activation && hasProposedValue && target.actions?.validate) {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(target.actions.validate);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n const validation = await run(program, world(overlay.fields, {\n kind: 'field-validate',\n target: ref,\n source: ref,\n value: proposedValue,\n willCommit: true,\n }));\n if (validation.error && validation.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, validation.error);\n }\n if (validation.error) {\n // Fault ladder: a broken validator must not reject the value.\n diagnostics.push({\n code: 'script-error',\n message: `Validate script failed (accepted the value): ${validation.error.message}`,\n });\n } else {\n if (!validation.output!.event.rc) {\n return this.finishRejected(bootEffects, uiEffects, diagnostics);\n }\n targetInput.value = cloneValue(validation.output!.event.value);\n applyEffects(overlay, validation.output!.formEffects);\n }\n }\n }\n\n const formatRefs: FormFieldRef[] = !activation && hasProposedValue ? [ref] : [];\n for (const calculationRef of activation ? [] : snapshot.calculationOrder) {\n if (!calculationRef) continue;\n const calculated = snapshotField(snapshot, calculationRef);\n const tree = calculated?.actions?.calculate;\n if (!calculated || !tree) continue;\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(tree);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n const calculatedInput = fieldByRef(overlay.fields, calculationRef);\n if (program && calculatedInput) {\n const calculation = await run(\n program,\n world(overlay.fields, {\n kind: 'field-calculate',\n target: calculationRef,\n ...(hasProposedValue ? { source: ref } : {}),\n value: calculatedInput.value,\n }),\n );\n if (calculation.error && calculation.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, calculation.error);\n }\n if (calculation.error) {\n // Fault ladder: this field's calculation is skipped; the /CO chain continues.\n diagnostics.push({\n code: 'script-error',\n message: `Calculate script failed (field left unchanged): ${calculation.error.message}`,\n });\n } else {\n applyEffects(overlay, calculation.output!.formEffects);\n }\n }\n if (!formatRefs.some((candidate) => sameRef(candidate, calculationRef))) {\n formatRefs.push(calculationRef);\n }\n }\n\n for (const formatRef of formatRefs) {\n const field = snapshotField(snapshot, formatRef);\n const tree = field?.actions?.format;\n const overlayField = fieldByRef(overlay.fields, formatRef);\n if (!field || !tree || !overlayField) continue;\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(tree);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (!program) continue;\n const format = await run(\n program,\n world(overlay.fields, {\n kind: 'field-format',\n target: formatRef,\n ...(hasProposedValue ? { source: ref } : {}),\n value: overlayField.value,\n }),\n );\n if (format.error && format.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, format.error);\n }\n if (format.error) {\n // Fault ladder: formatting is cosmetic — skip it, keep the raw value.\n diagnostics.push({\n code: 'script-error',\n message: `Format script failed (kept the raw value): ${format.error.message}`,\n });\n continue;\n }\n applyEffects(overlay, format.output!.formEffects);\n }\n\n const effects = canonicalEffects(overlay);\n if (effects.length + uiEffects.length > budget.maxEffects) {\n return this.failed(uiEffects, diagnostics, {\n kind: 'budget',\n message: `Form script transaction exceeded ${budget.maxEffects} effects`,\n });\n }\n if (effects.length === 0) {\n return { status: 'unchanged', scripted: true, effectsResult: null, uiEffects, diagnostics };\n }\n if (!this.options.doc.forms.applyEffects) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError('This engine does not support batched form effects'),\n );\n }\n\n const effectsResult = await this.options.doc.forms.applyEffects(effects);\n const status = statusFromEffects(effectsResult);\n return {\n status,\n scripted: true,\n effectsResult,\n uiEffects,\n diagnostics,\n ...(status === 'failed'\n ? { error: scriptError('One or more native form effects failed') }\n : {}),\n };\n }\n\n private failed(\n uiEffects: FormUiEffect[],\n diagnostics: ScriptDiagnostic[],\n error: ScriptExecutionError,\n ): FormCommitResult {\n return {\n status: 'failed',\n scripted: true,\n effectsResult: null,\n uiEffects,\n diagnostics,\n error,\n };\n }\n\n private async finishRejected(\n bootEffects: FormEffect[],\n uiEffects: FormUiEffect[],\n diagnostics: ScriptDiagnostic[],\n ): Promise<FormCommitResult> {\n if (bootEffects.length === 0) {\n return {\n status: 'rejected',\n scripted: true,\n effectsResult: null,\n uiEffects,\n diagnostics,\n };\n }\n if (!this.options.doc.forms.applyEffects) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError('Batched form effects are unavailable'),\n );\n }\n const effectsResult = await this.options.doc.forms.applyEffects(bootEffects);\n if (statusFromEffects(effectsResult) === 'failed') {\n return this.failed(uiEffects, diagnostics, scriptError('A name-tree boot effect failed'));\n }\n return {\n status: 'rejected',\n scripted: true,\n effectsResult,\n uiEffects,\n diagnostics,\n };\n }\n}\n\nexport function createFormScriptingController(\n options: FormScriptingControllerOptions,\n): FormScriptingController {\n return new FormScriptingController(options);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { ActionContext, ActionDiagnostic, ActionOrigin, ActionSubmitRequest, ActionTriggerResult, PdfAnnotationEventKind, SubmitIntent } from "@embedpdf/plugin-actions/contract";
|
|
2
|
+
import { AnnotationRef, FormDataExport, FormDataFormat, FormEffect, FormEffectsResult, FormFieldDTO, FormFieldFamily, FormFieldOption, FormFieldPatch, FormFieldRef, FormFieldValue, FormImportResult, FormRepairOptions, FormRepairResult, FormSetValueResult, FormSnapshot, PdfActionTargetRef, WidgetAppearance } from "@embedpdf/engine-core/runtime";
|
|
3
|
+
import { ScriptBudget, ScriptDiagnostic, ScriptExecutionError, ScriptIdentity, ScriptUiEffect } from "@embedpdf/core-acrojs";
|
|
4
|
+
import { ScriptSandboxFactory } from "@embedpdf/core-js-sandbox";
|
|
5
|
+
//#region src/core/model.d.ts
|
|
6
|
+
/** A content-space box (top-left origin, y-down, PDF points). */
|
|
7
|
+
interface Box {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** Stable client key for a field: object number when durable, else FQN. */
|
|
14
|
+
type FieldKey = string;
|
|
15
|
+
declare const fieldKeyOf: (field: {
|
|
16
|
+
fieldObjectNumber: number;
|
|
17
|
+
name: string;
|
|
18
|
+
}) => FieldKey;
|
|
19
|
+
interface Model {
|
|
20
|
+
/** Engine truth; replaced wholesale on refresh, patched on write-backs. */
|
|
21
|
+
snapshot: FormSnapshot | null;
|
|
22
|
+
/** fieldKey → index into snapshot.fields. */
|
|
23
|
+
byKey: Record<FieldKey, number>;
|
|
24
|
+
/** annotObjectNumber → index into snapshot.fields (widget → field join). */
|
|
25
|
+
byWidget: Record<number, number>;
|
|
26
|
+
/** Fields with an in-flight engine write (disables their controls). */
|
|
27
|
+
writing: Record<FieldKey, true>;
|
|
28
|
+
/**
|
|
29
|
+
* Widget geometry per page: annotObjectNumber → content-space box. Fed by
|
|
30
|
+
* the shell from the WIDGET plane (one annotations read per page); value
|
|
31
|
+
* writes never move widgets, so this survives snapshot refreshes and is
|
|
32
|
+
* only cleared on structural form events.
|
|
33
|
+
*/
|
|
34
|
+
geom: Record<number, Record<number, Box> | undefined>;
|
|
35
|
+
/** Bumped on every change — cheap equality for memoized projections. */
|
|
36
|
+
seq: number;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/core/fill-items.d.ts
|
|
40
|
+
interface FillItemBase {
|
|
41
|
+
key: FieldKey;
|
|
42
|
+
/** Widget identity — joins to the annotation plane. */
|
|
43
|
+
annotObjectNumber: number;
|
|
44
|
+
box: Box;
|
|
45
|
+
/** Read-only field or write in flight: render, don't accept input. */
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
/** Accessible name: /TU when present, else the fully qualified name. */
|
|
48
|
+
label: string;
|
|
49
|
+
}
|
|
50
|
+
type FillItem = FillItemBase & ({
|
|
51
|
+
control: 'text';
|
|
52
|
+
value: string;
|
|
53
|
+
multiline: boolean;
|
|
54
|
+
password: boolean;
|
|
55
|
+
maxLength: number | null;
|
|
56
|
+
comb: boolean;
|
|
57
|
+
} | {
|
|
58
|
+
control: 'toggle';
|
|
59
|
+
kind: 'checkbox' | 'radio';
|
|
60
|
+
checked: boolean;
|
|
61
|
+
onState: string;
|
|
62
|
+
} | {
|
|
63
|
+
control: 'choice';
|
|
64
|
+
kind: 'combo' | 'list';
|
|
65
|
+
edit: boolean;
|
|
66
|
+
multi: boolean;
|
|
67
|
+
options: FormFieldOption[];
|
|
68
|
+
selected: string[];
|
|
69
|
+
} | {
|
|
70
|
+
control: 'button';
|
|
71
|
+
});
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/types.d.ts
|
|
74
|
+
interface FormState {
|
|
75
|
+
model: Model;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Standalone-realm configuration for `createFormScriptingHost` — stamp's
|
|
79
|
+
* detached documents and direct controller tests. Viewer documents configure
|
|
80
|
+
* scripting on `actionsPlugin({ javascript })` instead (D8), and every UI
|
|
81
|
+
* effect/diagnostic surfaces through the actions port (D9).
|
|
82
|
+
*/
|
|
83
|
+
interface FormScriptingOptions {
|
|
84
|
+
/** Override the lazy QuickJS factory (tests or another isolated VM). */
|
|
85
|
+
sandboxFactory?: ScriptSandboxFactory;
|
|
86
|
+
/** Optional embedder identity fields layered over engine/JWT identity. */
|
|
87
|
+
identity?: Partial<ScriptIdentity> | (() => Partial<ScriptIdentity>);
|
|
88
|
+
/** Target document filename exposed as `this.documentFileName`. */
|
|
89
|
+
fileName?: () => string;
|
|
90
|
+
/** Injected deterministic transaction environment. */
|
|
91
|
+
now?: () => number;
|
|
92
|
+
utcOffsetMinutes?: () => number;
|
|
93
|
+
randomSeed?: () => number;
|
|
94
|
+
budget?: ScriptBudget;
|
|
95
|
+
}
|
|
96
|
+
/** The scripting switch moved to `actionsPlugin({ javascript })` (D8). */
|
|
97
|
+
interface FormPluginOptions {}
|
|
98
|
+
type FormCommitStatus = 'applied' | 'unchanged' | 'rejected' | 'failed';
|
|
99
|
+
interface FormCommitResult {
|
|
100
|
+
status: FormCommitStatus;
|
|
101
|
+
scripted: boolean;
|
|
102
|
+
effectsResult: FormEffectsResult | null;
|
|
103
|
+
uiEffects: FormUiEffect[];
|
|
104
|
+
diagnostics: ScriptDiagnostic[];
|
|
105
|
+
error?: ScriptExecutionError;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One DOM-free UI request produced by the curated Acrobat scripting surface.
|
|
109
|
+
* `phase` says WHO asked: `'boot'` = a document-open script (Adobe's
|
|
110
|
+
* version-check boilerplate lives here — embedders typically suppress these
|
|
111
|
+
* nags), `'user'` = a script triggered by the user's own interaction (a
|
|
112
|
+
* validation alert — show it).
|
|
113
|
+
*/
|
|
114
|
+
type FormUiEffect = ScriptUiEffect & {
|
|
115
|
+
/** WHO asked, script-model axis: `'boot'` = a name-tree/document-open boot
|
|
116
|
+
* script, `'user'` = a runtime script. */
|
|
117
|
+
phase: 'boot' | 'user';
|
|
118
|
+
/**
|
|
119
|
+
* The dispatch-origin axis (present when the script ran inside an action
|
|
120
|
+
* dispatch): a lifecycle `/OpenAction` script is NOT a name-tree boot
|
|
121
|
+
* script — the two axes are deliberately separate. Providers use this for
|
|
122
|
+
* the default visibility matrix (suppress lifecycle alerts, block
|
|
123
|
+
* non-user print); embedder handlers receive it and may decide otherwise.
|
|
124
|
+
*/
|
|
125
|
+
origin?: ActionOrigin;
|
|
126
|
+
};
|
|
127
|
+
/** Input for {@link FormCapability.placeField}. */
|
|
128
|
+
interface PlaceFieldInput {
|
|
129
|
+
family: Exclude<FormFieldFamily, 'pushbutton' | 'signature' | 'unknown'>;
|
|
130
|
+
pageObjectNumber: number;
|
|
131
|
+
/** Content-space LOGICAL field box (no visual padding semantics). */
|
|
132
|
+
box: Box;
|
|
133
|
+
/** Widget styling in the engine vocabulary (`WidgetAppearance`). Convert a
|
|
134
|
+
* tool's flat CSS defaults with `widgetAppearanceFromProps` from
|
|
135
|
+
* `@embedpdf/plugin-annotation`. Omitted → the engine's bare defaults. */
|
|
136
|
+
appearance?: WidgetAppearance;
|
|
137
|
+
}
|
|
138
|
+
/** What {@link FormCapability.placeField} created. */
|
|
139
|
+
interface PlacedField {
|
|
140
|
+
field: FormFieldDTO;
|
|
141
|
+
/** The widget placed on the requested page (join key to the annotation
|
|
142
|
+
* plane for auto-selection), or null when the engine reported none. */
|
|
143
|
+
widget: FormFieldDTO['widgets'][number] | null;
|
|
144
|
+
}
|
|
145
|
+
type FormAction = {
|
|
146
|
+
type: 'SET_MODEL';
|
|
147
|
+
model: Model;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The form plugin's public capability: the FIELD plane. Widgets stay
|
|
151
|
+
* annotations (geometry/appearance live there); this surface owns values,
|
|
152
|
+
* interchange, and the fill-mode projection.
|
|
153
|
+
*/
|
|
154
|
+
interface FormCapability {
|
|
155
|
+
/** The current reconciled form state (null until the first load lands). */
|
|
156
|
+
snapshot(): FormSnapshot | null;
|
|
157
|
+
/** Re-read the form from the engine (imports/repair/remote bursts). */
|
|
158
|
+
refresh(): Promise<void>;
|
|
159
|
+
/** Fill controls for one page — content-space, framework-agnostic. */
|
|
160
|
+
fillItems(pageObjectNumber: number): FillItem[];
|
|
161
|
+
/**
|
|
162
|
+
* The fill control for ONE widget, by annotation object number — the join
|
|
163
|
+
* the annotation-plane render layer uses (its RenderItem carries the live
|
|
164
|
+
* box, so this item's `box` is advisory). Reference-stable per model change.
|
|
165
|
+
* Null until the snapshot lands, or for families with no fill control.
|
|
166
|
+
*/
|
|
167
|
+
fillItem(annotObjectNumber: number): FillItem | null;
|
|
168
|
+
/** Make sure a page's widget geometry is loaded (idempotent, lazy). */
|
|
169
|
+
ensureGeom(pageObjectNumber: number): void;
|
|
170
|
+
field(key: FieldKey): FormFieldDTO | null;
|
|
171
|
+
fieldForWidget(annotObjectNumber: number): FormFieldDTO | null;
|
|
172
|
+
/** Commit a text value (call on blur/Enter — keystrokes stay local). */
|
|
173
|
+
setText(key: FieldKey, value: string): Promise<void>;
|
|
174
|
+
/** Toggle a checkbox/radio widget by its on-state; null clears the group. */
|
|
175
|
+
toggle(key: FieldKey, onState: string | null): Promise<void>;
|
|
176
|
+
/** Select choice options by export value. */
|
|
177
|
+
choose(key: FieldKey, values: string[]): Promise<void>;
|
|
178
|
+
/** Commit one originating-client value transaction, including K/V/C/F scripts when enabled. */
|
|
179
|
+
commitValue(ref: FormFieldRef, value: FormFieldValue): Promise<FormCommitResult>;
|
|
180
|
+
/**
|
|
181
|
+
* Execute one widget's `/A` activation. With the actions plugin installed
|
|
182
|
+
* the FULL tree is delegated to its dispatcher (Hide/ResetForm buttons work
|
|
183
|
+
* even with scripting off) — `kind: 'dispatched'`; without it, today's
|
|
184
|
+
* scripting-transaction path runs — `kind: 'form'`.
|
|
185
|
+
*/
|
|
186
|
+
activateWidget(key: FieldKey, annotationRef: AnnotationRef): Promise<WidgetActivationResult>;
|
|
187
|
+
/**
|
|
188
|
+
* Report one widget DOM event (pointer enter/leave, down/up, focus/blur).
|
|
189
|
+
* With the actions plugin present the matching `/AA` tree dispatches
|
|
190
|
+
* (hover rides the shared coalescing pump; `/A` shadows `/AA U` per ISO
|
|
191
|
+
* Table 197); without it — or without a tree — this is a cheap no-op.
|
|
192
|
+
* Fire-and-forget by design: results surface via the actions events.
|
|
193
|
+
*/
|
|
194
|
+
notifyWidgetEvent(key: FieldKey, ref: AnnotationRef, event: PdfAnnotationEventKind): void;
|
|
195
|
+
/** Restore a field to its /DV default. */
|
|
196
|
+
reset(key: FieldKey): Promise<void>;
|
|
197
|
+
/** Raw engine passthrough for anything the sugar above doesn't cover. */
|
|
198
|
+
setValue(ref: FormFieldRef, value: FormFieldValue): Promise<FormSetValueResult>;
|
|
199
|
+
exportData(format?: FormDataFormat): Promise<FormDataExport>;
|
|
200
|
+
importData(data: Uint8Array | ArrayBuffer, format?: FormDataFormat): Promise<FormImportResult>;
|
|
201
|
+
repair(options?: FormRepairOptions): Promise<FormRepairResult>;
|
|
202
|
+
/**
|
|
203
|
+
* Create a field of `family` with one styled widget at a content-space box
|
|
204
|
+
* — the palette tools' commit, and the programmatic authoring entry (works
|
|
205
|
+
* with NO annotation plugin: a pure `doc.forms` call). The box is clamped
|
|
206
|
+
* to the page; sizing policy (click default vs drag rect) is the caller's.
|
|
207
|
+
* The field gets a deterministic auto-name (`text_1`, …; rename in the
|
|
208
|
+
* field panel). Resolves AFTER the annotation plane (when present) has
|
|
209
|
+
* re-read the page, so the returned widget is immediately selectable.
|
|
210
|
+
*/
|
|
211
|
+
placeField(input: PlaceFieldInput): Promise<PlacedField>;
|
|
212
|
+
/** The page's content box (`{0,0,w,h}`) — page-bound placement math. */
|
|
213
|
+
pageBox(pageObjectNumber: number): Box | null;
|
|
214
|
+
/** Field-plane properties: name, required, options, default value. */
|
|
215
|
+
updateField(key: FieldKey, patch: FormFieldPatch): Promise<void>;
|
|
216
|
+
/** Delete the field and every widget of it (cascades on the page). */
|
|
217
|
+
deleteField(key: FieldKey): Promise<void>;
|
|
218
|
+
/** Unlink one widget: it stays as an inert annotation, the field survives. */
|
|
219
|
+
detachWidget(key: FieldKey, annotObjectNumber: number): Promise<void>;
|
|
220
|
+
/**
|
|
221
|
+
* The twins (permissions.md): would the family's verbs succeed now for
|
|
222
|
+
* this session? `canRead` — the form model hydrates at all (false = the
|
|
223
|
+
* hydration gate left it empty by right); `canFill` — value writes
|
|
224
|
+
* (`setText`/`toggle`/`choose`/`reset`; also fused into every
|
|
225
|
+
* `FillItem.disabled`); `canDesign` — the field-design family
|
|
226
|
+
* (`placeField`/`updateField`/`deleteField`/`detachWidget`).
|
|
227
|
+
*/
|
|
228
|
+
canRead(): boolean;
|
|
229
|
+
canFill(): boolean;
|
|
230
|
+
canDesign(): boolean;
|
|
231
|
+
}
|
|
232
|
+
/** What one widget activation did — which world handled it (see
|
|
233
|
+
* {@link FormCapability.activateWidget}). Both framework call sites ignore
|
|
234
|
+
* the value today; chrome that cares can discriminate on `kind`. */
|
|
235
|
+
type WidgetActivationResult = {
|
|
236
|
+
kind: 'form';
|
|
237
|
+
result: FormCommitResult;
|
|
238
|
+
} | {
|
|
239
|
+
kind: 'dispatched';
|
|
240
|
+
result: ActionTriggerResult;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* HOST lens — plugin-to-plugin only (the actions plugin's interim
|
|
244
|
+
* `javascript` / `reset-form` executors). Import the token from
|
|
245
|
+
* `@embedpdf/plugin-form/contract/host`, never from application code.
|
|
246
|
+
*/
|
|
247
|
+
interface FormHostCapability extends FormCapability {
|
|
248
|
+
/**
|
|
249
|
+
* Execute one ResetForm action: resolve targets against the live snapshot
|
|
250
|
+
* with the shared ISO selection (`null` = every field; a parent NAME
|
|
251
|
+
* selects its descendants too; `exclude` = complement; a resolution
|
|
252
|
+
* yielding zero refs never reaches the engine), reset as ONE engine
|
|
253
|
+
* batch, refresh, then recalculate when scripting is enabled (Acrobat's
|
|
254
|
+
* behaviour). `origin` (default `'user'`) is preserved through
|
|
255
|
+
* recalculation surfacing — a lifecycle ResetForm's alerts stay lifecycle.
|
|
256
|
+
*/
|
|
257
|
+
resetFormAction(fields: PdfActionTargetRef[] | null, exclude: boolean, origin?: ActionOrigin): Promise<FormCommitResult>;
|
|
258
|
+
/**
|
|
259
|
+
* The submit dataset resolver (Phase 4, D7): fresh engine read + the pure
|
|
260
|
+
* ISO builder (`field-selection.ts`). Registered with the actions plugin
|
|
261
|
+
* as THE resolver for both action-node and script submits.
|
|
262
|
+
*/
|
|
263
|
+
resolveSubmitDataset(intent: SubmitIntent, ctx: ActionContext, diagnose: (diagnostic: ActionDiagnostic) => void): Promise<ActionSubmitRequest>;
|
|
264
|
+
/** The form DOCUMENT-commit sink (D3): engine `applyEffects` + snapshot
|
|
265
|
+
* reconciliation. Sink contract: never throws, never enqueues, never
|
|
266
|
+
* touches the script host — callable under a held host transaction. */
|
|
267
|
+
commitScriptFormEffects(effects: FormEffect[]): Promise<FormEffectsResult>;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The form capability token. Typed to the full {@link FormHostCapability}
|
|
271
|
+
* here (the package internals + the `/internal` entry use this view). The
|
|
272
|
+
* package root re-exports the SAME token narrowed to {@link FormCapability}.
|
|
273
|
+
*/
|
|
274
|
+
declare const FormToken: import("@embedpdf/core").CapabilityToken<FormHostCapability>;
|
|
275
|
+
//#endregion
|
|
276
|
+
export { fieldKeyOf as _, FormHostCapability as a, FormState as c, PlaceFieldInput as d, PlacedField as f, FieldKey as g, Box as h, FormCommitStatus as i, FormToken as l, FillItem as m, FormCapability as n, FormPluginOptions as o, WidgetActivationResult as p, FormCommitResult as r, FormScriptingOptions as s, FormAction as t, FormUiEffect as u };
|
|
277
|
+
//# sourceMappingURL=types-BlXdnNRf.d.ts.map
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { AnnotationRef, FormDataExport, FormDataFormat, FormEffect, FormEffectsResult, FormFieldDTO, FormFieldFamily, FormFieldOption, FormFieldPatch, FormFieldRef, FormFieldValue, FormImportResult, FormRepairOptions, FormRepairResult, FormSetValueResult, FormSnapshot, PdfActionTargetRef, WidgetAppearance } from "@embedpdf/engine-core/runtime";
|
|
2
|
+
import { ActionContext, ActionDiagnostic, ActionOrigin, ActionSubmitRequest, ActionTriggerResult, PdfAnnotationEventKind, SubmitIntent } from "@embedpdf/plugin-actions/contract";
|
|
3
|
+
import { ScriptBudget, ScriptDiagnostic, ScriptExecutionError, ScriptIdentity, ScriptUiEffect } from "@embedpdf/core-acrojs";
|
|
4
|
+
import { ScriptSandboxFactory } from "@embedpdf/core-js-sandbox";
|
|
5
|
+
//#region src/core/model.d.ts
|
|
6
|
+
/** A content-space box (top-left origin, y-down, PDF points). */
|
|
7
|
+
interface Box {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** Stable client key for a field: object number when durable, else FQN. */
|
|
14
|
+
type FieldKey = string;
|
|
15
|
+
declare const fieldKeyOf: (field: {
|
|
16
|
+
fieldObjectNumber: number;
|
|
17
|
+
name: string;
|
|
18
|
+
}) => FieldKey;
|
|
19
|
+
interface Model {
|
|
20
|
+
/** Engine truth; replaced wholesale on refresh, patched on write-backs. */
|
|
21
|
+
snapshot: FormSnapshot | null;
|
|
22
|
+
/** fieldKey → index into snapshot.fields. */
|
|
23
|
+
byKey: Record<FieldKey, number>;
|
|
24
|
+
/** annotObjectNumber → index into snapshot.fields (widget → field join). */
|
|
25
|
+
byWidget: Record<number, number>;
|
|
26
|
+
/** Fields with an in-flight engine write (disables their controls). */
|
|
27
|
+
writing: Record<FieldKey, true>;
|
|
28
|
+
/**
|
|
29
|
+
* Widget geometry per page: annotObjectNumber → content-space box. Fed by
|
|
30
|
+
* the shell from the WIDGET plane (one annotations read per page); value
|
|
31
|
+
* writes never move widgets, so this survives snapshot refreshes and is
|
|
32
|
+
* only cleared on structural form events.
|
|
33
|
+
*/
|
|
34
|
+
geom: Record<number, Record<number, Box> | undefined>;
|
|
35
|
+
/** Bumped on every change — cheap equality for memoized projections. */
|
|
36
|
+
seq: number;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/core/fill-items.d.ts
|
|
40
|
+
interface FillItemBase {
|
|
41
|
+
key: FieldKey;
|
|
42
|
+
/** Widget identity — joins to the annotation plane. */
|
|
43
|
+
annotObjectNumber: number;
|
|
44
|
+
box: Box;
|
|
45
|
+
/** Read-only field or write in flight: render, don't accept input. */
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
/** Accessible name: /TU when present, else the fully qualified name. */
|
|
48
|
+
label: string;
|
|
49
|
+
}
|
|
50
|
+
type FillItem = FillItemBase & ({
|
|
51
|
+
control: 'text';
|
|
52
|
+
value: string;
|
|
53
|
+
multiline: boolean;
|
|
54
|
+
password: boolean;
|
|
55
|
+
maxLength: number | null;
|
|
56
|
+
comb: boolean;
|
|
57
|
+
} | {
|
|
58
|
+
control: 'toggle';
|
|
59
|
+
kind: 'checkbox' | 'radio';
|
|
60
|
+
checked: boolean;
|
|
61
|
+
onState: string;
|
|
62
|
+
} | {
|
|
63
|
+
control: 'choice';
|
|
64
|
+
kind: 'combo' | 'list';
|
|
65
|
+
edit: boolean;
|
|
66
|
+
multi: boolean;
|
|
67
|
+
options: FormFieldOption[];
|
|
68
|
+
selected: string[];
|
|
69
|
+
} | {
|
|
70
|
+
control: 'button';
|
|
71
|
+
});
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/types.d.ts
|
|
74
|
+
interface FormState {
|
|
75
|
+
model: Model;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Standalone-realm configuration for `createFormScriptingHost` — stamp's
|
|
79
|
+
* detached documents and direct controller tests. Viewer documents configure
|
|
80
|
+
* scripting on `actionsPlugin({ javascript })` instead (D8), and every UI
|
|
81
|
+
* effect/diagnostic surfaces through the actions port (D9).
|
|
82
|
+
*/
|
|
83
|
+
interface FormScriptingOptions {
|
|
84
|
+
/** Override the lazy QuickJS factory (tests or another isolated VM). */
|
|
85
|
+
sandboxFactory?: ScriptSandboxFactory;
|
|
86
|
+
/** Optional embedder identity fields layered over engine/JWT identity. */
|
|
87
|
+
identity?: Partial<ScriptIdentity> | (() => Partial<ScriptIdentity>);
|
|
88
|
+
/** Target document filename exposed as `this.documentFileName`. */
|
|
89
|
+
fileName?: () => string;
|
|
90
|
+
/** Injected deterministic transaction environment. */
|
|
91
|
+
now?: () => number;
|
|
92
|
+
utcOffsetMinutes?: () => number;
|
|
93
|
+
randomSeed?: () => number;
|
|
94
|
+
budget?: ScriptBudget;
|
|
95
|
+
}
|
|
96
|
+
/** The scripting switch moved to `actionsPlugin({ javascript })` (D8). */
|
|
97
|
+
interface FormPluginOptions {}
|
|
98
|
+
type FormCommitStatus = 'applied' | 'unchanged' | 'rejected' | 'failed';
|
|
99
|
+
interface FormCommitResult {
|
|
100
|
+
status: FormCommitStatus;
|
|
101
|
+
scripted: boolean;
|
|
102
|
+
effectsResult: FormEffectsResult | null;
|
|
103
|
+
uiEffects: FormUiEffect[];
|
|
104
|
+
diagnostics: ScriptDiagnostic[];
|
|
105
|
+
error?: ScriptExecutionError;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One DOM-free UI request produced by the curated Acrobat scripting surface.
|
|
109
|
+
* `phase` says WHO asked: `'boot'` = a document-open script (Adobe's
|
|
110
|
+
* version-check boilerplate lives here — embedders typically suppress these
|
|
111
|
+
* nags), `'user'` = a script triggered by the user's own interaction (a
|
|
112
|
+
* validation alert — show it).
|
|
113
|
+
*/
|
|
114
|
+
type FormUiEffect = ScriptUiEffect & {
|
|
115
|
+
/** WHO asked, script-model axis: `'boot'` = a name-tree/document-open boot
|
|
116
|
+
* script, `'user'` = a runtime script. */
|
|
117
|
+
phase: 'boot' | 'user';
|
|
118
|
+
/**
|
|
119
|
+
* The dispatch-origin axis (present when the script ran inside an action
|
|
120
|
+
* dispatch): a lifecycle `/OpenAction` script is NOT a name-tree boot
|
|
121
|
+
* script — the two axes are deliberately separate. Providers use this for
|
|
122
|
+
* the default visibility matrix (suppress lifecycle alerts, block
|
|
123
|
+
* non-user print); embedder handlers receive it and may decide otherwise.
|
|
124
|
+
*/
|
|
125
|
+
origin?: ActionOrigin;
|
|
126
|
+
};
|
|
127
|
+
/** Input for {@link FormCapability.placeField}. */
|
|
128
|
+
interface PlaceFieldInput {
|
|
129
|
+
family: Exclude<FormFieldFamily, 'pushbutton' | 'signature' | 'unknown'>;
|
|
130
|
+
pageObjectNumber: number;
|
|
131
|
+
/** Content-space LOGICAL field box (no visual padding semantics). */
|
|
132
|
+
box: Box;
|
|
133
|
+
/** Widget styling in the engine vocabulary (`WidgetAppearance`). Convert a
|
|
134
|
+
* tool's flat CSS defaults with `widgetAppearanceFromProps` from
|
|
135
|
+
* `@embedpdf/plugin-annotation`. Omitted → the engine's bare defaults. */
|
|
136
|
+
appearance?: WidgetAppearance;
|
|
137
|
+
}
|
|
138
|
+
/** What {@link FormCapability.placeField} created. */
|
|
139
|
+
interface PlacedField {
|
|
140
|
+
field: FormFieldDTO;
|
|
141
|
+
/** The widget placed on the requested page (join key to the annotation
|
|
142
|
+
* plane for auto-selection), or null when the engine reported none. */
|
|
143
|
+
widget: FormFieldDTO['widgets'][number] | null;
|
|
144
|
+
}
|
|
145
|
+
type FormAction = {
|
|
146
|
+
type: 'SET_MODEL';
|
|
147
|
+
model: Model;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The form plugin's public capability: the FIELD plane. Widgets stay
|
|
151
|
+
* annotations (geometry/appearance live there); this surface owns values,
|
|
152
|
+
* interchange, and the fill-mode projection.
|
|
153
|
+
*/
|
|
154
|
+
interface FormCapability {
|
|
155
|
+
/** The current reconciled form state (null until the first load lands). */
|
|
156
|
+
snapshot(): FormSnapshot | null;
|
|
157
|
+
/** Re-read the form from the engine (imports/repair/remote bursts). */
|
|
158
|
+
refresh(): Promise<void>;
|
|
159
|
+
/** Fill controls for one page — content-space, framework-agnostic. */
|
|
160
|
+
fillItems(pageObjectNumber: number): FillItem[];
|
|
161
|
+
/**
|
|
162
|
+
* The fill control for ONE widget, by annotation object number — the join
|
|
163
|
+
* the annotation-plane render layer uses (its RenderItem carries the live
|
|
164
|
+
* box, so this item's `box` is advisory). Reference-stable per model change.
|
|
165
|
+
* Null until the snapshot lands, or for families with no fill control.
|
|
166
|
+
*/
|
|
167
|
+
fillItem(annotObjectNumber: number): FillItem | null;
|
|
168
|
+
/** Make sure a page's widget geometry is loaded (idempotent, lazy). */
|
|
169
|
+
ensureGeom(pageObjectNumber: number): void;
|
|
170
|
+
field(key: FieldKey): FormFieldDTO | null;
|
|
171
|
+
fieldForWidget(annotObjectNumber: number): FormFieldDTO | null;
|
|
172
|
+
/** Commit a text value (call on blur/Enter — keystrokes stay local). */
|
|
173
|
+
setText(key: FieldKey, value: string): Promise<void>;
|
|
174
|
+
/** Toggle a checkbox/radio widget by its on-state; null clears the group. */
|
|
175
|
+
toggle(key: FieldKey, onState: string | null): Promise<void>;
|
|
176
|
+
/** Select choice options by export value. */
|
|
177
|
+
choose(key: FieldKey, values: string[]): Promise<void>;
|
|
178
|
+
/** Commit one originating-client value transaction, including K/V/C/F scripts when enabled. */
|
|
179
|
+
commitValue(ref: FormFieldRef, value: FormFieldValue): Promise<FormCommitResult>;
|
|
180
|
+
/**
|
|
181
|
+
* Execute one widget's `/A` activation. With the actions plugin installed
|
|
182
|
+
* the FULL tree is delegated to its dispatcher (Hide/ResetForm buttons work
|
|
183
|
+
* even with scripting off) — `kind: 'dispatched'`; without it, today's
|
|
184
|
+
* scripting-transaction path runs — `kind: 'form'`.
|
|
185
|
+
*/
|
|
186
|
+
activateWidget(key: FieldKey, annotationRef: AnnotationRef): Promise<WidgetActivationResult>;
|
|
187
|
+
/**
|
|
188
|
+
* Report one widget DOM event (pointer enter/leave, down/up, focus/blur).
|
|
189
|
+
* With the actions plugin present the matching `/AA` tree dispatches
|
|
190
|
+
* (hover rides the shared coalescing pump; `/A` shadows `/AA U` per ISO
|
|
191
|
+
* Table 197); without it — or without a tree — this is a cheap no-op.
|
|
192
|
+
* Fire-and-forget by design: results surface via the actions events.
|
|
193
|
+
*/
|
|
194
|
+
notifyWidgetEvent(key: FieldKey, ref: AnnotationRef, event: PdfAnnotationEventKind): void;
|
|
195
|
+
/** Restore a field to its /DV default. */
|
|
196
|
+
reset(key: FieldKey): Promise<void>;
|
|
197
|
+
/** Raw engine passthrough for anything the sugar above doesn't cover. */
|
|
198
|
+
setValue(ref: FormFieldRef, value: FormFieldValue): Promise<FormSetValueResult>;
|
|
199
|
+
exportData(format?: FormDataFormat): Promise<FormDataExport>;
|
|
200
|
+
importData(data: Uint8Array | ArrayBuffer, format?: FormDataFormat): Promise<FormImportResult>;
|
|
201
|
+
repair(options?: FormRepairOptions): Promise<FormRepairResult>;
|
|
202
|
+
/**
|
|
203
|
+
* Create a field of `family` with one styled widget at a content-space box
|
|
204
|
+
* — the palette tools' commit, and the programmatic authoring entry (works
|
|
205
|
+
* with NO annotation plugin: a pure `doc.forms` call). The box is clamped
|
|
206
|
+
* to the page; sizing policy (click default vs drag rect) is the caller's.
|
|
207
|
+
* The field gets a deterministic auto-name (`text_1`, …; rename in the
|
|
208
|
+
* field panel). Resolves AFTER the annotation plane (when present) has
|
|
209
|
+
* re-read the page, so the returned widget is immediately selectable.
|
|
210
|
+
*/
|
|
211
|
+
placeField(input: PlaceFieldInput): Promise<PlacedField>;
|
|
212
|
+
/** The page's content box (`{0,0,w,h}`) — page-bound placement math. */
|
|
213
|
+
pageBox(pageObjectNumber: number): Box | null;
|
|
214
|
+
/** Field-plane properties: name, required, options, default value. */
|
|
215
|
+
updateField(key: FieldKey, patch: FormFieldPatch): Promise<void>;
|
|
216
|
+
/** Delete the field and every widget of it (cascades on the page). */
|
|
217
|
+
deleteField(key: FieldKey): Promise<void>;
|
|
218
|
+
/** Unlink one widget: it stays as an inert annotation, the field survives. */
|
|
219
|
+
detachWidget(key: FieldKey, annotObjectNumber: number): Promise<void>;
|
|
220
|
+
/**
|
|
221
|
+
* The twins (permissions.md): would the family's verbs succeed now for
|
|
222
|
+
* this session? `canRead` — the form model hydrates at all (false = the
|
|
223
|
+
* hydration gate left it empty by right); `canFill` — value writes
|
|
224
|
+
* (`setText`/`toggle`/`choose`/`reset`; also fused into every
|
|
225
|
+
* `FillItem.disabled`); `canDesign` — the field-design family
|
|
226
|
+
* (`placeField`/`updateField`/`deleteField`/`detachWidget`).
|
|
227
|
+
*/
|
|
228
|
+
canRead(): boolean;
|
|
229
|
+
canFill(): boolean;
|
|
230
|
+
canDesign(): boolean;
|
|
231
|
+
}
|
|
232
|
+
/** What one widget activation did — which world handled it (see
|
|
233
|
+
* {@link FormCapability.activateWidget}). Both framework call sites ignore
|
|
234
|
+
* the value today; chrome that cares can discriminate on `kind`. */
|
|
235
|
+
type WidgetActivationResult = {
|
|
236
|
+
kind: 'form';
|
|
237
|
+
result: FormCommitResult;
|
|
238
|
+
} | {
|
|
239
|
+
kind: 'dispatched';
|
|
240
|
+
result: ActionTriggerResult;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* HOST lens — plugin-to-plugin only (the actions plugin's interim
|
|
244
|
+
* `javascript` / `reset-form` executors). Import the token from
|
|
245
|
+
* `@embedpdf/plugin-form/contract/host`, never from application code.
|
|
246
|
+
*/
|
|
247
|
+
interface FormHostCapability extends FormCapability {
|
|
248
|
+
/**
|
|
249
|
+
* Execute one ResetForm action: resolve targets against the live snapshot
|
|
250
|
+
* with the shared ISO selection (`null` = every field; a parent NAME
|
|
251
|
+
* selects its descendants too; `exclude` = complement; a resolution
|
|
252
|
+
* yielding zero refs never reaches the engine), reset as ONE engine
|
|
253
|
+
* batch, refresh, then recalculate when scripting is enabled (Acrobat's
|
|
254
|
+
* behaviour). `origin` (default `'user'`) is preserved through
|
|
255
|
+
* recalculation surfacing — a lifecycle ResetForm's alerts stay lifecycle.
|
|
256
|
+
*/
|
|
257
|
+
resetFormAction(fields: PdfActionTargetRef[] | null, exclude: boolean, origin?: ActionOrigin): Promise<FormCommitResult>;
|
|
258
|
+
/**
|
|
259
|
+
* The submit dataset resolver (Phase 4, D7): fresh engine read + the pure
|
|
260
|
+
* ISO builder (`field-selection.ts`). Registered with the actions plugin
|
|
261
|
+
* as THE resolver for both action-node and script submits.
|
|
262
|
+
*/
|
|
263
|
+
resolveSubmitDataset(intent: SubmitIntent, ctx: ActionContext, diagnose: (diagnostic: ActionDiagnostic) => void): Promise<ActionSubmitRequest>;
|
|
264
|
+
/** The form DOCUMENT-commit sink (D3): engine `applyEffects` + snapshot
|
|
265
|
+
* reconciliation. Sink contract: never throws, never enqueues, never
|
|
266
|
+
* touches the script host — callable under a held host transaction. */
|
|
267
|
+
commitScriptFormEffects(effects: FormEffect[]): Promise<FormEffectsResult>;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The form capability token. Typed to the full {@link FormHostCapability}
|
|
271
|
+
* here (the package internals + the `/internal` entry use this view). The
|
|
272
|
+
* package root re-exports the SAME token narrowed to {@link FormCapability}.
|
|
273
|
+
*/
|
|
274
|
+
declare const FormToken: import("@embedpdf/core").CapabilityToken<FormHostCapability>;
|
|
275
|
+
//#endregion
|
|
276
|
+
export { fieldKeyOf as _, FormHostCapability as a, FormState as c, PlaceFieldInput as d, PlacedField as f, FieldKey as g, Box as h, FormCommitStatus as i, FormToken as l, FillItem as m, FormCapability as n, FormPluginOptions as o, WidgetActivationResult as p, FormCommitResult as r, FormScriptingOptions as s, FormAction as t, FormUiEffect as u };
|
|
277
|
+
//# sourceMappingURL=types-Blr4QQM5.d.cts.map
|