@embedpdf/react 3.0.0-next.10 → 3.0.0-next.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.ts → AnnotationListSnapshot-C1U3XUxP-6kGwNoxy.d.cts} +223 -24
  2. package/dist/{AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.cts → AnnotationListSnapshot-C1U3XUxP-6kGwNoxy.d.ts} +223 -24
  3. package/dist/actions.cjs +42 -0
  4. package/dist/actions.cjs.map +1 -0
  5. package/dist/actions.d.cts +18 -0
  6. package/dist/actions.d.ts +18 -0
  7. package/dist/actions.js +34 -0
  8. package/dist/actions.js.map +1 -0
  9. package/dist/annotation-BCED_p9H.d.cts +160 -0
  10. package/dist/annotation-anchors-CetwM1g7.js +15 -0
  11. package/dist/annotation-anchors-CetwM1g7.js.map +1 -0
  12. package/dist/annotation-anchors-DuYRBJLl.cjs +26 -0
  13. package/dist/annotation-anchors-DuYRBJLl.cjs.map +1 -0
  14. package/dist/annotation-hooks-BB5ZhPpf.cjs +16 -0
  15. package/dist/annotation-hooks-BB5ZhPpf.cjs.map +1 -0
  16. package/dist/annotation-hooks-Df8EmzDO.js +11 -0
  17. package/dist/annotation-hooks-Df8EmzDO.js.map +1 -0
  18. package/dist/annotation-k38KDlIZ.d.ts +160 -0
  19. package/dist/annotation-menu.cjs +5 -5
  20. package/dist/annotation-menu.cjs.map +1 -1
  21. package/dist/annotation-menu.js +2 -2
  22. package/dist/annotation-menu.js.map +1 -1
  23. package/dist/annotation.cjs +5 -19
  24. package/dist/annotation.cjs.map +1 -1
  25. package/dist/annotation.d.cts +2 -159
  26. package/dist/annotation.d.ts +2 -159
  27. package/dist/annotation.js +2 -16
  28. package/dist/annotation.js.map +1 -1
  29. package/dist/form-BwrLFpKN.js +805 -0
  30. package/dist/form-BwrLFpKN.js.map +1 -0
  31. package/dist/form-D-ZSfGmB.cjs +836 -0
  32. package/dist/form-D-ZSfGmB.cjs.map +1 -0
  33. package/dist/form.cjs +8 -551
  34. package/dist/form.d.cts +4 -21
  35. package/dist/form.d.ts +4 -21
  36. package/dist/form.js +3 -544
  37. package/dist/index.cjs +15 -11
  38. package/dist/index.d.cts +7 -6
  39. package/dist/index.d.ts +7 -6
  40. package/dist/index.js +8 -5
  41. package/dist/link.cjs +48 -8
  42. package/dist/link.cjs.map +1 -1
  43. package/dist/link.d.cts +2 -2
  44. package/dist/link.d.ts +2 -2
  45. package/dist/link.js +49 -9
  46. package/dist/link.js.map +1 -1
  47. package/dist/{runtime-Cvi0wQ4d.d.cts → runtime-DE76hlnl.d.ts} +13 -5
  48. package/dist/{runtime-DXxr8T4L.d.ts → runtime-umHA_xHt.d.cts} +13 -5
  49. package/dist/runtime.cjs +19 -0
  50. package/dist/runtime.cjs.map +1 -1
  51. package/dist/runtime.d.cts +2 -2
  52. package/dist/runtime.d.ts +2 -2
  53. package/dist/runtime.js +19 -1
  54. package/dist/runtime.js.map +1 -1
  55. package/dist/scrollbar.cjs +3 -3
  56. package/dist/scrollbar.cjs.map +1 -1
  57. package/dist/scrollbar.d.cts +1 -1
  58. package/dist/scrollbar.d.ts +1 -1
  59. package/dist/scrollbar.js +1 -1
  60. package/dist/scrollbar.js.map +1 -1
  61. package/dist/search.d.cts +1 -1
  62. package/dist/search.d.ts +1 -1
  63. package/dist/selection.cjs +2 -2
  64. package/dist/selection.cjs.map +1 -1
  65. package/dist/selection.d.cts +1 -1
  66. package/dist/selection.d.ts +1 -1
  67. package/dist/selection.js +1 -1
  68. package/dist/selection.js.map +1 -1
  69. package/dist/stage.cjs +2 -2
  70. package/dist/stage.cjs.map +1 -1
  71. package/dist/stage.d.cts +1 -1
  72. package/dist/stage.d.ts +1 -1
  73. package/dist/stage.js +1 -1
  74. package/dist/stage.js.map +1 -1
  75. package/dist/toolbar.cjs +4 -4
  76. package/dist/toolbar.cjs.map +1 -1
  77. package/dist/toolbar.d.cts +1 -1
  78. package/dist/toolbar.d.ts +1 -1
  79. package/dist/toolbar.js +1 -1
  80. package/dist/toolbar.js.map +1 -1
  81. package/package.json +29 -23
  82. package/dist/form.cjs.map +0 -1
  83. package/dist/form.js.map +0 -1
