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

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 (104) hide show
  1. package/dist/AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.cts +347 -0
  2. package/dist/AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.ts +347 -0
  3. package/dist/anchored.cjs +89 -0
  4. package/dist/anchored.cjs.map +1 -0
  5. package/dist/anchored.d.cts +45 -0
  6. package/dist/anchored.d.ts +45 -0
  7. package/dist/anchored.js +60 -0
  8. package/dist/anchored.js.map +1 -0
  9. package/dist/annotation-menu.cjs +33 -84
  10. package/dist/annotation-menu.cjs.map +1 -1
  11. package/dist/annotation-menu.d.cts +28 -2
  12. package/dist/annotation-menu.d.ts +28 -2
  13. package/dist/annotation-menu.js +27 -78
  14. package/dist/annotation-menu.js.map +1 -1
  15. package/dist/annotation.cjs +752 -16
  16. package/dist/annotation.cjs.map +1 -0
  17. package/dist/annotation.d.cts +159 -2
  18. package/dist/annotation.d.ts +159 -2
  19. package/dist/annotation.js +737 -3
  20. package/dist/annotation.js.map +1 -0
  21. package/dist/commands.js +1 -0
  22. package/dist/commands.js.map +1 -1
  23. package/dist/form.cjs +3 -3
  24. package/dist/form.cjs.map +1 -1
  25. package/dist/form.d.cts +2 -2
  26. package/dist/form.d.ts +2 -2
  27. package/dist/form.js +3 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/index.cjs +34 -5
  30. package/dist/index.d.cts +14 -8
  31. package/dist/index.d.ts +14 -8
  32. package/dist/index.js +8 -6
  33. package/dist/interaction.cjs +17 -4
  34. package/dist/interaction.cjs.map +1 -1
  35. package/dist/interaction.d.cts +2 -2
  36. package/dist/interaction.d.ts +2 -2
  37. package/dist/interaction.js +7 -5
  38. package/dist/interaction.js.map +1 -1
  39. package/dist/link.cjs +29 -6
  40. package/dist/link.cjs.map +1 -1
  41. package/dist/link.d.cts +12 -3
  42. package/dist/link.d.ts +12 -3
  43. package/dist/link.js +31 -8
  44. package/dist/link.js.map +1 -1
  45. package/dist/metadata.js +1 -0
  46. package/dist/metadata.js.map +1 -1
  47. package/dist/page-edit.js +1 -0
  48. package/dist/page-edit.js.map +1 -1
  49. package/dist/page-view.cjs +62 -38
  50. package/dist/page-view.cjs.map +1 -1
  51. package/dist/page-view.js +59 -37
  52. package/dist/page-view.js.map +1 -1
  53. package/dist/redaction.js +1 -0
  54. package/dist/redaction.js.map +1 -1
  55. package/dist/render.cjs +67 -50
  56. package/dist/render.cjs.map +1 -1
  57. package/dist/render.d.cts +6 -19
  58. package/dist/render.d.ts +6 -19
  59. package/dist/render.js +71 -52
  60. package/dist/render.js.map +1 -1
  61. package/dist/rolldown-runtime-BBjsoOtd.js +27 -0
  62. package/dist/{runtime-DV98q7I0.d.ts → runtime-Cvi0wQ4d.d.cts} +23 -10
  63. package/dist/{runtime-CMxIuF2O.d.cts → runtime-DXxr8T4L.d.ts} +23 -10
  64. package/dist/runtime.cjs +6 -5
  65. package/dist/runtime.cjs.map +1 -1
  66. package/dist/runtime.d.cts +1 -1
  67. package/dist/runtime.d.ts +1 -1
  68. package/dist/runtime.js +7 -5
  69. package/dist/runtime.js.map +1 -1
  70. package/dist/search.cjs +36 -19
  71. package/dist/search.cjs.map +1 -1
  72. package/dist/search.d.cts +1 -1
  73. package/dist/search.d.ts +1 -1
  74. package/dist/search.js +36 -19
  75. package/dist/search.js.map +1 -1
  76. package/dist/selection.cjs +217 -22
  77. package/dist/selection.cjs.map +1 -1
  78. package/dist/selection.d.cts +60 -2
  79. package/dist/selection.d.ts +60 -2
  80. package/dist/selection.js +214 -27
  81. package/dist/selection.js.map +1 -1
  82. package/dist/shell.js +1 -0
  83. package/dist/shell.js.map +1 -1
  84. package/dist/stage.cjs +47 -146
  85. package/dist/stage.cjs.map +1 -1
  86. package/dist/stage.d.cts +18 -5
  87. package/dist/stage.d.ts +18 -5
  88. package/dist/stage.js +50 -148
  89. package/dist/stage.js.map +1 -1
  90. package/dist/stamp.js +1 -0
  91. package/dist/stamp.js.map +1 -1
  92. package/dist/toolbar.js +1 -0
  93. package/dist/toolbar.js.map +1 -1
  94. package/dist/views.js +1 -0
  95. package/dist/views.js.map +1 -1
  96. package/package.json +27 -26
  97. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.cts +0 -2593
  98. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.ts +0 -2593
  99. package/dist/annotation-B6jwfTCh.d.cts +0 -173
  100. package/dist/annotation-Ct6dFtzJ.cjs +0 -913
  101. package/dist/annotation-Ct6dFtzJ.cjs.map +0 -1
  102. package/dist/annotation-DjGWJljY.js +0 -829
  103. package/dist/annotation-DjGWJljY.js.map +0 -1
  104. package/dist/annotation-nUMefyjw.d.ts +0 -173
