@embedpdf/angular 3.0.0-next.14 → 3.0.0-next.16

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.
Files changed (87) hide show
  1. package/README.md +309 -0
  2. package/dist/README.md +309 -0
  3. package/dist/actions/index.d.ts +34 -16
  4. package/dist/anchored/index.d.ts +67 -0
  5. package/dist/annotation/index.d.ts +602 -0
  6. package/dist/attachment/index.d.ts +29 -0
  7. package/dist/commands/index.d.ts +109 -0
  8. package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
  9. package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
  10. package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
  11. package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
  12. package/dist/fesm2022/embedpdf-angular-annotation.mjs +1933 -0
  13. package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
  14. package/dist/fesm2022/embedpdf-angular-attachment.mjs +67 -0
  15. package/dist/fesm2022/embedpdf-angular-attachment.mjs.map +1 -0
  16. package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
  17. package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
  18. package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
  19. package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
  20. package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
  21. package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
  22. package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
  23. package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
  24. package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
  25. package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
  26. package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
  27. package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
  28. package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
  29. package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
  30. package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
  31. package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
  32. package/dist/fesm2022/embedpdf-angular-outline.mjs +76 -0
  33. package/dist/fesm2022/embedpdf-angular-outline.mjs.map +1 -0
  34. package/dist/fesm2022/embedpdf-angular-page-edit.mjs +68 -0
  35. package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
  36. package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
  37. package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
  38. package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
  39. package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
  40. package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
  41. package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
  42. package/dist/fesm2022/embedpdf-angular-runtime.mjs +1151 -390
  43. package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
  44. package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
  45. package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
  46. package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
  47. package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
  48. package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
  49. package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
  50. package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
  51. package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
  52. package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
  53. package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
  54. package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
  55. package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
  56. package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
  57. package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
  58. package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
  59. package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
  60. package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
  61. package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
  62. package/dist/fesm2022/embedpdf-angular.mjs +25 -3
  63. package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
  64. package/dist/form/index.d.ts +79 -0
  65. package/dist/history/index.d.ts +28 -0
  66. package/dist/i18n/index.d.ts +52 -0
  67. package/dist/index.d.ts +22 -0
  68. package/dist/interaction/index.d.ts +58 -0
  69. package/dist/link/index.d.ts +77 -0
  70. package/dist/measurement/index.d.ts +46 -0
  71. package/dist/metadata/index.d.ts +51 -0
  72. package/dist/outline/index.d.ts +32 -0
  73. package/dist/page-edit/index.d.ts +27 -0
  74. package/dist/page-view/index.d.ts +94 -0
  75. package/dist/redaction/index.d.ts +38 -0
  76. package/dist/render/index.d.ts +45 -6
  77. package/dist/runtime/index.d.ts +579 -199
  78. package/dist/search/index.d.ts +71 -0
  79. package/dist/selection/index.d.ts +141 -0
  80. package/dist/shell/index.d.ts +88 -0
  81. package/dist/signature/index.d.ts +45 -0
  82. package/dist/stage/index.d.ts +242 -141
  83. package/dist/stamp/index.d.ts +58 -0
  84. package/dist/testing/index.d.ts +53 -0
  85. package/dist/toolbar/index.d.ts +257 -0
  86. package/dist/view-manager/index.d.ts +23 -0
  87. package/package.json +145 -14