@@ -0,0 +1,805 @@
1
+ import { shallowArray, useCapability, usePage, useSelector } from "./runtime.js";
2
+ import { t as useAnnotationSelected } from "./annotation-hooks-Df8EmzDO.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
6
+ import { FormToken } from "@embedpdf/plugin-form";
7
+ //#region src/form-focus-ring.tsx
8
+ /**
9
+ * Paint the form focus indicator above baked PDF appearances and transparent
10
+ * native controls. Putting an inset outline on the widget wrapper lets an
11
+ * opaque child cover it, which makes rectangular checkboxes and combo boxes
12
+ * appear unfocused even though they are active in the native Tab order.
13
+ */
14
+ function FormFocusRing({ visible }) {
15
+ if (!visible) return null;
16
+ return /* @__PURE__ */ jsx("span", {
17
+ "aria-hidden": "true",
18
+ "data-embedpdf-form-focus-ring": "",
19
+ style: {
20
+ position: "absolute",
21
+ inset: 0,
22
+ zIndex: 1,
23
+ boxSizing: "border-box",
24
+ outline: "2px solid rgba(66, 133, 244, 0.8)",
25
+ outlineOffset: -2,
26
+ pointerEvents: "none"
27
+ }
28
+ });
29
+ }
30
+ //#endregion
31
+ //#region src/form-listbox.tsx
32
+ /**
33
+ * A PDF list box needs one owner for pixels, hit-testing, and scrolling. A
34
+ * baked /AP picture with an invisible native select cannot provide that: the
35
+ * browser and PDF each keep an independent top-visible row. This visible
36
+ * native control keeps a local optimistic selection while the engine write is
37
+ * pending and synchronizes when engine truth changes, without remounting the
38
+ * DOM node or resetting its scroll position.
39
+ */
40
+ function NativeListBox({ ariaLabel, disabled, multi, options, selected, onSelect, onFocus, onBlur, style }) {
41
+ const [draft, setDraft] = useState(() => [...selected]);
42
+ const controlRef = useRef(null);
43
+ const selectedRef = useRef(selected);
44
+ selectedRef.current = selected;
45
+ const selectedSignature = JSON.stringify(selected);
46
+ useEffect(() => {
47
+ const control = controlRef.current;
48
+ if (!control) return;
49
+ const isolateWheel = (event) => event.stopPropagation();
50
+ control.addEventListener("wheel", isolateWheel);
51
+ return () => control.removeEventListener("wheel", isolateWheel);
52
+ }, []);
53
+ useEffect(() => {
54
+ setDraft([...selectedRef.current]);
55
+ }, [selectedSignature]);
56
+ const rollback = () => setDraft([...selectedRef.current]);
57
+ const choose = (values) => {
58
+ setDraft(values);
59
+ try {
60
+ const result = onSelect(values);
61
+ if (result) result.catch(rollback);
62
+ } catch {
63
+ rollback();
64
+ }
65
+ };
66
+ return /* @__PURE__ */ jsx("select", {
67
+ ref: controlRef,
68
+ "aria-label": ariaLabel,
69
+ multiple: multi,
70
+ size: Math.max(2, options.length),
71
+ disabled,
72
+ value: multi ? draft : draft[0] ?? "",
73
+ onFocus,
74
+ onBlur,
75
+ onChange: (event) => choose(Array.from(event.currentTarget.selectedOptions).map((option) => option.value)),
76
+ style,
77
+ children: options.map((option, index) => /* @__PURE__ */ jsx("option", {
78
+ value: option.value,
79
+ children: option.label
80
+ }, index))
81
+ });
82
+ }
83
+ //#endregion
84
+ //#region src/form.tsx
85
+ /** Content rect → a view-px box (the page wrapper's own coordinate space). */
86
+ function viewBox(r, page) {
87
+ const tl = page.transform.toPixels({
88
+ x: r.x,
89
+ y: r.y
90
+ });
91
+ const br = page.transform.toPixels({
92
+ x: r.x + r.width,
93
+ y: r.y + r.height
94
+ });
95
+ return {
96
+ left: tl.x,
97
+ top: tl.y,
98
+ width: br.x - tl.x,
99
+ height: br.y - tl.y
100
+ };
101
+ }
102
+ /**
103
+ * Keep the interaction hub out of gestures that begin inside a fill control.
104
+ * Native listener (not React's) so it runs during real DOM bubbling, before
105
+ * the Stage's own native listener on an ancestor — same trick as FreeText.
106
+ */
107
+ function useIsolated() {
108
+ const ref = useRef(null);
109
+ useEffect(() => {
110
+ const el = ref.current;
111
+ if (!el) return;
112
+ const stop = (e) => e.stopPropagation();
113
+ el.addEventListener("pointerdown", stop);
114
+ return () => el.removeEventListener("pointerdown", stop);
115
+ }, []);
116
+ return ref;
117
+ }
118
+ /**
119
+ * The widget DOM-event feed (Phase 2): wire one widget surface's pointer and
120
+ * focus events to `form.notifyWidgetEvent` — the `/AA` E/X/D/U/Fo/Bl trigger
121
+ * door. Handlers live on the ALWAYS-ACTIVE event surface, never the inner
122
+ * control: "may edit this field" and "may receive PDF action events" are
123
+ * different rights, and a read-only session (no `doc.forms.fill`) must still
124
+ * see hover tooltips (session Hide needs no write authority).
125
+ */
126
+ function useWidgetEvents(key, annotationRef) {
127
+ const form = useCapability(FormToken);
128
+ const refBox = useRef(annotationRef);
129
+ refBox.current = annotationRef;
130
+ return useMemo(() => {
131
+ const notify = (event) => {
132
+ const target = refBox.current;
133
+ if (target) form.notifyWidgetEvent(key, target, event);
134
+ };
135
+ return {
136
+ onPointerEnter: () => notify("cursorEnter"),
137
+ onPointerLeave: () => notify("cursorExit"),
138
+ onPointerDown: () => notify("mouseDown"),
139
+ onPointerUp: () => notify("mouseUp"),
140
+ onFocus: () => notify("focus"),
141
+ onBlur: () => notify("blur")
142
+ };
143
+ }, [form, key]);
144
+ }
145
+ /**
146
+ * Widget ACTIVATION: a click on ANY widget runs its `/A`. ISO puts the
147
+ * activate action on the annotation dictionary — any subtype, any field
148
+ * type — so activation is a WIDGET behavior, not a push-button behavior:
149
+ * real-world producers ship "buttons" as read-only text fields carrying an
150
+ * `/A` (the Test Lab's Reset/Next/Hide controls), and Acrobat runs them.
151
+ * A widget without an `/A` resolves inert — dispatching is the cheap,
152
+ * honest way to ask (the dispatcher owns `/A`-vs-`/AA U` precedence).
153
+ * Push buttons keep their own gated door: `disabled` still blocks
154
+ * activation there — unchanged shipped semantics.
155
+ */
156
+ function useWidgetActivation(key, annotationRef) {
157
+ const form = useCapability(FormToken);
158
+ const refBox = useRef(annotationRef);
159
+ refBox.current = annotationRef;
160
+ return useCallback(() => {
161
+ const target = refBox.current;
162
+ if (target) form.activateWidget(key, target);
163
+ }, [form, key]);
164
+ }
165
+ /** The baked /AP raster, blitted by its OWN box (exactly like BakedImage). */
166
+ function Picture({ page, appearance, apBox, frame, hidden }) {
167
+ if (!appearance) return null;
168
+ const b = viewBox(apBox ?? appearance.box, page);
169
+ return /* @__PURE__ */ jsx("img", {
170
+ src: appearance.url,
171
+ alt: "",
172
+ draggable: false,
173
+ style: {
174
+ position: "absolute",
175
+ left: b.left - (frame?.left ?? 0),
176
+ top: b.top - (frame?.top ?? 0),
177
+ width: b.width,
178
+ height: b.height,
179
+ maxWidth: "none",
180
+ pointerEvents: "none",
181
+ visibility: hidden ? "hidden" : "visible"
182
+ }
183
+ });
184
+ }
185
+ /**
186
+ * Dispatch one engaged widget to its fill control. The FIELD plane (value,
187
+ * behavior, disabled) comes from the form plugin's single-widget projection;
188
+ * the WIDGET plane (live box, /DA font, baked raster) rides in on the
189
+ * renderer props — no geometry is read from the form model here.
190
+ */
191
+ function FormWidget({ item, page, appearance }) {
192
+ const annot = item.ref?.kind === "objectNumber" ? item.ref.annotObjectNumber : 0;
193
+ const fill = useSelector(FormToken, (c) => annot > 0 ? c.fillItem(annot) : null);
194
+ if (!fill) return /* @__PURE__ */ jsx(Picture, {
195
+ page,
196
+ appearance,
197
+ apBox: item.apBox
198
+ });
199
+ switch (fill.control) {
200
+ case "text": return /* @__PURE__ */ jsx(TextWidget, {
201
+ fill,
202
+ item,
203
+ page,
204
+ appearance
205
+ });
206
+ case "toggle": return /* @__PURE__ */ jsx(ToggleWidget, {
207
+ fill,
208
+ item,
209
+ page,
210
+ appearance
211
+ });
212
+ case "choice": return /* @__PURE__ */ jsx(ChoiceWidget, {
213
+ fill,
214
+ item,
215
+ page,
216
+ appearance
217
+ });
218
+ case "button": return /* @__PURE__ */ jsx(ButtonWidget, {
219
+ fill,
220
+ item,
221
+ page,
222
+ appearance
223
+ });
224
+ }
225
+ }
226
+ function ButtonWidget({ fill, item, page, appearance }) {
227
+ const form = useCapability(FormToken);
228
+ const wrap = useIsolated();
229
+ const events = useWidgetEvents(fill.key, item.ref);
230
+ const b = viewBox(item.box, page);
231
+ return /* @__PURE__ */ jsx("div", {
232
+ ref: wrap,
233
+ ...events,
234
+ style: {
235
+ position: "absolute",
236
+ left: b.left,
237
+ top: b.top,
238
+ width: b.width,
239
+ height: b.height,
240
+ cursor: fill.disabled ? "default" : "pointer",
241
+ pointerEvents: "auto"
242
+ },
243
+ children: /* @__PURE__ */ jsx("button", {
244
+ type: "button",
245
+ "aria-label": fill.label,
246
+ disabled: fill.disabled,
247
+ onClick: () => {
248
+ if (item.ref) form.activateWidget(fill.key, item.ref);
249
+ },
250
+ style: {
251
+ position: "absolute",
252
+ inset: 0,
253
+ width: "100%",
254
+ height: "100%",
255
+ boxSizing: "border-box",
256
+ padding: 0,
257
+ border: 0,
258
+ background: "transparent",
259
+ cursor: "inherit",
260
+ pointerEvents: fill.disabled ? "none" : "auto"
261
+ },
262
+ children: /* @__PURE__ */ jsx(Picture, {
263
+ page,
264
+ appearance,
265
+ apBox: item.apBox,
266
+ frame: b
267
+ })
268
+ })
269
+ });
270
+ }
271
+ function TextWidget({ fill, item, page, appearance }) {
272
+ const form = useCapability(FormToken);
273
+ const wrap = useIsolated();
274
+ const events = useWidgetEvents(fill.key, item.ref);
275
+ const activate = useWidgetActivation(fill.key, item.ref);
276
+ const [focused, setFocused] = useState(false);
277
+ const [draft, setDraft] = useState(fill.value);
278
+ const cancelled = useRef(false);
279
+ useEffect(() => {
280
+ if (!focused) setDraft(fill.value);
281
+ }, [fill.value, focused]);
282
+ const b = viewBox(item.box, page);
283
+ const scale = item.box.width > 0 ? b.width / item.box.width : 1;
284
+ const fontSize = item.text && item.text.fontSize > 0 ? item.text.fontSize * scale : fill.multiline ? 12 * scale : Math.max(6, b.height * .72);
285
+ const editorStyle = {
286
+ position: "absolute",
287
+ inset: 0,
288
+ width: "100%",
289
+ height: "100%",
290
+ boxSizing: "border-box",
291
+ border: "none",
292
+ outline: focused ? "2px solid rgba(66, 133, 244, 0.8)" : "none",
293
+ outlineOffset: -2,
294
+ padding: "0 2px",
295
+ margin: 0,
296
+ background: item.style.interiorColor ?? "#fff",
297
+ color: item.text?.fontColor ?? "#000",
298
+ fontFamily: item.text?.fontFamily ?? "Helvetica, Arial, sans-serif",
299
+ fontSize,
300
+ textAlign: item.text?.textAlign ?? "left",
301
+ resize: "none",
302
+ cursor: "text",
303
+ opacity: focused ? 1 : 0,
304
+ ...fill.disabled ? { pointerEvents: "none" } : {}
305
+ };
306
+ const editorProps = {
307
+ value: draft,
308
+ maxLength: fill.maxLength ?? void 0,
309
+ "aria-label": fill.label,
310
+ disabled: fill.disabled,
311
+ onFocus: () => setFocused(true),
312
+ onChange: (e) => setDraft(e.target.value),
313
+ onBlur: () => {
314
+ setFocused(false);
315
+ if (cancelled.current) {
316
+ cancelled.current = false;
317
+ setDraft(fill.value);
318
+ return;
319
+ }
320
+ if (draft !== fill.value) form.setText(fill.key, draft);
321
+ },
322
+ style: editorStyle
323
+ };
324
+ const onKeys = (e) => {
325
+ if (e.key === "Escape") {
326
+ cancelled.current = true;
327
+ e.currentTarget.blur();
328
+ }
329
+ if (e.key === "Enter" && !fill.multiline) e.currentTarget.blur();
330
+ };
331
+ return /* @__PURE__ */ jsxs("div", {
332
+ ref: wrap,
333
+ ...events,
334
+ onClick: activate,
335
+ style: {
336
+ position: "absolute",
337
+ left: b.left,
338
+ top: b.top,
339
+ width: b.width,
340
+ height: b.height,
341
+ pointerEvents: "auto"
342
+ },
343
+ children: [/* @__PURE__ */ jsx(Picture, {
344
+ page,
345
+ appearance,
346
+ apBox: item.apBox,
347
+ frame: b,
348
+ hidden: focused
349
+ }), fill.multiline ? /* @__PURE__ */ jsx("textarea", {
350
+ ...editorProps,
351
+ onKeyDown: onKeys
352
+ }) : /* @__PURE__ */ jsx("input", {
353
+ ...editorProps,
354
+ type: fill.password ? "password" : "text",
355
+ onKeyDown: onKeys
356
+ })]
357
+ });
358
+ }
359
+ function ToggleWidget({ fill, item, page, appearance }) {
360
+ const form = useCapability(FormToken);
361
+ const wrap = useIsolated();
362
+ const events = useWidgetEvents(fill.key, item.ref);
363
+ const activate = useWidgetActivation(fill.key, item.ref);
364
+ const [focused, setFocused] = useState(false);
365
+ const b = viewBox(item.box, page);
366
+ const press = () => {
367
+ const flipped = fill.disabled ? void 0 : form.toggle(fill.key, fill.kind === "checkbox" && fill.checked ? null : fill.onState);
368
+ Promise.resolve(flipped).then(activate, activate);
369
+ };
370
+ return /* @__PURE__ */ jsxs("div", {
371
+ ref: wrap,
372
+ role: fill.kind === "checkbox" ? "checkbox" : "radio",
373
+ "aria-checked": fill.checked,
374
+ "aria-label": fill.label,
375
+ tabIndex: fill.disabled ? -1 : 0,
376
+ ...events,
377
+ onClick: press,
378
+ onFocus: (e) => {
379
+ setFocused(true);
380
+ events.onFocus?.(e);
381
+ },
382
+ onBlur: (e) => {
383
+ setFocused(false);
384
+ events.onBlur?.(e);
385
+ },
386
+ onKeyDown: (e) => {
387
+ if (e.key === " " || e.key === "Enter") {
388
+ e.preventDefault();
389
+ press();
390
+ }
391
+ },
392
+ style: {
393
+ position: "absolute",
394
+ left: b.left,
395
+ top: b.top,
396
+ width: b.width,
397
+ height: b.height,
398
+ cursor: fill.disabled ? "default" : "pointer",
399
+ pointerEvents: "auto",
400
+ outline: "none"
401
+ },
402
+ children: [/* @__PURE__ */ jsx(Picture, {
403
+ page,
404
+ appearance,
405
+ apBox: item.apBox,
406
+ frame: b
407
+ }), /* @__PURE__ */ jsx(FormFocusRing, { visible: focused })]
408
+ });
409
+ }
410
+ function ChoiceWidget(props) {
411
+ return props.fill.kind === "list" ? /* @__PURE__ */ jsx(ListBoxWidget, { ...props }) : /* @__PURE__ */ jsx(ComboBoxWidget, { ...props });
412
+ }
413
+ function ChoiceFrame({ fill, item, page, focused, innerRef, children }) {
414
+ const events = useWidgetEvents(fill.key, item.ref);
415
+ const activate = useWidgetActivation(fill.key, item.ref);
416
+ const b = viewBox(item.box, page);
417
+ return /* @__PURE__ */ jsxs("div", {
418
+ ref: innerRef,
419
+ ...events,
420
+ onClick: activate,
421
+ style: {
422
+ position: "absolute",
423
+ left: b.left,
424
+ top: b.top,
425
+ width: b.width,
426
+ height: b.height,
427
+ pointerEvents: "auto",
428
+ outline: "none"
429
+ },
430
+ children: [children, /* @__PURE__ */ jsx(FormFocusRing, { visible: focused })]
431
+ });
432
+ }
433
+ function ComboBoxWidget({ fill, item, page, appearance }) {
434
+ const form = useCapability(FormToken);
435
+ const wrap = useIsolated();
436
+ const [focused, setFocused] = useState(false);
437
+ const b = viewBox(item.box, page);
438
+ return /* @__PURE__ */ jsxs(ChoiceFrame, {
439
+ fill,
440
+ item,
441
+ page,
442
+ focused,
443
+ innerRef: wrap,
444
+ children: [/* @__PURE__ */ jsx(Picture, {
445
+ page,
446
+ appearance,
447
+ apBox: item.apBox,
448
+ frame: b
449
+ }), /* @__PURE__ */ jsx("select", {
450
+ defaultValue: fill.selected[0] ?? "",
451
+ "aria-label": fill.label,
452
+ disabled: fill.disabled,
453
+ onFocus: () => setFocused(true),
454
+ onBlur: () => setFocused(false),
455
+ onChange: (e) => {
456
+ const values = Array.from(e.currentTarget.selectedOptions).map((o) => o.value);
457
+ form.choose(fill.key, values);
458
+ },
459
+ style: {
460
+ position: "absolute",
461
+ inset: 0,
462
+ width: "100%",
463
+ height: "100%",
464
+ opacity: 0,
465
+ cursor: fill.disabled ? "default" : "pointer",
466
+ ...fill.disabled ? { pointerEvents: "none" } : {}
467
+ },
468
+ children: fill.options.map((o, i) => /* @__PURE__ */ jsx("option", {
469
+ value: o.value,
470
+ children: o.label
471
+ }, i))
472
+ }, fill.selected.join("\0"))]
473
+ });
474
+ }
475
+ function ListBoxWidget({ fill, item, page }) {
476
+ const form = useCapability(FormToken);
477
+ const wrap = useIsolated();
478
+ const [focused, setFocused] = useState(false);
479
+ const b = viewBox(item.box, page);
480
+ const scale = item.box.width > 0 ? b.width / item.box.width : 1;
481
+ const strokeWidth = item.style.strokeWidth * scale;
482
+ const borderStyle = item.style.border.kind === "dashed" ? "dashed" : "solid";
483
+ return /* @__PURE__ */ jsx(ChoiceFrame, {
484
+ fill,
485
+ item,
486
+ page,
487
+ focused,
488
+ innerRef: wrap,
489
+ children: /* @__PURE__ */ jsx(NativeListBox, {
490
+ ariaLabel: fill.label,
491
+ disabled: fill.disabled,
492
+ multi: fill.multi,
493
+ options: fill.options,
494
+ selected: fill.selected,
495
+ onFocus: () => setFocused(true),
496
+ onBlur: () => setFocused(false),
497
+ onSelect: (values) => form.choose(fill.key, values),
498
+ style: {
499
+ position: "absolute",
500
+ inset: 0,
501
+ width: "100%",
502
+ height: "100%",
503
+ boxSizing: "border-box",
504
+ margin: 0,
505
+ padding: 0,
506
+ borderWidth: strokeWidth,
507
+ borderStyle,
508
+ borderColor: item.style.color,
509
+ borderRadius: 0,
510
+ outline: "none",
511
+ background: item.style.interiorColor ?? "#fff",
512
+ color: item.text?.fontColor ?? "#000",
513
+ fontFamily: item.text?.fontFamily ?? "Helvetica, Arial, sans-serif",
514
+ fontSize: (item.text?.fontSize || 12) * scale,
515
+ textAlign: item.text?.textAlign ?? "left",
516
+ cursor: fill.disabled ? "default" : "pointer",
517
+ ...fill.disabled ? { pointerEvents: "none" } : {}
518
+ }
519
+ })
520
+ });
521
+ }
522
+ /**
523
+ * The form plugin's renderer entry, ready for
524
+ * `<AnnotationLayer renderers={[formWidgetRenderer]} />`. It answers for the
525
+ * PLUGIN-registered 'form-widgets' Behavior — the form plugin decides when
526
+ * widgets are fill controls vs. editable annotations, never the app.
527
+ */
528
+ const formWidgetRenderer = {
529
+ behavior: "form-widgets",
530
+ component: FormWidget
531
+ };
532
+ const controlBase = {
533
+ position: "absolute",
534
+ boxSizing: "border-box",
535
+ border: "1px solid rgba(56, 88, 233, 0.55)",
536
+ background: "rgba(255, 255, 255, 0.92)",
537
+ borderRadius: 2,
538
+ font: "inherit"
539
+ };
540
+ const fillBox = (item, page) => viewBox(item.box, page);
541
+ /** The standalone layer's event surface: positions ONE control's box and
542
+ * carries the /AA DOM-event feed — always pointer-active (see
543
+ * useWidgetEvents); the control inside fills it and self-gates edits.
544
+ * With `activate`, a click on the box runs the widget's /A (see
545
+ * useWidgetActivation — activation is a WIDGET behavior); push buttons
546
+ * keep their own gated inner door and do NOT set it. */
547
+ function FillEventBox({ item, page, cursor, activate, children }) {
548
+ const events = useWidgetEvents(item.key, {
549
+ kind: "objectNumber",
550
+ pageObjectNumber: page.pon,
551
+ annotObjectNumber: item.annotObjectNumber
552
+ });
553
+ const onActivate = useWidgetActivation(item.key, {
554
+ kind: "objectNumber",
555
+ pageObjectNumber: page.pon,
556
+ annotObjectNumber: item.annotObjectNumber
557
+ });
558
+ const css = fillBox(item, page);
559
+ return /* @__PURE__ */ jsx("div", {
560
+ ...events,
561
+ onClick: activate ? onActivate : void 0,
562
+ style: {
563
+ position: "absolute",
564
+ ...css,
565
+ pointerEvents: "auto",
566
+ ...cursor ? { cursor } : {}
567
+ },
568
+ children
569
+ });
570
+ }
571
+ const fillControl = {
572
+ position: "absolute",
573
+ inset: 0,
574
+ width: "100%",
575
+ height: "100%"
576
+ };
577
+ /** Text control: keystrokes stay LOCAL (the input is the draft store);
578
+ * the engine write happens on blur or Enter. */
579
+ function FillText({ item, page }) {
580
+ const form = useCapability(FormToken);
581
+ const [draft, setDraft] = useState(item.value);
582
+ useEffect(() => setDraft(item.value), [item.value]);
583
+ const css = fillBox(item, page);
584
+ const fontSize = Math.max(9, Math.min(css.height * .62, 24));
585
+ const commit = () => {
586
+ if (draft !== item.value) form.setText(item.key, draft).catch(() => setDraft(item.value));
587
+ };
588
+ const shared = {
589
+ ...controlBase,
590
+ ...fillControl,
591
+ fontSize,
592
+ padding: "0 3px",
593
+ ...item.comb ? { letterSpacing: css.width / Math.max(1, item.maxLength ?? 1) / 2 } : {}
594
+ };
595
+ const editorGate = item.disabled ? { pointerEvents: "none" } : {};
596
+ return /* @__PURE__ */ jsx(FillEventBox, {
597
+ item,
598
+ page,
599
+ activate: true,
600
+ children: item.multiline ? /* @__PURE__ */ jsx("textarea", {
601
+ "aria-label": item.label,
602
+ value: draft,
603
+ disabled: item.disabled,
604
+ onChange: (e) => setDraft(e.target.value),
605
+ onBlur: commit,
606
+ style: {
607
+ ...shared,
608
+ resize: "none",
609
+ ...editorGate
610
+ }
611
+ }) : /* @__PURE__ */ jsx("input", {
612
+ "aria-label": item.label,
613
+ type: item.password ? "password" : "text",
614
+ value: draft,
615
+ maxLength: item.maxLength ?? void 0,
616
+ disabled: item.disabled,
617
+ onChange: (e) => setDraft(e.target.value),
618
+ onBlur: commit,
619
+ onKeyDown: (e) => {
620
+ if (e.key === "Enter") e.target.blur();
621
+ },
622
+ style: {
623
+ ...shared,
624
+ ...editorGate
625
+ }
626
+ })
627
+ });
628
+ }
629
+ function FillToggle({ item, page }) {
630
+ const form = useCapability(FormToken);
631
+ const activate = useWidgetActivation(item.key, {
632
+ kind: "objectNumber",
633
+ pageObjectNumber: page.pon,
634
+ annotObjectNumber: item.annotObjectNumber
635
+ });
636
+ const css = fillBox(item, page);
637
+ const glyphSize = Math.min(css.width, css.height) * .72;
638
+ return /* @__PURE__ */ jsx(FillEventBox, {
639
+ item,
640
+ page,
641
+ cursor: item.disabled ? "default" : "pointer",
642
+ activate: true,
643
+ children: /* @__PURE__ */ jsx("button", {
644
+ role: item.kind,
645
+ "aria-checked": item.checked,
646
+ "aria-label": item.label,
647
+ disabled: item.disabled,
648
+ onClick: (e) => {
649
+ e.stopPropagation();
650
+ Promise.resolve(form.toggle(item.key, item.kind === "checkbox" && item.checked ? null : item.onState)).then(activate, activate);
651
+ },
652
+ style: {
653
+ ...controlBase,
654
+ ...fillControl,
655
+ cursor: "inherit",
656
+ display: "grid",
657
+ placeItems: "center",
658
+ padding: 0,
659
+ borderRadius: item.kind === "radio" ? "50%" : 2,
660
+ fontSize: glyphSize,
661
+ lineHeight: 1,
662
+ color: "#1f2a44",
663
+ ...item.disabled ? { pointerEvents: "none" } : {}
664
+ },
665
+ children: item.checked ? item.kind === "radio" ? "●" : "✓" : ""
666
+ })
667
+ });
668
+ }
669
+ function FillChoice({ item, page }) {
670
+ const form = useCapability(FormToken);
671
+ const css = fillBox(item, page);
672
+ const controlGate = item.disabled ? { pointerEvents: "none" } : {};
673
+ if (item.kind === "list") return /* @__PURE__ */ jsx(FillEventBox, {
674
+ item,
675
+ page,
676
+ activate: true,
677
+ children: /* @__PURE__ */ jsx(NativeListBox, {
678
+ ariaLabel: item.label,
679
+ disabled: item.disabled,
680
+ multi: item.multi,
681
+ options: item.options,
682
+ selected: item.selected,
683
+ onSelect: (values) => form.choose(item.key, values),
684
+ style: {
685
+ ...controlBase,
686
+ ...fillControl,
687
+ fontSize: Math.max(9, Math.min(css.height * .55, 18)),
688
+ ...controlGate
689
+ }
690
+ })
691
+ });
692
+ return /* @__PURE__ */ jsx(FillEventBox, {
693
+ item,
694
+ page,
695
+ activate: true,
696
+ children: /* @__PURE__ */ jsxs("select", {
697
+ "aria-label": item.label,
698
+ disabled: item.disabled,
699
+ value: item.selected[0] ?? "",
700
+ onChange: (e) => void form.choose(item.key, [e.target.value]),
701
+ style: {
702
+ ...controlBase,
703
+ ...fillControl,
704
+ fontSize: Math.max(9, Math.min(css.height * .55, 18)),
705
+ ...controlGate
706
+ },
707
+ children: [item.selected.length === 0 && /* @__PURE__ */ jsx("option", { value: "" }), item.options.map((o) => /* @__PURE__ */ jsx("option", {
708
+ value: o.value,
709
+ children: o.label
710
+ }, o.value))]
711
+ })
712
+ });
713
+ }
714
+ function FillButton({ item, page }) {
715
+ const form = useCapability(FormToken);
716
+ return /* @__PURE__ */ jsx(FillEventBox, {
717
+ item,
718
+ page,
719
+ cursor: item.disabled ? "default" : "pointer",
720
+ children: /* @__PURE__ */ jsx("button", {
721
+ type: "button",
722
+ "aria-label": item.label,
723
+ disabled: item.disabled,
724
+ onClick: () => void form.activateWidget(item.key, {
725
+ kind: "objectNumber",
726
+ pageObjectNumber: page.pon,
727
+ annotObjectNumber: item.annotObjectNumber
728
+ }),
729
+ style: {
730
+ ...controlBase,
731
+ ...fillControl,
732
+ padding: 0,
733
+ border: 0,
734
+ background: "transparent",
735
+ cursor: "inherit",
736
+ pointerEvents: item.disabled ? "none" : "auto"
737
+ }
738
+ })
739
+ });
740
+ }
741
+ /**
742
+ * Fill-mode form controls for one page, positioned from the form model's own
743
+ * widget geometry — the ANNOTATION-LESS path (a fill-only viewer whose page
744
+ * pixels come from an annotated RenderLayer raster). Active whenever the
745
+ * active tool carries the 'form-fill' tag (the built-in pointer/pan tools
746
+ * do); any other tool stands the controls down. Do not mount next to an
747
+ * AnnotationLayer wired with `formWidgetBehaviors`.
748
+ */
749
+ function FormLayer() {
750
+ const page = usePage();
751
+ const form = useCapability(FormToken);
752
+ const active = useSelector(InteractionToken, (c) => c.activeTool().enables.has("form-fill"));
753
+ useEffect(() => {
754
+ if (active) form.ensureGeom(page.pon);
755
+ }, [
756
+ active,
757
+ form,
758
+ page.pon
759
+ ]);
760
+ const items = useSelector(FormToken, (c) => c.fillItems(page.pon), shallowArray);
761
+ if (!active) return null;
762
+ return /* @__PURE__ */ jsx("div", {
763
+ style: {
764
+ position: "absolute",
765
+ inset: 0
766
+ },
767
+ children: items.map((item) => item.control === "text" ? /* @__PURE__ */ jsx(FillText, {
768
+ item,
769
+ page
770
+ }, `${item.key}:${item.annotObjectNumber}`) : item.control === "toggle" ? /* @__PURE__ */ jsx(FillToggle, {
771
+ item,
772
+ page
773
+ }, `${item.key}:${item.annotObjectNumber}`) : item.control === "choice" ? /* @__PURE__ */ jsx(FillChoice, {
774
+ item,
775
+ page
776
+ }, `${item.key}:${item.annotObjectNumber}`) : item.control === "button" ? /* @__PURE__ */ jsx(FillButton, {
777
+ item,
778
+ page
779
+ }, `${item.key}:${item.annotObjectNumber}`) : null)
780
+ });
781
+ }
782
+ /** The form capability — values, interchange, design-mode verbs. */
783
+ function useForm() {
784
+ return useCapability(FormToken);
785
+ }
786
+ /** The reconciled form snapshot (null until the first load lands),
787
+ * re-rendering on every form model change. */
788
+ function useFormSnapshot() {
789
+ return useSelector(FormToken, (c) => c.snapshot());
790
+ }
791
+ /**
792
+ * The FIELD behind the currently selected widget annotation (single
793
+ * selection), or null. The design-mode join: widget selection lives in the
794
+ * annotation plane, field properties live here.
795
+ */
796
+ function useFormField() {
797
+ const selected = useAnnotationSelected();
798
+ const widget = selected.length === 1 && selected[0].subtype === "widget" ? selected[0] : null;
799
+ const objnum = widget && widget.ref.kind === "objectNumber" ? widget.ref.annotObjectNumber : 0;
800
+ return useSelector(FormToken, (c) => objnum > 0 ? c.fieldForWidget(objnum) : null);
801
+ }
802
+ //#endregion
803
+ export { useFormSnapshot as a, useFormField as i, formWidgetRenderer as n, useForm as r, FormLayer as t };
804
+
805
+ //# sourceMappingURL=form-BwrLFpKN.js.map