@@ -1,829 +0,0 @@
1
- import { shallowArray, useCapability, useOptionalCapability, usePage, useSelector } from "./runtime.js";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import * as React from "react";
4
- import { useEffect, useLayoutEffect, useState } from "react";
5
- import { pickFile } from "@embedpdf/web";
6
- import { AnnotationToken, refKey } from "@embedpdf/plugin-annotation";
7
- import { createPortal } from "react-dom";
8
- import { AnnotationToken as AnnotationToken$1 } from "@embedpdf/plugin-annotation/internal";
9
- import { MITER_LIMIT, scene } from "@embedpdf/core-annotation";
10
- //#region src/internal/annotation-menu-position.ts
11
- /**
12
- * Place an upright menu around `box` (screen px). `knob` is the rotate handle's
13
- * screen point, when the selection has one: the menu extends ONLY the edge it
14
- * sits on, and ONLY when the knob protrudes past that edge — so it clears the
15
- * handle without ever shifting off-centre on the other axis. When the knob is on
16
- * another side (e.g. a 90deg shape, knob at mid-height for a `top` menu) the edge
17
- * is untouched and the menu stays centred on `box`.
18
- */
19
- function positionMenuAroundRect(box, placement, gap, knob) {
20
- const cx = box.x + box.width / 2;
21
- const cy = box.y + box.height / 2;
22
- switch (placement) {
23
- case "bottom": return {
24
- left: cx,
25
- top: Math.max(box.y + box.height, knob ? knob.y : -Infinity) + gap,
26
- transform: "translate(-50%, 0)"
27
- };
28
- case "left": return {
29
- left: Math.min(box.x, knob ? knob.x : Infinity) - gap,
30
- top: cy,
31
- transform: "translate(-100%, -50%)"
32
- };
33
- case "right": return {
34
- left: Math.max(box.x + box.width, knob ? knob.x : -Infinity) + gap,
35
- top: cy,
36
- transform: "translate(0, -50%)"
37
- };
38
- default: return {
39
- left: cx,
40
- top: Math.min(box.y, knob ? knob.y : Infinity) - gap,
41
- transform: "translate(-50%, -100%)"
42
- };
43
- }
44
- }
45
- //#endregion
46
- //#region src/annotation.tsx
47
- /** `#rrggbb` → `rgba(...)` — the marquee's translucent fill derives from the
48
- * accent, so one `setChrome({ accent })` restyles every piece of chrome. */
49
- const rgba = (hex, alpha) => {
50
- const m = /^#?([0-9a-f]{6})$/i.exec(hex);
51
- if (!m) return hex;
52
- const n = parseInt(m[1], 16);
53
- return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${alpha})`;
54
- };
55
- /** Content rect → a view-px box (the page wrapper's own coordinate space). */
56
- function boxOf(r, page) {
57
- const tl = page.transform.pageToContent({
58
- x: r.x,
59
- y: r.y
60
- });
61
- const br = page.transform.pageToContent({
62
- x: r.x + r.width,
63
- y: r.y + r.height
64
- });
65
- return {
66
- left: tl.x,
67
- top: tl.y,
68
- width: br.x - tl.x,
69
- height: br.y - tl.y
70
- };
71
- }
72
- /** Map a core `Paint` to SVG presentation attributes — the whole framework-facing
73
- * surface. Everything else about appearance is decided in the core's `scene`. */
74
- function paintAttrs(p) {
75
- return {
76
- fill: p.fill ?? "none",
77
- stroke: p.stroke ?? "none",
78
- strokeWidth: p.width,
79
- opacity: p.opacity,
80
- strokeLinejoin: p.join ?? "miter",
81
- strokeMiterlimit: MITER_LIMIT,
82
- strokeLinecap: p.cap,
83
- strokeDasharray: p.dash ? p.dash.join(" ") : void 0,
84
- ...p.blend ? { style: { mixBlendMode: p.blend } } : {}
85
- };
86
- }
87
- /**
88
- * The dumb painter. The pure core computed `item.box` and the painted `scene`; we
89
- * size the <svg> to the box with a content-space `viewBox` and map each SceneNode
90
- * to one element, applying its `paint`. No per-kind logic, no bounds math — so
91
- * shapes, cloudy borders and every text-markup type all render here, and a Vue /
92
- * Svelte painter is the same ~10-line loop.
93
- */
94
- function Shape({ item, page }) {
95
- if (item.box.width <= 0 || item.box.height <= 0) return null;
96
- const { left, top, width, height } = boxOf(item.box, page);
97
- const vb = `${item.box.x} ${item.box.y} ${item.box.width} ${item.box.height}`;
98
- const rot = item.geom.t === "rect" ? item.rot ?? 0 : 0;
99
- return /* @__PURE__ */ jsx("svg", {
100
- viewBox: vb,
101
- style: {
102
- position: "absolute",
103
- left,
104
- top,
105
- width,
106
- height,
107
- overflow: "visible",
108
- pointerEvents: "none",
109
- ...rot ? {
110
- transform: `rotate(${rot}deg)`,
111
- transformOrigin: "center"
112
- } : {}
113
- },
114
- children: sceneNodes(item)
115
- });
116
- }
117
- /** Map a core scene to SVG children. */
118
- function sceneNodes(item) {
119
- return scene(item).map((n, i) => {
120
- const a = paintAttrs(n.paint);
121
- if (n.kind === "rect") return /* @__PURE__ */ jsx("rect", {
122
- x: n.rect.x,
123
- y: n.rect.y,
124
- width: n.rect.width,
125
- height: n.rect.height,
126
- ...a
127
- }, i);
128
- if (n.kind === "ellipse") return /* @__PURE__ */ jsx("ellipse", {
129
- cx: n.rect.x + n.rect.width / 2,
130
- cy: n.rect.y + n.rect.height / 2,
131
- rx: n.rect.width / 2,
132
- ry: n.rect.height / 2,
133
- ...a
134
- }, i);
135
- if (n.kind === "line") return /* @__PURE__ */ jsx("line", {
136
- x1: n.a.x,
137
- y1: n.a.y,
138
- x2: n.b.x,
139
- y2: n.b.y,
140
- ...a
141
- }, i);
142
- if (n.kind === "path") return /* @__PURE__ */ jsx("path", {
143
- d: n.d,
144
- ...a
145
- }, i);
146
- if (n.kind === "text") return /* @__PURE__ */ jsx("text", {
147
- x: n.at.x,
148
- y: n.at.y,
149
- fontSize: n.fontSize,
150
- ...n.fontFamily ? { fontFamily: n.fontFamily } : {},
151
- ...a,
152
- children: n.text
153
- }, i);
154
- const pts = n.points.map((p) => `${p.x},${p.y}`).join(" ");
155
- return n.closed ? /* @__PURE__ */ jsx("polygon", {
156
- points: pts,
157
- ...a
158
- }, i) : /* @__PURE__ */ jsx("polyline", {
159
- points: pts,
160
- ...a
161
- }, i);
162
- });
163
- }
164
- function BakedImage({ box, url, page, blend, rot }) {
165
- const b = boxOf(box, page);
166
- return /* @__PURE__ */ jsx("img", {
167
- src: url,
168
- alt: "",
169
- draggable: false,
170
- style: {
171
- position: "absolute",
172
- left: b.left,
173
- top: b.top,
174
- width: b.width,
175
- height: b.height,
176
- maxWidth: "none",
177
- maxHeight: "none",
178
- pointerEvents: "none",
179
- mixBlendMode: blend,
180
- ...rot ? {
181
- transform: `rotate(${rot}deg)`,
182
- transformOrigin: "center"
183
- } : {}
184
- }
185
- });
186
- }
187
- /**
188
- * The armed stamp's IMAGE footprint ghost: a translucent render of the payload
189
- * drawn in the EXACT box a click would place it (the plugin computes it with
190
- * the same fit + clamp as placement). Vector footprint ghosts never reach this
191
- * component — they ride `pageItems` like every draft preview. The preview
192
- * bytes live in the capability closure; this layer owns only the object-URL
193
- * lifetime, keyed on the arm epoch — a new arm swaps the image, a disarm (or
194
- * tool change) drops it.
195
- */
196
- function ToolGhostImage({ page }) {
197
- const anno = useCapability(AnnotationToken$1);
198
- const ghost = useSelector(AnnotationToken$1, (c) => c.toolGhost(page.pon));
199
- const epoch = useSelector(AnnotationToken$1, (c) => c.stampArmEpoch());
200
- const [url, setUrl] = useState(null);
201
- useEffect(() => {
202
- const preview = anno.armedStampPreview();
203
- if (!preview) {
204
- setUrl(null);
205
- return;
206
- }
207
- const body = new ArrayBuffer(preview.bytes.byteLength);
208
- new Uint8Array(body).set(preview.bytes);
209
- const blob = new Blob([body], preview.mimeType ? { type: preview.mimeType } : {});
210
- const obj = URL.createObjectURL(blob);
211
- setUrl(obj);
212
- return () => {
213
- URL.revokeObjectURL(obj);
214
- setUrl(null);
215
- };
216
- }, [anno, epoch]);
217
- if (!ghost || ghost.kind !== "image" || !url) return null;
218
- const b = boxOf(ghost.box, page);
219
- return /* @__PURE__ */ jsx("img", {
220
- src: url,
221
- alt: "",
222
- draggable: false,
223
- style: {
224
- position: "absolute",
225
- left: b.left,
226
- top: b.top,
227
- width: b.width,
228
- height: b.height,
229
- maxWidth: "none",
230
- maxHeight: "none",
231
- pointerEvents: "none",
232
- opacity: .5,
233
- ...ghost.rot ? {
234
- transform: `rotate(${ghost.rot}deg)`,
235
- transformOrigin: "center"
236
- } : {}
237
- }
238
- });
239
- }
240
- function Chrome({ page }) {
241
- const scale = page.transform.viewScale;
242
- const rotation = page.transform.rotation;
243
- const zoom = page.transform.zoom;
244
- const nodes = useSelector(AnnotationToken$1, (c) => c.chrome(page.pon, scale, rotation, zoom), shallowArray);
245
- const cs = useSelector(AnnotationToken$1, (c) => c.chromeSettings());
246
- const outlineStroke = cs.outline.color ?? cs.accent;
247
- const handleStroke = cs.handles.stroke ?? cs.accent;
248
- const knobStroke = cs.knob.stroke ?? cs.accent;
249
- const outlineDash = cs.outline.style === "dashed" ? "4 3" : void 0;
250
- const chip = nodes.find((n) => n.kind === "angle-chip");
251
- const chipAt = chip ? page.transform.pageToContent(chip.at) : null;
252
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("svg", {
253
- style: {
254
- position: "absolute",
255
- inset: 0,
256
- overflow: "visible",
257
- pointerEvents: "none"
258
- },
259
- children: nodes.map((n, i) => {
260
- if (n.kind === "angle-chip") return null;
261
- if (n.kind === "handle") {
262
- const p = page.transform.pageToContent(n.at);
263
- const hs = cs.handles.size;
264
- return /* @__PURE__ */ jsx("rect", {
265
- x: p.x - hs / 2,
266
- y: p.y - hs / 2,
267
- width: hs,
268
- height: hs,
269
- fill: cs.handles.fill,
270
- stroke: handleStroke,
271
- strokeWidth: 1.5,
272
- ...n.rot ? { transform: `rotate(${n.rot} ${p.x} ${p.y})` } : {}
273
- }, i);
274
- }
275
- if (n.kind === "guide") {
276
- const a = page.transform.pageToContent(n.axis === "x" ? {
277
- x: n.at,
278
- y: n.lo
279
- } : {
280
- x: n.lo,
281
- y: n.at
282
- });
283
- const b = page.transform.pageToContent(n.axis === "x" ? {
284
- x: n.at,
285
- y: n.hi
286
- } : {
287
- x: n.hi,
288
- y: n.at
289
- });
290
- return /* @__PURE__ */ jsx("line", {
291
- x1: a.x,
292
- y1: a.y,
293
- x2: b.x,
294
- y2: b.y,
295
- stroke: "#e91e63",
296
- strokeWidth: 1.5,
297
- shapeRendering: "crispEdges"
298
- }, i);
299
- }
300
- if (n.kind === "obb") return /* @__PURE__ */ jsx("polygon", {
301
- points: n.corners.map((c) => {
302
- const p = page.transform.pageToContent(c);
303
- return `${p.x},${p.y}`;
304
- }).join(" "),
305
- fill: "none",
306
- stroke: outlineStroke,
307
- strokeWidth: cs.outline.width,
308
- strokeDasharray: outlineDash
309
- }, i);
310
- if (n.kind === "rotate-guides") {
311
- const guideDash = cs.guides.style === "dashed" ? "4 3" : void 0;
312
- return /* @__PURE__ */ jsx("g", { children: n.lines.map((l, j) => {
313
- const a = page.transform.pageToContent(l.a);
314
- const b = page.transform.pageToContent(l.b);
315
- const axis = l.role === "axis";
316
- return /* @__PURE__ */ jsx("line", {
317
- x1: a.x,
318
- y1: a.y,
319
- x2: b.x,
320
- y2: b.y,
321
- stroke: axis ? cs.guides.axisColor ?? cs.accent : cs.guides.indicatorColor ?? cs.accent,
322
- opacity: axis ? cs.guides.axisOpacity : cs.guides.indicatorOpacity,
323
- strokeWidth: cs.guides.width,
324
- strokeDasharray: guideDash
325
- }, j);
326
- }) }, i);
327
- }
328
- if (n.kind === "rotate-knob") {
329
- const at = page.transform.pageToContent(n.at);
330
- const from = page.transform.pageToContent(n.from);
331
- return /* @__PURE__ */ jsxs("g", { children: [cs.knob.stalk && /* @__PURE__ */ jsx("line", {
332
- x1: from.x,
333
- y1: from.y,
334
- x2: at.x,
335
- y2: at.y,
336
- stroke: knobStroke,
337
- strokeWidth: 1
338
- }), /* @__PURE__ */ jsx("circle", {
339
- cx: at.x,
340
- cy: at.y,
341
- r: cs.knob.size / 2,
342
- fill: cs.knob.fill,
343
- stroke: knobStroke,
344
- strokeWidth: 1.5
345
- })] }, i);
346
- }
347
- const b = boxOf(n.rect, page);
348
- if (n.kind === "marquee") return /* @__PURE__ */ jsx("rect", {
349
- x: b.left,
350
- y: b.top,
351
- width: b.width,
352
- height: b.height,
353
- fill: rgba(cs.accent, .08),
354
- stroke: cs.accent,
355
- strokeWidth: 1,
356
- strokeDasharray: "4 3"
357
- }, i);
358
- return /* @__PURE__ */ jsx("rect", {
359
- x: b.left,
360
- y: b.top,
361
- width: b.width,
362
- height: b.height,
363
- fill: "none",
364
- stroke: outlineStroke,
365
- strokeWidth: cs.outline.width,
366
- strokeDasharray: outlineDash
367
- }, i);
368
- })
369
- }), chip && chipAt && /* @__PURE__ */ jsxs("div", {
370
- style: {
371
- position: "absolute",
372
- left: chipAt.x + 16,
373
- top: chipAt.y - 28,
374
- background: "rgba(0,0,0,0.8)",
375
- color: "#fff",
376
- padding: "2px 6px",
377
- borderRadius: 4,
378
- fontSize: 12,
379
- fontFamily: "monospace",
380
- pointerEvents: "none",
381
- whiteSpace: "nowrap",
382
- zIndex: 1
383
- },
384
- children: [chip.angle, "°"]
385
- })] });
386
- }
387
- /**
388
- * A free-text annotation: the SAME styled element for viewing and editing —
389
- * `contentEditable` just toggles, so the text never jumps. The plugin handed us a
390
- * ready-to-spread style (`item.css`); the browser owns layout, caret, selection,
391
- * IME and clipboard; the plugin owns the text truth + the debounced engine write.
392
- * This component is the ENTIRE per-framework surface for text editing.
393
- */
394
- function FreeText({ item, page }) {
395
- const anno = useCapability(AnnotationToken$1);
396
- const ref = React.useRef(null);
397
- const box = boxOf(item.box, page);
398
- const scale = item.box.width > 0 ? box.width / item.box.width : 1;
399
- useEffect(() => {
400
- const el = ref.current;
401
- if (el && document.activeElement !== el && el.innerText !== item.contents) el.innerText = item.contents;
402
- }, [item.contents, item.editing]);
403
- useEffect(() => {
404
- if (item.editing) ref.current?.focus();
405
- }, [item.editing]);
406
- useEffect(() => {
407
- const el = ref.current;
408
- if (!el) return;
409
- const stop = (e) => e.stopPropagation();
410
- el.addEventListener("pointerdown", stop);
411
- return () => el.removeEventListener("pointerdown", stop);
412
- }, []);
413
- return /* @__PURE__ */ jsx("div", {
414
- ref,
415
- contentEditable: item.editing,
416
- suppressContentEditableWarning: true,
417
- onInput: () => item.ref && anno.setContents(item.ref, ref.current.innerText),
418
- onBlur: (e) => {
419
- if (e.relatedTarget == null && ref.current?.isConnected && anno.currentEditing() === item.id) ref.current.focus();
420
- },
421
- onPaste: (e) => {
422
- e.preventDefault();
423
- document.execCommand("insertText", false, e.clipboardData.getData("text/plain"));
424
- },
425
- style: {
426
- position: "absolute",
427
- left: box.left,
428
- top: box.top,
429
- width: box.width,
430
- height: box.height,
431
- fontFamily: item.css.fontFamily,
432
- fontSize: item.css.fontSize * scale,
433
- lineHeight: `${item.css.lineHeight * scale}px`,
434
- color: item.css.color,
435
- textAlign: item.css.align,
436
- padding: item.css.padding * scale,
437
- boxSizing: "border-box",
438
- background: item.css.background ?? "transparent",
439
- whiteSpace: "pre-wrap",
440
- overflowWrap: "break-word",
441
- overflowY: item.editing ? "auto" : "hidden",
442
- overflowX: "hidden",
443
- outline: item.editing ? "1px solid #3858e9" : "none",
444
- cursor: item.editing ? "text" : "default",
445
- ...item.rot ? {
446
- transform: `rotate(${item.rot}deg)`,
447
- transformOrigin: "center"
448
- } : {},
449
- pointerEvents: item.editing ? "auto" : "none"
450
- }
451
- });
452
- }
453
- /**
454
- * Auto-registered behaviors for `interactive` renderer entries — refcounted
455
- * per (capability, entry) because the layer mounts once PER PAGE: the first
456
- * page registers, the last unregisters. Entry identity is the key, hence the
457
- * "define entries outside render" rule on {@link AnnotationRenderer}.
458
- */
459
- const autoBehaviors = /* @__PURE__ */ new WeakMap();
460
- let autoBehaviorSeq = 0;
461
- function useAutoBehaviors(anno, renderers) {
462
- useEffect(() => {
463
- if (!renderers) return;
464
- const released = [];
465
- for (const r of renderers) {
466
- if (!("for" in r) || !r.interactive) continue;
467
- let perCap = autoBehaviors.get(anno);
468
- if (!perCap) autoBehaviors.set(anno, perCap = /* @__PURE__ */ new Map());
469
- let rec = perCap.get(r);
470
- if (!rec) {
471
- const id = r.id ?? `renderer:${++autoBehaviorSeq}`;
472
- const engaged = typeof r.interactive === "function" ? r.interactive : () => true;
473
- rec = {
474
- id,
475
- count: 0,
476
- unregister: anno.registerBehavior({
477
- id,
478
- matches: r.for,
479
- engaged
480
- })
481
- };
482
- perCap.set(r, rec);
483
- }
484
- rec.count++;
485
- const owned = rec;
486
- released.push(() => {
487
- owned.count--;
488
- if (owned.count === 0) {
489
- owned.unregister();
490
- autoBehaviors.get(anno)?.delete(r);
491
- }
492
- });
493
- }
494
- return () => released.forEach((f) => f());
495
- }, [anno, renderers]);
496
- }
497
- /** The behavior id a renderer entry answers for (plugin-owned or auto-registered). */
498
- function rendererBehaviorId(anno, r) {
499
- if ("behavior" in r) return r.behavior;
500
- return autoBehaviors.get(anno)?.get(r)?.id ?? null;
501
- }
502
- /** React 18 spells the `inert` attribute as a string spread; it hard-disables
503
- * pointer AND focus for the whole subtree — the mechanical guarantee that a
504
- * non-interactive renderer entry (a skin) can never steal input. */
505
- const INERT = { inert: "" };
506
- function AnnotationLayer({ renderers } = {}) {
507
- const page = usePage();
508
- const anno = useCapability(AnnotationToken$1);
509
- const viewZoom = page.transform.zoom;
510
- const viewRotation = page.transform.rotation;
511
- const items = useSelector(AnnotationToken$1, (c) => c.pageItems(page.pon, {
512
- zoom: viewZoom,
513
- rotation: viewRotation
514
- }), shallowArray);
515
- const texts = useSelector(AnnotationToken$1, (c) => c.textItems(page.pon, {
516
- zoom: viewZoom,
517
- rotation: viewRotation
518
- }), shallowArray);
519
- const [urls, setUrls] = useState({});
520
- useAutoBehaviors(anno, renderers);
521
- useEffect(() => {
522
- anno.ensurePage(page.pon);
523
- }, [anno, page.pon]);
524
- const bakedKey = useSelector(AnnotationToken$1, (c) => c.appearanceEpoch(page.pon));
525
- const bakeScale = useSelector(AnnotationToken$1, (c) => c.bakeScale(page.transform.renderScale));
526
- useEffect(() => {
527
- const controller = new AbortController();
528
- const revokers = [];
529
- (async () => {
530
- try {
531
- const imgs = await anno.appearances(page.pon, bakeScale, controller.signal);
532
- const map = {};
533
- for (const ap of imgs) {
534
- const box = anno.toContentBox(page.pon, ap.rect);
535
- if (!box) continue;
536
- const obj = await ap.image.objectUrl(controller.signal);
537
- if (controller.signal.aborted) {
538
- obj.revoke();
539
- return;
540
- }
541
- revokers.push(obj.revoke);
542
- map[refKey(ap.ref)] = {
543
- url: obj.url,
544
- box
545
- };
546
- }
547
- if (!controller.signal.aborted) setUrls(map);
548
- } catch {}
549
- })();
550
- return () => {
551
- controller.abort();
552
- revokers.forEach((r) => r());
553
- };
554
- }, [
555
- anno,
556
- page.pon,
557
- bakeScale,
558
- bakedKey
559
- ]);
560
- return /* @__PURE__ */ jsxs("div", {
561
- style: {
562
- position: "absolute",
563
- inset: 0,
564
- pointerEvents: "none"
565
- },
566
- children: [
567
- items.map((item) => {
568
- const baked = urls[item.id];
569
- const native = item.source === "baked" ? baked ? /* @__PURE__ */ jsx(BakedImage, {
570
- box: item.apBox ?? baked.box,
571
- url: baked.url,
572
- page,
573
- blend: item.blend,
574
- rot: item.apRot
575
- }) : null : /* @__PURE__ */ jsx(Shape, {
576
- item,
577
- page
578
- });
579
- const behavior = anno.behaviorFor({
580
- subtype: item.subtype,
581
- ref: item.ref
582
- });
583
- let out;
584
- if (behavior) {
585
- const entry = renderers?.find((r) => rendererBehaviorId(anno, r) === behavior.id);
586
- if (entry) {
587
- const Owner = entry.component;
588
- out = /* @__PURE__ */ jsx(Owner, {
589
- item,
590
- page,
591
- appearance: baked ?? null,
592
- native,
593
- interactive: true
594
- });
595
- } else out = null;
596
- } else {
597
- const entry = renderers?.find((r) => "for" in r && r.for({
598
- subtype: item.subtype,
599
- ref: item.ref
600
- }));
601
- if (entry) {
602
- const Skin = entry.component;
603
- out = /* @__PURE__ */ jsx("div", {
604
- ...INERT,
605
- style: { pointerEvents: "none" },
606
- children: /* @__PURE__ */ jsx(Skin, {
607
- item,
608
- page,
609
- appearance: baked ?? null,
610
- native,
611
- interactive: false
612
- })
613
- });
614
- } else out = native;
615
- }
616
- return /* @__PURE__ */ jsx(React.Fragment, { children: out }, item.id);
617
- }),
618
- texts.map((t) => /* @__PURE__ */ jsx(FreeText, {
619
- item: t,
620
- page
621
- }, t.id)),
622
- /* @__PURE__ */ jsx(ToolGhostImage, { page }),
623
- /* @__PURE__ */ jsx(Chrome, { page })
624
- ]
625
- });
626
- }
627
- /**
628
- * The default file-picker provider: the built-in file dialog (from
629
- * `@embedpdf/web`), honouring the tool's `accept` filter. This is the ADAPTER
630
- * fulfilling the plugin's DOM-free port — the dialog lives here, in the
631
- * framework layer, never in the plugin. A picked `File` carries its own name
632
- * and mime, so it goes straight through as the engine's file source.
633
- */
634
- const filePickerProvider = async (req) => {
635
- const file = await pickFile({ accept: req.accept ?? "*/*" });
636
- return file ? { data: file } : null;
637
- };
638
- /**
639
- * Install the file-picker provider for the active document — the ONE port
640
- * behind every click-then-pick tool (a stamp `'prompt'` source, the file-
641
- * attachment tool). Call ONCE at a document-scoped spot (not inside
642
- * `<AnnotationLayer>`, which is per page). Defaults to
643
- * {@link filePickerProvider}, so a bare `useFilePickerProvider()` makes all of
644
- * them work out of the box; pass a custom provider (asset library, cloud
645
- * drive — switch on `req.subtype` / `req.toolId`) or `null` to make
646
- * click-then-pick tools inert. Cleared on unmount.
647
- */
648
- function useFilePickerProvider(provider = filePickerProvider) {
649
- const anno = useOptionalCapability(AnnotationToken);
650
- useEffect(() => {
651
- if (!anno) return;
652
- anno.setFilePickerProvider(provider);
653
- return () => anno.setFilePickerProvider(null);
654
- }, [anno, provider]);
655
- }
656
- function useAnnotation() {
657
- return useCapability(AnnotationToken);
658
- }
659
- function useAnnotationSelection() {
660
- return useSelector(AnnotationToken, (c) => c.selection(), shallowArray);
661
- }
662
- /** The selected annotations as engine DTOs — for selection-aware toolbars/sidebars. */
663
- function useAnnotationSelected() {
664
- return useSelector(AnnotationToken, (c) => c.getSelected(), shallowArray);
665
- }
666
- /** Structural equality for a resolved props bag — keeps the subscription from
667
- * re-rendering on unrelated dispatches, since `currentDefaults` returns a fresh
668
- * object each call. Small flat objects; JSON compare is exact and cheap here. */
669
- const sameProps = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
670
- /**
671
- * A tool's RESOLVED defaults (base + per-tool override) as a full flat props
672
- * bag, subscribed so a `setDefaults` re-renders the consumer. Use this — not the
673
- * imperative `useAnnotation().currentDefaults(id)` — to drive default-editing
674
- * controls, so they reflect changes live. Pair with `propsForTool(id)` for the
675
- * specs to render.
676
- */
677
- function useAnnotationDefaults(toolId) {
678
- return useSelector(AnnotationToken, (c) => c.currentDefaults(toolId), sameProps);
679
- }
680
- /**
681
- * The selection's editable properties — ordered specs shared by every selected
682
- * kind, current values, and which keys are mixed. THE hook a property sidebar
683
- * renders from; write back with `useAnnotation().updateSelection({ [key]: v })`.
684
- * Reference-stable between model changes (the capability memoizes by model
685
- * identity), so the default equality is enough.
686
- */
687
- function useSelectionProps() {
688
- return useSelector(AnnotationToken, (c) => c.getSelectionProps());
689
- }
690
- /**
691
- * The selection's `/F` annotation flags — per-flag `true`/`false`, `null` where
692
- * the selected annotations disagree (render an indeterminate control), `null`
693
- * overall when nothing is selected. Write back with
694
- * `useAnnotation().updateSelectionFlags({ locked: true })`. Reference-stable
695
- * between model changes, like {@link useSelectionProps}.
696
- */
697
- function useSelectionFlags() {
698
- return useSelector(AnnotationToken, (c) => c.getSelectionFlags());
699
- }
700
- /** Structural equality for the selection anchor — keeps the menu from re-rendering
701
- * on unrelated dispatches (the capability returns a fresh object each call). */
702
- function sameAnchor(a, b) {
703
- if (a === b) return true;
704
- if (!a || !b) return false;
705
- return a.pon === b.pon && a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height && a.knob?.x === b.knob?.x && a.knob?.y === b.knob?.y;
706
- }
707
- function sameCreationDraftAnchor(a, b) {
708
- if (a === b) return true;
709
- if (!a || !b) return false;
710
- return a.kind === b.kind && a.subtype === b.subtype && a.pon === b.pon && a.pointCount === b.pointCount && a.minPoints === b.minPoints && a.canFinish === b.canFinish && a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height;
711
- }
712
- /**
713
- * The Stage-FREE selection menu, for `<PageView>` (no camera). It transforms a
714
- * selected content rect to client px via `page.toClientRect`, then renders an
715
- * UPRIGHT menu through a portal to
716
- * `document.body` (so it never rotates with the page nor clips at a container
717
- * edge). Re-measures on scroll/resize. For `<Stage>`, prefer `<AnnotationMenu>`
718
- * from `@embedpdf/react/annotation-menu` (camera-driven, more responsive).
719
- */
720
- function PageAnnotationMenu({ children, gap = 8, placement = "top" }) {
721
- const page = usePage();
722
- const anno = useCapability(AnnotationToken$1);
723
- const anchor = useSelector(AnnotationToken$1, (c) => c.selectionAnchor(page.transform.viewScale, page.transform.rotation, page.transform.zoom), sameAnchor);
724
- const selected = useAnnotationSelected();
725
- const [pos, setPos] = useState(null);
726
- const here = !!anchor && anchor.pon === page.pon;
727
- useLayoutEffect(() => {
728
- if (!here || !anchor) {
729
- setPos(null);
730
- return;
731
- }
732
- const measure = () => {
733
- const knob = anchor.knob ? page.toClientPoint(anchor.knob) : null;
734
- setPos(positionMenuAroundRect(page.toClientRect(anchor.bounds), placement, gap, knob));
735
- };
736
- measure();
737
- window.addEventListener("scroll", measure, true);
738
- window.addEventListener("resize", measure);
739
- return () => {
740
- window.removeEventListener("scroll", measure, true);
741
- window.removeEventListener("resize", measure);
742
- };
743
- }, [
744
- here,
745
- anchor,
746
- page,
747
- gap,
748
- placement
749
- ]);
750
- if (!pos) return null;
751
- return createPortal(/* @__PURE__ */ jsx("div", {
752
- style: {
753
- position: "fixed",
754
- left: pos.left,
755
- top: pos.top,
756
- transform: pos.transform,
757
- pointerEvents: "auto"
758
- },
759
- children: children({
760
- selected,
761
- deleteSelection: anno.deleteSelection,
762
- deselect: anno.deselect,
763
- updateSelection: anno.updateSelection,
764
- rotate90: anno.rotateSelection90,
765
- resetRotation: anno.resetSelectionRotation,
766
- group: anno.group,
767
- ungroup: anno.ungroup,
768
- canGroup: anno.canGroup(),
769
- canUngroup: anno.canUngroup()
770
- })
771
- }), document.body);
772
- }
773
- function PageAnnotationDraftMenu({ children, gap = 8, placement = "top" }) {
774
- const page = usePage();
775
- const anno = useCapability(AnnotationToken$1);
776
- const anchor = useSelector(AnnotationToken$1, (c) => c.creationDraftAnchor(), sameCreationDraftAnchor);
777
- const [pos, setPos] = useState(null);
778
- const ref = React.useRef(null);
779
- const here = !!anchor && anchor.pon === page.pon;
780
- useEffect(() => {
781
- const el = ref.current;
782
- if (!el) return;
783
- const stop = (e) => e.stopPropagation();
784
- el.addEventListener("pointerdown", stop);
785
- return () => el.removeEventListener("pointerdown", stop);
786
- });
787
- useLayoutEffect(() => {
788
- if (!here || !anchor) {
789
- setPos(null);
790
- return;
791
- }
792
- const measure = () => {
793
- setPos(positionMenuAroundRect(page.toClientRect(anchor.bounds), placement, gap));
794
- };
795
- measure();
796
- window.addEventListener("scroll", measure, true);
797
- window.addEventListener("resize", measure);
798
- return () => {
799
- window.removeEventListener("scroll", measure, true);
800
- window.removeEventListener("resize", measure);
801
- };
802
- }, [
803
- here,
804
- anchor,
805
- page,
806
- gap,
807
- placement
808
- ]);
809
- if (!anchor || !pos) return null;
810
- return createPortal(/* @__PURE__ */ jsx("div", {
811
- ref,
812
- style: {
813
- position: "fixed",
814
- left: pos.left,
815
- top: pos.top,
816
- transform: pos.transform,
817
- pointerEvents: "auto"
818
- },
819
- children: children({
820
- ...anchor,
821
- finish: anno.finishCreationDraft,
822
- cancel: anno.cancelCreationDraft
823
- })
824
- }), document.body);
825
- }
826
- //#endregion
827
- export { sameAnchor as a, useAnnotationDefaults as c, useFilePickerProvider as d, useSelectionFlags as f, filePickerProvider as i, useAnnotationSelected as l, positionMenuAroundRect as m, PageAnnotationDraftMenu as n, sameCreationDraftAnchor as o, useSelectionProps as p, PageAnnotationMenu as r, useAnnotation as s, AnnotationLayer as t, useAnnotationSelection as u };
828
-
829
- //# sourceMappingURL=annotation-DjGWJljY.js.map