@embedpdf/angular 3.0.0-next.14 → 3.0.0-next.15
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/README.md +309 -0
- package/dist/README.md +309 -0
- package/dist/actions/index.d.ts +34 -16
- package/dist/anchored/index.d.ts +67 -0
- package/dist/annotation/index.d.ts +599 -0
- package/dist/commands/index.d.ts +109 -0
- package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
- package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
- package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-annotation.mjs +1960 -0
- package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
- package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
- package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
- package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
- package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
- package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
- package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
- package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
- package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-page-edit.mjs +65 -0
- package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
- package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
- package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
- package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-runtime.mjs +1147 -390
- package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
- package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
- package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
- package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
- package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
- package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
- package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
- package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
- package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
- package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular.mjs +23 -3
- package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
- package/dist/form/index.d.ts +79 -0
- package/dist/history/index.d.ts +28 -0
- package/dist/i18n/index.d.ts +52 -0
- package/dist/index.d.ts +20 -0
- package/dist/interaction/index.d.ts +58 -0
- package/dist/link/index.d.ts +77 -0
- package/dist/measurement/index.d.ts +46 -0
- package/dist/metadata/index.d.ts +51 -0
- package/dist/page-edit/index.d.ts +22 -0
- package/dist/page-view/index.d.ts +94 -0
- package/dist/redaction/index.d.ts +38 -0
- package/dist/render/index.d.ts +45 -6
- package/dist/runtime/index.d.ts +579 -199
- package/dist/search/index.d.ts +71 -0
- package/dist/selection/index.d.ts +141 -0
- package/dist/shell/index.d.ts +88 -0
- package/dist/signature/index.d.ts +45 -0
- package/dist/stage/index.d.ts +242 -141
- package/dist/stamp/index.d.ts +58 -0
- package/dist/testing/index.d.ts +53 -0
- package/dist/toolbar/index.d.ts +257 -0
- package/dist/view-manager/index.d.ts +23 -0
- package/package.json +135 -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
|