@@ -0,0 +1,1074 @@
1
+ import { formState, FormToken, formPlugin, FORM_DEFAULTS } from '@embedpdf/plugin-form';
2
+ export * from '@embedpdf/plugin-form';
3
+ import * as i0 from '@angular/core';
4
+ import { assertInInjectionContext, untracked, effect, inject, DestroyRef, Injectable, input, ElementRef, computed, signal, afterRenderEffect, PLATFORM_ID, ChangeDetectionStrategy, Component, Directive, viewChild } from '@angular/core';
5
+ import { FormControl } from '@angular/forms';
6
+ import { pluginService, injectPage, CapabilityBinding, injectKernelHost, EpdfSvgShapes, paintsPagePart } from '@embedpdf/angular/runtime';
7
+ import { FormToken as FormToken$1 } from '@embedpdf/plugin-form/contract/host';
8
+ import { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';
9
+ import { createOptimisticSelection, showSelectedOptions, isolateWheel, rectInPixels, widgetBoxStyleOf, bindWidgetEvents, isolatePointerDown, formFocusRingStyleOf, createTextFieldEditor, textFieldEditorStyleOf, createFontMounts, pressToggle, FORM_CONTROL_FILL, listBoxControlStyleOf, createShownUrls, pictureOrPainting, shownAppearance, svgShapesOf, loadAppearanceUrls, formColorsOf } from '@embedpdf/web';
10
+ import { isPlatformBrowser } from '@angular/common';
11
+ import { SignatureToken } from '@embedpdf/plugin-signature/contract';
12
+ import { annotationKey } from '@embedpdf/core';
13
+ import { MITER_LIMIT } from '@embedpdf/core-annotation';
14
+ import { RenderToken } from '@embedpdf/plugin-render/contract/host';
15
+
16
+ /**
17
+ * The form plugin's service and feature:
18
+ *
19
+ * withForm(config) the plugin, for provideEmbedPdf()
20
+ * inject(EpdfForm) read, fill and build the form: `fields()`, `setValue()`, `create()`,
21
+ * the events as streams (`valueChanged$`), and the settings
22
+ * form.valueOf(ref) one field's value, as a signal
23
+ * form.controlOf(ref) one field as a Reactive Forms `FormControl`, for your own inputs
24
+ */
25
+ const refGetter = (ref) => typeof ref === 'function' ? ref : () => ref;
26
+ /** Two plain values the same: lists element by element, everything else by value. */
27
+ const samePlain = (left, right) => Array.isArray(left) && Array.isArray(right)
28
+ ? left.length === right.length && left.every((value, index) => value === right[index])
29
+ : left === right;
30
+ /**
31
+ * The form: `fields()`, `status()`, `formKind()` and `selectedField()` as signals; reading,
32
+ * filling, form data and building as methods (`setValue()`, `importValues()`, `export()`,
33
+ * `create()`, `update()`, …, the Methods tables of the Filling forms and Building forms pages);
34
+ * the events as streams (`valueChanged$`, `validationRejected$`, `fieldCreated$`, …); and the
35
+ * settings. Without a document, `fields()` is empty and a method refuses with `not-ready`.
36
+ */
37
+ class EpdfForm extends pluginService({
38
+ name: 'EpdfForm',
39
+ feature: 'withForm()',
40
+ token: FormToken,
41
+ state: formState,
42
+ methods: [
43
+ 'getSnapshot',
44
+ 'getStatus',
45
+ 'getFormKind',
46
+ 'list',
47
+ 'get',
48
+ 'getFieldForWidget',
49
+ 'getWidget',
50
+ 'getValue',
51
+ 'listWidgets',
52
+ 'getWidgetAt',
53
+ 'getSelectedField',
54
+ 'validate',
55
+ 'exportValues',
56
+ 'setValue',
57
+ 'setValues',
58
+ 'importValues',
59
+ 'reset',
60
+ 'activateWidget',
61
+ 'export',
62
+ 'import',
63
+ 'refresh',
64
+ 'create',
65
+ 'update',
66
+ 'delete',
67
+ 'removeWidget',
68
+ 'deleteWidget',
69
+ 'reorderCalculations',
70
+ 'repair',
71
+ 'canRead',
72
+ 'canFill',
73
+ 'canDesign',
74
+ 'canRestoreAttribution',
75
+ 'canWriteScripts',
76
+ ],
77
+ events: [
78
+ 'onValueChanged',
79
+ 'onFieldCreated',
80
+ 'onFieldUpdated',
81
+ 'onFieldDeleted',
82
+ 'onValidationRejected',
83
+ 'onResynced',
84
+ ],
85
+ }) {
86
+ /**
87
+ * One field's value, in the shape `setValue()` takes, as a signal: it changes only when that
88
+ * field's value does, and is `null` for a field without a value, and without a document. Keep
89
+ * it in a field (`readonly total = form.valueOf(toFieldRef('total'))`); `ref` may be a
90
+ * function, such as a signal input, and the value follows it.
91
+ *
92
+ * (`override`: every object has a `valueOf()` without arguments, which JavaScript calls to
93
+ * turn it into a primitive. Called that way, this returns a signal, which isn't one, so
94
+ * JavaScript goes on to `toString()` as it would without it.)
95
+ */
96
+ valueOf(ref) {
97
+ const refOf = refGetter(ref);
98
+ // The plugin keeps one value object per field while the field stays the same.
99
+ return this.binding.select((form) => form.getValue(refOf()), null, Object.is);
100
+ }
101
+ /**
102
+ * One field as a Reactive Forms `FormControl`, for an input of your own:
103
+ * `<input [formControl]="name" />`. The control holds the field's value as plain data (a text
104
+ * box's, dropdown's or radio group's text, a checkbox's `true` or `false`, a list's selected
105
+ * values; `null` for none), and takes every change of it, whoever made it. A change of the
106
+ * control fills in the field, with the same checks and scripts as typing in the PDF does; a
107
+ * value the form refuses puts the field's value back in the control. The control is disabled
108
+ * while the field can't be filled in: no such field (yet), a read-only one, or no
109
+ * `doc.forms.fill`.
110
+ *
111
+ * Call it in an injection context (a constructor or a field initializer): the control follows
112
+ * the field for as long as the caller lives.
113
+ */
114
+ controlOf(ref) {
115
+ assertInInjectionContext(this.controlOf);
116
+ const refOf = refGetter(ref);
117
+ const field = this.binding.select((form) => form.get(refOf()), null, Object.is);
118
+ // The plain value the plugin exports for the field, so a control reads what
119
+ // `exportValues()` gives and writes what `importValues()` takes.
120
+ const value = this.binding.select((form) => {
121
+ const current = form.get(refOf());
122
+ return current ? (form.exportValues()[current.name] ?? null) : null;
123
+ }, null, samePlain);
124
+ const fillable = this.binding.select((form) => {
125
+ const current = form.get(refOf());
126
+ return current !== null && !current.readOnly && form.canFill(current.ref);
127
+ }, false);
128
+ const control = new FormControl(untracked(value));
129
+ const show = (plain) => {
130
+ if (!samePlain(plain, control.value))
131
+ control.setValue(plain, { emitEvent: false });
132
+ };
133
+ // The field shows a write as soon as it's made, so the control follows the field: a value
134
+ // the form refuses, or its scripts rewrite, shows as the field has it.
135
+ effect(() => {
136
+ const plain = value();
137
+ untracked(() => show(plain));
138
+ });
139
+ effect(() => {
140
+ const enabled = fillable();
141
+ untracked(() => enabled ? control.enable({ emitEvent: false }) : control.disable({ emitEvent: false }));
142
+ });
143
+ const subscription = control.valueChanges.subscribe((plain) => {
144
+ const name = untracked(field)?.name;
145
+ if (name === undefined)
146
+ return;
147
+ // By name, through the plugin's own plain-value conversion and its write queue.
148
+ void Promise.resolve()
149
+ .then(() => this.importValues({ [name]: plain }))
150
+ .catch(() => undefined);
151
+ });
152
+ inject(DestroyRef).onDestroy(() => subscription.unsubscribe());
153
+ return control;
154
+ }
155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfForm, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
156
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfForm, providedIn: 'root' });
157
+ }
158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfForm, decorators: [{
159
+ type: Injectable,
160
+ args: [{ providedIn: 'root' }]
161
+ }] });
162
+ /** The form plugin: fields people fill in on every page with an `<epdf-form-layer>`. */
163
+ function withForm(config) {
164
+ return { plugins: [formPlugin(config)], services: [EpdfForm] };
165
+ }
166
+
167
+ /**
168
+ * A PDF list box as a visible native `<select>`. A list needs one owner for its pixels, its
169
+ * hit-testing and its scrolling: a baked PDF picture under an invisible select can't give that,
170
+ * because the browser and the PDF would each keep their own top row. So the select is the list,
171
+ * and it's never made again while the field is shown: its scroll position is the user's.
172
+ *
173
+ * A choice shows at once while its write is on its way, and the engine's selection again if
174
+ * the write fails (`@embedpdf/web`'s `createOptimisticSelection`). The wheel stays in the list,
175
+ * so it scrolls instead of the Stage below panning or zooming.
176
+ */
177
+ class FormListBox {
178
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
179
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
180
+ /** Whether several rows can be selected. */
181
+ multi = input(false, ...(ngDevMode ? [{ debugName: "multi" }] : []));
182
+ options = input.required(...(ngDevMode ? [{ debugName: "options" }] : []));
183
+ /** The selection the engine has. */
184
+ selected = input.required(...(ngDevMode ? [{ debugName: "selected" }] : []));
185
+ /** Writes a choice; a promise that rejects puts the engine's selection back. */
186
+ write = input.required(...(ngDevMode ? [{ debugName: "write" }] : []));
187
+ element = inject(ElementRef).nativeElement;
188
+ /** Every row shows at least two, so a list never looks like a dropdown. */
189
+ size = computed(() => Math.max(2, this.options().length), ...(ngDevMode ? [{ debugName: "size" }] : []));
190
+ /**
191
+ * The optimistic selection, made at the first read (inputs are there by then) from the
192
+ * engine's selection. It reads no signal, so it's made once.
193
+ */
194
+ selection = computed(() => createOptimisticSelection(untracked(this.selected)), ...(ngDevMode ? [{ debugName: "selection" }] : []));
195
+ /** Goes up whenever the selection shows something new. */
196
+ changes = signal(0, ...(ngDevMode ? [{ debugName: "changes" }] : []));
197
+ /** The selection the list shows. */
198
+ shown = computed(() => {
199
+ this.changes();
200
+ return this.selection().get();
201
+ }, ...(ngDevMode ? [{ debugName: "shown" }] : []));
202
+ constructor() {
203
+ effect((onCleanup) => {
204
+ const selection = this.selection();
205
+ onCleanup(selection.subscribe(() => this.changes.update((count) => count + 1)));
206
+ });
207
+ effect(() => {
208
+ const selected = this.selected();
209
+ untracked(() => this.selection().setConfirmed(selected));
210
+ });
211
+ // The rows' selected state is written after each render, not bound: a choice that is
212
+ // refused before the next render leaves the shown selection as it was, and a binding that
213
+ // didn't change would leave the user's row selected.
214
+ afterRenderEffect({
215
+ write: () => {
216
+ const shown = this.shown();
217
+ this.options();
218
+ showSelectedOptions(this.element, shown);
219
+ },
220
+ });
221
+ if (isPlatformBrowser(inject(PLATFORM_ID))) {
222
+ inject(DestroyRef).onDestroy(isolateWheel(this.element));
223
+ }
224
+ }
225
+ choose() {
226
+ const values = Array.from(this.element.selectedOptions, (option) => option.value);
227
+ this.selection().choose(values, (chosen) => this.write()(chosen) ?? undefined);
228
+ }
229
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormListBox, deps: [], target: i0.ɵɵFactoryTarget.Component });
230
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormListBox, isStandalone: true, selector: "select[epdfFormListBox]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multi: { classPropertyName: "multi", publicName: "multi", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: true, transformFunction: null }, write: { classPropertyName: "write", publicName: "write", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "change": "choose()" }, properties: { "attr.aria-label": "label()", "multiple": "multi()", "attr.size": "size()", "disabled": "disabled()" } }, ngImport: i0, template: `
231
+ @for (option of options(); track $index) {
232
+ <option [value]="option.value">{{ option.label }}</option>
233
+ }
234
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
235
+ }
236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormListBox, decorators: [{
237
+ type: Component,
238
+ args: [{
239
+ selector: 'select[epdfFormListBox]',
240
+ changeDetection: ChangeDetectionStrategy.OnPush,
241
+ host: {
242
+ '[attr.aria-label]': 'label()',
243
+ '[multiple]': 'multi()',
244
+ '[attr.size]': 'size()',
245
+ '[disabled]': 'disabled()',
246
+ '(change)': 'choose()',
247
+ },
248
+ template: `
249
+ @for (option of options(); track $index) {
250
+ <option [value]="option.value">{{ option.label }}</option>
251
+ }
252
+ `,
253
+ }]
254
+ }], ctorParameters: () => [] });
255
+
256
+ /**
257
+ * What every field control on a page shares: its box. The box is placed over the field's
258
+ * widget on the page, and is the widget's event surface:
259
+ *
260
+ * - it is always there for the pointer, whatever the control inside allows, so a field that
261
+ * can't be filled in still runs its PDF actions on enter, exit, press, release, focus and blur
262
+ * ("may edit this field" and "may receive PDF action events" are different rights);
263
+ * - it keeps a press that begins in it from the Stage below (which would start the active
264
+ * tool's gesture, or end an edit as a click outside), and still sends it to the field's PDF
265
+ * "mouse down" action;
266
+ * - a field without a border of its own gets an edge in the form settings' color, so people
267
+ * see where to fill in (lists and buttons draw their own).
268
+ *
269
+ * Each control (`controls.ts`) is a component whose host element is its box: it extends this
270
+ * class and adds its own control inside. The listeners, the box's style and the colors are
271
+ * `@embedpdf/web`'s, the same for every framework.
272
+ */
273
+ class FormWidgetBox {
274
+ /** The widget: where it is, how it looks, its control and its value. */
275
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
276
+ /** The colors the viewer draws fields with: the form settings over the accent. */
277
+ colors = input.required(...(ngDevMode ? [{ debugName: "colors" }] : []));
278
+ page = injectPage('<epdf-form-layer>');
279
+ element = inject(ElementRef).nativeElement;
280
+ /** The form plugin's host side, for the page's own document: writes, drafts, activation. */
281
+ form = new CapabilityBinding(injectKernelHost('<epdf-form-layer>'), () => FormToken$1, () => this.page.documentId).capability;
282
+ /** Whether a field without a border of its own gets an edge. A list and a button draw their own. */
283
+ drawsEdge = true;
284
+ /** The box in the page layer's pixels: moves with the camera. */
285
+ frame = computed(() => rectInPixels(this.item().box, this.page.transform()), ...(ngDevMode ? [{ debugName: "frame" }] : []));
286
+ /** The box's place over the widget, and its edge. */
287
+ boxStyle = computed(() => widgetBoxStyleOf(this.item(), this.frame(), this.colors(), { edge: this.drawsEdge }), ...(ngDevMode ? [{ debugName: "boxStyle" }] : []));
288
+ constructor() {
289
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
290
+ return;
291
+ // Native listeners on the box: the PDF events of the widget, and the press kept from the
292
+ // Stage. `bindWidgetEvents` sends the press as "mouse down" itself; the press only stops
293
+ // here, so both run.
294
+ const detachEvents = bindWidgetEvents(this.element, (kind) => this.notify(kind));
295
+ const detachPress = isolatePointerDown(this.element);
296
+ inject(DestroyRef).onDestroy(() => {
297
+ detachEvents();
298
+ detachPress();
299
+ });
300
+ }
301
+ /**
302
+ * Run the widget's own action, as a click on it does. ISO 32000 puts the activate action on
303
+ * the widget, whatever its field, and many forms ship "buttons" as read-only text fields with
304
+ * an action that Acrobat runs; a widget without one does nothing.
305
+ */
306
+ activate() {
307
+ const widget = this.item().annotationRef;
308
+ const form = this.form();
309
+ if (widget && form)
310
+ void form.activateWidget(widget);
311
+ }
312
+ /** One of the widget's pointer and focus events, for its `/AA` actions. */
313
+ notify(kind) {
314
+ const item = this.item();
315
+ const form = this.form();
316
+ if (item.annotationRef && form)
317
+ form.notifyWidgetEvent(item.fieldRef, item.annotationRef, kind);
318
+ }
319
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormWidgetBox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
320
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: FormWidgetBox, isStandalone: true, inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style": "boxStyle()" } }, ngImport: i0 });
321
+ }
322
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormWidgetBox, decorators: [{
323
+ type: Directive,
324
+ args: [{
325
+ host: { '[style]': 'boxStyle()' },
326
+ }]
327
+ }], ctorParameters: () => [] });
328
+
329
+ /**
330
+ * The controls `<epdf-form-layer>` puts over a page's fields, one per widget. Each is a
331
+ * component whose host element is the widget's box (`widget-box.ts`), with a real HTML control
332
+ * inside. The field's own picture (the engine's drawing of its value, borders and fonts) is
333
+ * drawn below, by the form layer's `<epdf-field-pictures>`; the controls add what people work
334
+ * with on top of it:
335
+ *
336
+ * text the picture at rest; focus shows an editor in the field's own font (the plugin
337
+ * keeps what's typed, so a download writes it); blur or Enter commits, Escape
338
+ * puts the value back
339
+ * toggle the picture is the control; a click writes the toggled value
340
+ * combo an invisible native <select> over the picture: the browser owns the dropdown,
341
+ * the engine the pixels at rest
342
+ * list a visible native <select> (`list-box.ts`): one surface owns the rows, the
343
+ * keyboard and the scrolling
344
+ * button a native click target over the picture, which runs the widget's action
345
+ * signature with the signature plugin, "sign here" on an empty field, and the signature's
346
+ * details on a signed one
347
+ *
348
+ * The text-editing, toggle and list policies, the field looks, the controls' styles and the
349
+ * colors are `@embedpdf/web`'s, the same for every framework.
350
+ */
351
+ /**
352
+ * The focus ring of a field whose control is see-through or is the box itself: painted above
353
+ * the field's picture, because an outline on the box would sit under an opaque child, and a
354
+ * checkbox or a dropdown would look unfocused while it has the focus.
355
+ */
356
+ class FormFocusRing {
357
+ /** The ring's CSS color: the form's `focus.color` setting, through its CSS variable. */
358
+ color = input.required(...(ngDevMode ? [{ debugName: "color" }] : []));
359
+ ringStyle = computed(() => formFocusRingStyleOf(this.color()), ...(ngDevMode ? [{ debugName: "ringStyle" }] : []));
360
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormFocusRing, deps: [], target: i0.ɵɵFactoryTarget.Component });
361
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.2.4", type: FormFocusRing, isStandalone: true, selector: "span[epdfFormFocusRing]", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-hidden": "true", "data-embedpdf-form-focus-ring": "" }, properties: { "style": "ringStyle()" } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
362
+ }
363
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormFocusRing, decorators: [{
364
+ type: Component,
365
+ args: [{
366
+ selector: 'span[epdfFormFocusRing]',
367
+ changeDetection: ChangeDetectionStrategy.OnPush,
368
+ host: {
369
+ 'aria-hidden': 'true',
370
+ 'data-embedpdf-form-focus-ring': '',
371
+ '[style]': 'ringStyle()',
372
+ },
373
+ template: '',
374
+ }]
375
+ }] });
376
+ /** A text box: the picture at rest, an editor in the field's own font while focused. */
377
+ class FormTextControl extends FormWidgetBox {
378
+ /**
379
+ * The editing policy of this field. The editor is always there, see-through at rest and
380
+ * shown while focused: the DOM is the focus manager, so Tab reaches every field. A control is
381
+ * one widget for its whole life (the layer tracks widgets by field and widget), so one
382
+ * editor per form.
383
+ */
384
+ editor = computed(() => {
385
+ const form = this.form();
386
+ return form
387
+ ? untracked(() => createTextFieldEditor(form, this.item().fieldRef, this.item().value))
388
+ : null;
389
+ }, ...(ngDevMode ? [{ debugName: "editor" }] : []));
390
+ state = signal({ focused: false, draft: '' }, ...(ngDevMode ? [{ debugName: "state" }] : []));
391
+ /** The text being typed, where the engine will draw it, in its face. */
392
+ layout = computed(() => {
393
+ const form = this.form();
394
+ const widget = this.item().annotationRef;
395
+ return form && widget ? form.getTextEditorLayout(widget, this.state().draft) : null;
396
+ }, ...(ngDevMode ? [{ debugName: "layout" }] : []));
397
+ editorStyle = computed(() => textFieldEditorStyleOf(this.item(), this.frame(), this.colors(), this.state().focused, this.layout()), ...(ngDevMode ? [{ debugName: "editorStyle" }] : []));
398
+ constructor() {
399
+ super();
400
+ if (isPlatformBrowser(inject(PLATFORM_ID))) {
401
+ const mounts = createFontMounts();
402
+ inject(DestroyRef).onDestroy(() => mounts.release());
403
+ effect(() => {
404
+ const layout = this.layout();
405
+ if (layout)
406
+ mounts.mount([layout.program]);
407
+ });
408
+ }
409
+ effect((onCleanup) => {
410
+ const editor = this.editor();
411
+ if (!editor)
412
+ return;
413
+ untracked(() => this.state.set(editor.getState()));
414
+ onCleanup(editor.subscribe(() => this.state.set(editor.getState())));
415
+ });
416
+ // Take the field's value whenever it changes under us; the editor never takes it mid-edit.
417
+ effect(() => {
418
+ const editor = this.editor();
419
+ const value = this.item().value;
420
+ if (editor)
421
+ untracked(() => editor.setValue(value));
422
+ });
423
+ }
424
+ keyDown(event, field) {
425
+ if (this.editor()?.keyDown(event.key, this.item().multiline))
426
+ field.blur();
427
+ }
428
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormTextControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
429
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormTextControl, isStandalone: true, selector: "div[epdfFormText]", host: { listeners: { "click": "activate()" } }, usesInheritance: true, ngImport: i0, template: `
430
+ @if (item().multiline) {
431
+ <textarea
432
+ #field
433
+ [value]="state().draft"
434
+ [attr.maxlength]="item().maxLength"
435
+ [attr.aria-label]="item().label"
436
+ [disabled]="item().disabled"
437
+ [style]="editorStyle()"
438
+ (focus)="editor()?.focus()"
439
+ (input)="editor()?.input(field.value)"
440
+ (blur)="editor()?.blur()"
441
+ (keydown)="keyDown($event, field)"
442
+ ></textarea>
443
+ } @else {
444
+ <input
445
+ #field
446
+ [type]="item().password ? 'password' : 'text'"
447
+ [value]="state().draft"
448
+ [attr.maxlength]="item().maxLength"
449
+ [attr.aria-label]="item().label"
450
+ [disabled]="item().disabled"
451
+ [style]="editorStyle()"
452
+ (focus)="editor()?.focus()"
453
+ (input)="editor()?.input(field.value)"
454
+ (blur)="editor()?.blur()"
455
+ (keydown)="keyDown($event, field)"
456
+ />
457
+ }
458
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
459
+ }
460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormTextControl, decorators: [{
461
+ type: Component,
462
+ args: [{
463
+ selector: 'div[epdfFormText]',
464
+ changeDetection: ChangeDetectionStrategy.OnPush,
465
+ // A click runs the widget's action too, as in Acrobat: the editor's focus click bubbles
466
+ // here, and a read-only field's lands here directly.
467
+ host: { '(click)': 'activate()' },
468
+ template: `
469
+ @if (item().multiline) {
470
+ <textarea
471
+ #field
472
+ [value]="state().draft"
473
+ [attr.maxlength]="item().maxLength"
474
+ [attr.aria-label]="item().label"
475
+ [disabled]="item().disabled"
476
+ [style]="editorStyle()"
477
+ (focus)="editor()?.focus()"
478
+ (input)="editor()?.input(field.value)"
479
+ (blur)="editor()?.blur()"
480
+ (keydown)="keyDown($event, field)"
481
+ ></textarea>
482
+ } @else {
483
+ <input
484
+ #field
485
+ [type]="item().password ? 'password' : 'text'"
486
+ [value]="state().draft"
487
+ [attr.maxlength]="item().maxLength"
488
+ [attr.aria-label]="item().label"
489
+ [disabled]="item().disabled"
490
+ [style]="editorStyle()"
491
+ (focus)="editor()?.focus()"
492
+ (input)="editor()?.input(field.value)"
493
+ (blur)="editor()?.blur()"
494
+ (keydown)="keyDown($event, field)"
495
+ />
496
+ }
497
+ `,
498
+ }]
499
+ }], ctorParameters: () => [] });
500
+ /** A checkbox or a radio button: the picture is the control, a click writes the toggled value. */
501
+ class FormToggleControl extends FormWidgetBox {
502
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
503
+ /** The value first, then the widget's action (Acrobat's order), so its script reads the new state. */
504
+ press() {
505
+ const form = this.form();
506
+ if (form)
507
+ pressToggle(form, this.item(), () => this.activate());
508
+ }
509
+ keyDown(event) {
510
+ if (event.key !== ' ' && event.key !== 'Enter')
511
+ return;
512
+ event.preventDefault();
513
+ this.press();
514
+ }
515
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormToggleControl, deps: null, target: i0.ɵɵFactoryTarget.Component });
516
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormToggleControl, isStandalone: true, selector: "div[epdfFormToggle]", host: { listeners: { "click": "press()", "keydown": "keyDown($event)", "focus": "focused.set(true)", "blur": "focused.set(false)" }, properties: { "attr.role": "item().kind === 'checkbox' ? 'checkbox' : 'radio'", "attr.aria-checked": "item().checked", "attr.aria-label": "item().label", "attr.tabindex": "item().disabled ? -1 : 0", "style.cursor": "item().disabled ? 'default' : 'pointer'", "style.outline": "'none'" } }, usesInheritance: true, ngImport: i0, template: `
517
+ @if (focused()) {
518
+ <span epdfFormFocusRing [color]="colors().focus"></span>
519
+ }
520
+ `, isInline: true, dependencies: [{ kind: "component", type: FormFocusRing, selector: "span[epdfFormFocusRing]", inputs: ["color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
521
+ }
522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormToggleControl, decorators: [{
523
+ type: Component,
524
+ args: [{
525
+ selector: 'div[epdfFormToggle]',
526
+ imports: [FormFocusRing],
527
+ changeDetection: ChangeDetectionStrategy.OnPush,
528
+ host: {
529
+ '[attr.role]': "item().kind === 'checkbox' ? 'checkbox' : 'radio'",
530
+ '[attr.aria-checked]': 'item().checked',
531
+ '[attr.aria-label]': 'item().label',
532
+ '[attr.tabindex]': 'item().disabled ? -1 : 0',
533
+ '[style.cursor]': "item().disabled ? 'default' : 'pointer'",
534
+ '[style.outline]': "'none'",
535
+ '(click)': 'press()',
536
+ '(keydown)': 'keyDown($event)',
537
+ '(focus)': 'focused.set(true)',
538
+ '(blur)': 'focused.set(false)',
539
+ },
540
+ template: `
541
+ @if (focused()) {
542
+ <span epdfFormFocusRing [color]="colors().focus"></span>
543
+ }
544
+ `,
545
+ }]
546
+ }] });
547
+ /** A dropdown: an invisible native select over the field's picture. */
548
+ class FormComboControl extends FormWidgetBox {
549
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
550
+ select = viewChild.required('select');
551
+ /** The field's choice, and its options, by value: what the select is set to show. */
552
+ choice = computed(() => this.item().selected[0] ?? '', ...(ngDevMode ? [{ debugName: "choice" }] : []));
553
+ optionValues = computed(() => this.item().options.map((option) => option.value).join('\0'), ...(ngDevMode ? [{ debugName: "optionValues" }] : []));
554
+ selectStyle = computed(() => {
555
+ const { disabled } = this.item();
556
+ return {
557
+ ...FORM_CONTROL_FILL,
558
+ opacity: '0',
559
+ cursor: disabled ? 'default' : 'pointer',
560
+ 'pointer-events': disabled ? 'none' : null,
561
+ };
562
+ }, ...(ngDevMode ? [{ debugName: "selectStyle" }] : []));
563
+ constructor() {
564
+ super();
565
+ // The select shows the field's choice whenever that changes, and is the user's otherwise:
566
+ // a write on its way (which disables the field for a moment) leaves their choice alone.
567
+ afterRenderEffect({
568
+ write: () => {
569
+ const choice = this.choice();
570
+ this.optionValues();
571
+ this.select().nativeElement.value = choice;
572
+ },
573
+ });
574
+ }
575
+ choose(value) {
576
+ void this.form()?.setValue(this.item().fieldRef, { value: value || null });
577
+ }
578
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormComboControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
579
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormComboControl, isStandalone: true, selector: "div[epdfFormCombo]", host: { listeners: { "click": "activate()" } }, viewQueries: [{ propertyName: "select", first: true, predicate: ["select"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
580
+ <select
581
+ #select
582
+ [attr.aria-label]="item().label"
583
+ [disabled]="item().disabled"
584
+ [style]="selectStyle()"
585
+ (focus)="focused.set(true)"
586
+ (blur)="focused.set(false)"
587
+ (change)="choose(select.value)"
588
+ >
589
+ @if (item().selected.length === 0) {
590
+ <option value=""></option>
591
+ }
592
+ @for (option of item().options; track $index) {
593
+ <option [value]="option.value">{{ option.label }}</option>
594
+ }
595
+ </select>
596
+ @if (focused()) {
597
+ <span epdfFormFocusRing [color]="colors().focus"></span>
598
+ }
599
+ `, isInline: true, dependencies: [{ kind: "component", type: FormFocusRing, selector: "span[epdfFormFocusRing]", inputs: ["color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
600
+ }
601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormComboControl, decorators: [{
602
+ type: Component,
603
+ args: [{
604
+ selector: 'div[epdfFormCombo]',
605
+ imports: [FormFocusRing],
606
+ changeDetection: ChangeDetectionStrategy.OnPush,
607
+ host: { '(click)': 'activate()' },
608
+ template: `
609
+ <select
610
+ #select
611
+ [attr.aria-label]="item().label"
612
+ [disabled]="item().disabled"
613
+ [style]="selectStyle()"
614
+ (focus)="focused.set(true)"
615
+ (blur)="focused.set(false)"
616
+ (change)="choose(select.value)"
617
+ >
618
+ @if (item().selected.length === 0) {
619
+ <option value=""></option>
620
+ }
621
+ @for (option of item().options; track $index) {
622
+ <option [value]="option.value">{{ option.label }}</option>
623
+ }
624
+ </select>
625
+ @if (focused()) {
626
+ <span epdfFormFocusRing [color]="colors().focus"></span>
627
+ }
628
+ `,
629
+ }]
630
+ }], ctorParameters: () => [] });
631
+ /** A list: a visible native select in the field's own look, the settings where it has none. */
632
+ class FormListControl extends FormWidgetBox {
633
+ drawsEdge = false;
634
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
635
+ listStyle = computed(() => listBoxControlStyleOf(this.item(), this.frame(), this.colors()), ...(ngDevMode ? [{ debugName: "listStyle" }] : []));
636
+ /** The list's write: the selection it shows at once, and the engine's once this resolves. */
637
+ write = (values) => this.form()?.setValue(this.item().fieldRef, { selectedValues: values });
638
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormListControl, deps: null, target: i0.ɵɵFactoryTarget.Component });
639
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormListControl, isStandalone: true, selector: "div[epdfFormList]", host: { listeners: { "click": "activate()" } }, usesInheritance: true, ngImport: i0, template: `
640
+ <select
641
+ epdfFormListBox
642
+ [label]="item().label"
643
+ [disabled]="item().disabled"
644
+ [multi]="item().multi"
645
+ [options]="item().options"
646
+ [selected]="item().selected"
647
+ [write]="write"
648
+ [style]="listStyle()"
649
+ (focus)="focused.set(true)"
650
+ (blur)="focused.set(false)"
651
+ ></select>
652
+ @if (focused()) {
653
+ <span epdfFormFocusRing [color]="colors().focus"></span>
654
+ }
655
+ `, isInline: true, dependencies: [{ kind: "component", type: FormFocusRing, selector: "span[epdfFormFocusRing]", inputs: ["color"] }, { kind: "component", type: FormListBox, selector: "select[epdfFormListBox]", inputs: ["label", "disabled", "multi", "options", "selected", "write"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
656
+ }
657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormListControl, decorators: [{
658
+ type: Component,
659
+ args: [{
660
+ selector: 'div[epdfFormList]',
661
+ imports: [FormFocusRing, FormListBox],
662
+ changeDetection: ChangeDetectionStrategy.OnPush,
663
+ host: { '(click)': 'activate()' },
664
+ template: `
665
+ <select
666
+ epdfFormListBox
667
+ [label]="item().label"
668
+ [disabled]="item().disabled"
669
+ [multi]="item().multi"
670
+ [options]="item().options"
671
+ [selected]="item().selected"
672
+ [write]="write"
673
+ [style]="listStyle()"
674
+ (focus)="focused.set(true)"
675
+ (blur)="focused.set(false)"
676
+ ></select>
677
+ @if (focused()) {
678
+ <span epdfFormFocusRing [color]="colors().focus"></span>
679
+ }
680
+ `,
681
+ }]
682
+ }] });
683
+ /** A push button: a native click target over its picture that runs its action. */
684
+ class FormButtonControl extends FormWidgetBox {
685
+ drawsEdge = false;
686
+ buttonStyle = computed(() => ({
687
+ ...FORM_CONTROL_FILL,
688
+ padding: '0',
689
+ border: '0',
690
+ background: 'transparent',
691
+ cursor: 'inherit',
692
+ // The box is the event surface; a disabled button must not swallow the pointer.
693
+ 'pointer-events': this.item().disabled ? 'none' : 'auto',
694
+ }), ...(ngDevMode ? [{ debugName: "buttonStyle" }] : []));
695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormButtonControl, deps: null, target: i0.ɵɵFactoryTarget.Component });
696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.4", type: FormButtonControl, isStandalone: true, selector: "div[epdfFormButton]", host: { properties: { "style.cursor": "item().disabled ? 'default' : 'pointer'" } }, usesInheritance: true, ngImport: i0, template: `
697
+ <button
698
+ type="button"
699
+ [attr.aria-label]="item().label"
700
+ [disabled]="item().disabled"
701
+ [style]="buttonStyle()"
702
+ (click)="activate()"
703
+ ></button>
704
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
705
+ }
706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormButtonControl, decorators: [{
707
+ type: Component,
708
+ args: [{
709
+ selector: 'div[epdfFormButton]',
710
+ changeDetection: ChangeDetectionStrategy.OnPush,
711
+ host: { '[style.cursor]': "item().disabled ? 'default' : 'pointer'" },
712
+ template: `
713
+ <button
714
+ type="button"
715
+ [attr.aria-label]="item().label"
716
+ [disabled]="item().disabled"
717
+ [style]="buttonStyle()"
718
+ (click)="activate()"
719
+ ></button>
720
+ `,
721
+ }]
722
+ }] });
723
+ /**
724
+ * A signature field: with the signature plugin, an empty field is "sign here" (it becomes the
725
+ * target the next mark goes to), and a signed one asks your UI to show its details. Without
726
+ * the plugin, the field is only its picture.
727
+ */
728
+ class FormSignatureControl extends FormWidgetBox {
729
+ signature = new CapabilityBinding(injectKernelHost('<epdf-form-layer>'), () => SignatureToken, () => this.page.documentId);
730
+ signedByPlugin = this.signature.select((signature) => signature.getSignature(this.item().fieldRef)?.signed ?? null, null);
731
+ /**
732
+ * Whether the field is signed: the signature plugin knows best (it reads again after every
733
+ * new version), the field's own value is the fallback.
734
+ */
735
+ signed = computed(() => this.signedByPlugin() ?? this.item().signed, ...(ngDevMode ? [{ debugName: "signed" }] : []));
736
+ actionable = computed(() => this.signature.capability() !== null && (this.signed() || !this.item().disabled), ...(ngDevMode ? [{ debugName: "actionable" }] : []));
737
+ buttonStyle = {
738
+ ...FORM_CONTROL_FILL,
739
+ padding: '0',
740
+ border: '0',
741
+ background: 'transparent',
742
+ cursor: 'inherit',
743
+ };
744
+ open() {
745
+ const signature = this.signature.capability();
746
+ if (!signature)
747
+ return;
748
+ const field = this.item().fieldRef;
749
+ if (this.signed())
750
+ signature.requestInspection(field);
751
+ else
752
+ signature.setTarget(field);
753
+ }
754
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormSignatureControl, deps: null, target: i0.ɵɵFactoryTarget.Component });
755
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: FormSignatureControl, isStandalone: true, selector: "div[epdfFormSignature]", host: { properties: { "style.cursor": "actionable() ? 'pointer' : 'default'" } }, usesInheritance: true, ngImport: i0, template: `
756
+ @if (actionable()) {
757
+ <button
758
+ type="button"
759
+ [attr.aria-label]="item().label"
760
+ [attr.data-signed]="signed() ? '' : null"
761
+ [style]="buttonStyle"
762
+ (click)="open()"
763
+ ></button>
764
+ }
765
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
766
+ }
767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: FormSignatureControl, decorators: [{
768
+ type: Component,
769
+ args: [{
770
+ selector: 'div[epdfFormSignature]',
771
+ changeDetection: ChangeDetectionStrategy.OnPush,
772
+ host: { '[style.cursor]': "actionable() ? 'pointer' : 'default'" },
773
+ template: `
774
+ @if (actionable()) {
775
+ <button
776
+ type="button"
777
+ [attr.aria-label]="item().label"
778
+ [attr.data-signed]="signed() ? '' : null"
779
+ [style]="buttonStyle"
780
+ (click)="open()"
781
+ ></button>
782
+ }
783
+ `,
784
+ }]
785
+ }] });
786
+
787
+ /**
788
+ * `<epdf-field-pictures>`: the page's fields as they look now, under the form layer's controls.
789
+ * Each widget shows its picture, as the engine draws it (the form plugin's, every state loaded, so
790
+ * a check box shows its new state at once), or the painter's drawing while its look is pending or
791
+ * its picture is of an older version (`pictureOrPainting`): a value, a calculation, a restyle
792
+ * shows at once. Hidden widgets aren't drawn.
793
+ */
794
+ const NO_WIDGETS$1 = Object.freeze([]);
795
+ class EpdfFieldPictures {
796
+ page = injectPage('<epdf-form-layer>');
797
+ host = injectKernelHost('<epdf-form-layer>');
798
+ render = new CapabilityBinding(this.host, () => RenderToken, () => this.page.documentId);
799
+ form = new CapabilityBinding(this.host, () => FormToken$1, () => this.page.documentId);
800
+ /** The widgets the page shows: the same array while none of them changes. */
801
+ widgets = this.form.select((form) => form.listShownWidgets(this.page.ref), NO_WIDGETS$1);
802
+ /** Loaded again when one of the page's widgets is drawn again, and at appearance-scale crossings. */
803
+ epoch = this.form.select((form) => form.getAppearanceEpoch(this.page.ref), '');
804
+ scale = this.render.select((render) => render.getAppearanceScale(this.page.transform().renderScale), 0);
805
+ /** The pictures' URLs, by widget and state; `null` until the page's first ones arrive. */
806
+ urls = signal(null, ...(ngDevMode ? [{ debugName: "urls" }] : []));
807
+ /** The pictures shown stay valid until the next ones are shown. */
808
+ shown = createShownUrls();
809
+ looks = computed(() => {
810
+ const urls = this.urls();
811
+ const transform = this.page.transform();
812
+ const form = this.form.capability();
813
+ return this.widgets().flatMap((widget) => {
814
+ const key = annotationKey(widget.ref);
815
+ const look = pictureOrPainting({
816
+ pending: widget.pending,
817
+ picture: urls && shownAppearance(urls, key, widget.appearanceState),
818
+ version: widget.pictureVersion,
819
+ loaded: urls !== null,
820
+ paint: () => form?.getWidgetPainting(widget.ref) ?? null,
821
+ });
822
+ if (!look)
823
+ return [];
824
+ if (look.kind === 'picture') {
825
+ return [
826
+ {
827
+ kind: 'picture',
828
+ key,
829
+ url: look.picture.url,
830
+ frame: rectInPixels(look.picture.box, transform),
831
+ },
832
+ ];
833
+ }
834
+ const { x, y, width, height } = widget.rect;
835
+ return [
836
+ {
837
+ kind: 'painting',
838
+ key,
839
+ viewBox: `${x} ${y} ${width} ${height}`,
840
+ shapes: svgShapesOf(look.painting.scene, { miterLimit: MITER_LIMIT }),
841
+ frame: rectInPixels(widget.rect, transform),
842
+ },
843
+ ];
844
+ });
845
+ }, ...(ngDevMode ? [{ debugName: "looks" }] : []));
846
+ constructor() {
847
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
848
+ return;
849
+ inject(DestroyRef).onDestroy(() => this.shown.release());
850
+ effect((onCleanup) => {
851
+ const form = this.form.capability();
852
+ const scale = this.scale();
853
+ const ref = this.page.ref;
854
+ this.epoch();
855
+ if (!form || !scale)
856
+ return;
857
+ onCleanup(untracked(() => loadAppearanceUrls(this.shown, (signal) => form.renderAppearances(ref, scale, signal), annotationKey, (urls) => this.urls.set(urls))));
858
+ });
859
+ }
860
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFieldPictures, deps: [], target: i0.ɵɵFactoryTarget.Component });
861
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfFieldPictures, isStandalone: true, selector: "epdf-field-pictures", ngImport: i0, template: `
862
+ @for (look of looks(); track look.key) {
863
+ @if (look.kind === 'picture') {
864
+ <img
865
+ alt=""
866
+ draggable="false"
867
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none"
868
+ [attr.src]="look.url"
869
+ [style.left.px]="look.frame.left"
870
+ [style.top.px]="look.frame.top"
871
+ [style.width.px]="look.frame.width"
872
+ [style.height.px]="look.frame.height"
873
+ />
874
+ } @else {
875
+ <svg
876
+ style="position: absolute; overflow: visible; pointer-events: none"
877
+ [attr.viewBox]="look.viewBox"
878
+ [style.left.px]="look.frame.left"
879
+ [style.top.px]="look.frame.top"
880
+ [style.width.px]="look.frame.width"
881
+ [style.height.px]="look.frame.height"
882
+ >
883
+ <svg:g [epdfSvgShapes]="look.shapes"></svg:g>
884
+ </svg>
885
+ }
886
+ }
887
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfSvgShapes, selector: "g[epdfSvgShapes]", inputs: ["epdfSvgShapes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
888
+ }
889
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFieldPictures, decorators: [{
890
+ type: Component,
891
+ args: [{
892
+ selector: 'epdf-field-pictures',
893
+ imports: [EpdfSvgShapes],
894
+ changeDetection: ChangeDetectionStrategy.OnPush,
895
+ // A global `img { max-width: 100% }` reset would clamp a picture: the sizes are explicit.
896
+ template: `
897
+ @for (look of looks(); track look.key) {
898
+ @if (look.kind === 'picture') {
899
+ <img
900
+ alt=""
901
+ draggable="false"
902
+ style="position: absolute; max-width: none; max-height: none; pointer-events: none"
903
+ [attr.src]="look.url"
904
+ [style.left.px]="look.frame.left"
905
+ [style.top.px]="look.frame.top"
906
+ [style.width.px]="look.frame.width"
907
+ [style.height.px]="look.frame.height"
908
+ />
909
+ } @else {
910
+ <svg
911
+ style="position: absolute; overflow: visible; pointer-events: none"
912
+ [attr.viewBox]="look.viewBox"
913
+ [style.left.px]="look.frame.left"
914
+ [style.top.px]="look.frame.top"
915
+ [style.width.px]="look.frame.width"
916
+ [style.height.px]="look.frame.height"
917
+ >
918
+ <svg:g [epdfSvgShapes]="look.shapes"></svg:g>
919
+ </svg>
920
+ }
921
+ }
922
+ `,
923
+ }]
924
+ }], ctorParameters: () => [] });
925
+
926
+ /**
927
+ * `<epdf-form-layer>`: the form's fields on one page, painted as the engine draws them (their
928
+ * values, borders and fonts), with the controls people fill them in with. Put it above the
929
+ * page's picture (`<epdf-render-layer>`, and `<epdf-annotation-layer>` when the annotation
930
+ * plugin is registered); while it's there, the render layer leaves the fields out of the page's
931
+ * picture:
932
+ *
933
+ * <ng-template epdfPage>
934
+ * <epdf-render-layer />
935
+ * <epdf-form-layer />
936
+ * </ng-template>
937
+ *
938
+ * It works with or without the annotation plugin. The controls show while the active tool fills
939
+ * forms (the `pointer` and `pan` tools do), and stand down in design mode, where fields are
940
+ * boxes you select and move. Each field becomes a real HTML control (`controls.ts`), placed and
941
+ * styled like the field in the PDF, in the colors of the form settings, which the
942
+ * `--epdf-form-*` CSS variables override.
943
+ */
944
+ const NO_WIDGETS = Object.freeze([]);
945
+ class EpdfFormLayer {
946
+ page = injectPage('<epdf-form-layer>');
947
+ host = injectKernelHost('<epdf-form-layer>');
948
+ /** A plugin of the page's own document: every plugin below is read for it. */
949
+ bindTo(token) {
950
+ return new CapabilityBinding(this.host, () => token, () => this.page.documentId);
951
+ }
952
+ form = this.bindTo(FormToken$1);
953
+ /** Whether the active tool fills forms: the layer draws nothing in design mode. */
954
+ active = this.bindTo(InteractionToken).select((interaction) => interaction.activeToolEnables('form-fill'), false);
955
+ /** The page's widgets: the same array while nothing on the page changes. */
956
+ widgets = this.form.select((form) => form.listWidgets(this.page.ref), NO_WIDGETS);
957
+ /** The form settings, with or without a document: they belong to the plugin. */
958
+ settings = this.host.provides(FormToken)
959
+ ? this.host.settingsOf(FormToken).current
960
+ : signal(FORM_DEFAULTS).asReadonly();
961
+ /** The colors the viewer draws fields with: the form settings over the viewer's accent. */
962
+ colors = computed(() => {
963
+ const { focus, fields } = this.settings();
964
+ return formColorsOf({ focus, fields }, this.host.viewerSettings().accent);
965
+ }, ...(ngDevMode ? [{ debugName: "colors" }] : []));
966
+ constructor() {
967
+ // While it's here with the form plugin, the page's picture leaves the fields to it.
968
+ paintsPagePart(() => (this.form.capability() ? this.page.ref : null), 'formFields');
969
+ // Read the page's widgets when the layer comes to it: one read for the form.
970
+ effect(() => {
971
+ const form = this.form.capability();
972
+ if (form)
973
+ untracked(() => void form.ensureLoaded(this.page.ref));
974
+ });
975
+ }
976
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFormLayer, deps: [], target: i0.ɵɵFactoryTarget.Component });
977
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfFormLayer, isStandalone: true, selector: "epdf-form-layer", ngImport: i0, template: `
978
+ <div style="position: absolute; inset: 0; pointer-events: none">
979
+ <epdf-field-pictures />
980
+ @if (active()) {
981
+ <!-- One control per widget: a field with several boxes has one per box. -->
982
+ @for (item of widgets(); track item.key + ':' + item.annotObjectNumber) {
983
+ @switch (item.control) {
984
+ @case ('text') {
985
+ <div epdfFormText [item]="item" [colors]="colors()"></div>
986
+ }
987
+ @case ('toggle') {
988
+ <div epdfFormToggle [item]="item" [colors]="colors()"></div>
989
+ }
990
+ @case ('choice') {
991
+ @if (item.kind === 'list') {
992
+ <div epdfFormList [item]="item" [colors]="colors()"></div>
993
+ } @else {
994
+ <div epdfFormCombo [item]="item" [colors]="colors()"></div>
995
+ }
996
+ }
997
+ @case ('button') {
998
+ <div epdfFormButton [item]="item" [colors]="colors()"></div>
999
+ }
1000
+ @case ('signature') {
1001
+ <div epdfFormSignature [item]="item" [colors]="colors()"></div>
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ </div>
1007
+ `, isInline: true, dependencies: [{ kind: "component", type: EpdfFieldPictures, selector: "epdf-field-pictures" }, { kind: "component", type: FormTextControl, selector: "div[epdfFormText]" }, { kind: "component", type: FormToggleControl, selector: "div[epdfFormToggle]" }, { kind: "component", type: FormComboControl, selector: "div[epdfFormCombo]" }, { kind: "component", type: FormListControl, selector: "div[epdfFormList]" }, { kind: "component", type: FormButtonControl, selector: "div[epdfFormButton]" }, { kind: "component", type: FormSignatureControl, selector: "div[epdfFormSignature]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1008
+ }
1009
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfFormLayer, decorators: [{
1010
+ type: Component,
1011
+ args: [{
1012
+ selector: 'epdf-form-layer',
1013
+ imports: [
1014
+ EpdfFieldPictures,
1015
+ FormTextControl,
1016
+ FormToggleControl,
1017
+ FormComboControl,
1018
+ FormListControl,
1019
+ FormButtonControl,
1020
+ FormSignatureControl,
1021
+ ],
1022
+ changeDetection: ChangeDetectionStrategy.OnPush,
1023
+ template: `
1024
+ <div style="position: absolute; inset: 0; pointer-events: none">
1025
+ <epdf-field-pictures />
1026
+ @if (active()) {
1027
+ <!-- One control per widget: a field with several boxes has one per box. -->
1028
+ @for (item of widgets(); track item.key + ':' + item.annotObjectNumber) {
1029
+ @switch (item.control) {
1030
+ @case ('text') {
1031
+ <div epdfFormText [item]="item" [colors]="colors()"></div>
1032
+ }
1033
+ @case ('toggle') {
1034
+ <div epdfFormToggle [item]="item" [colors]="colors()"></div>
1035
+ }
1036
+ @case ('choice') {
1037
+ @if (item.kind === 'list') {
1038
+ <div epdfFormList [item]="item" [colors]="colors()"></div>
1039
+ } @else {
1040
+ <div epdfFormCombo [item]="item" [colors]="colors()"></div>
1041
+ }
1042
+ }
1043
+ @case ('button') {
1044
+ <div epdfFormButton [item]="item" [colors]="colors()"></div>
1045
+ }
1046
+ @case ('signature') {
1047
+ <div epdfFormSignature [item]="item" [colors]="colors()"></div>
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ </div>
1053
+ `,
1054
+ }]
1055
+ }], ctorParameters: () => [] });
1056
+
1057
+ /**
1058
+ * @embedpdf/angular/form: PDF forms people fill in, and that your code reads, fills and builds.
1059
+ *
1060
+ * withForm(config) the plugin, for provideEmbedPdf()
1061
+ * inject(EpdfForm) the fields, filling, form data and building, as signals, methods
1062
+ * and streams
1063
+ * form.valueOf(ref) one field's value, as a signal
1064
+ * form.controlOf(ref) one field as a Reactive Forms FormControl
1065
+ * <epdf-form-layer> the fields on each page, as real HTML controls
1066
+ */
1067
+ // The plugin's types and helpers (`toFieldRef`), so app code has one import for the feature.
1068
+
1069
+ /**
1070
+ * Generated bundle index. Do not edit.
1071
+ */
1072
+
1073
+ export { EpdfForm, EpdfFormLayer, withForm };
1074
+ //# sourceMappingURL=embedpdf-angular-form.mjs.map