@embedpdf/plugin-annotation 2.2.0 → 2.4.0

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 (119) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1232 -101
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/actions.d.ts +33 -3
  6. package/dist/lib/annotation-plugin.d.ts +178 -2
  7. package/dist/lib/handlers/types.d.ts +1 -1
  8. package/dist/lib/helpers.d.ts +8 -2
  9. package/dist/lib/selectors.d.ts +97 -8
  10. package/dist/lib/tools/default-tools.d.ts +39 -11
  11. package/dist/lib/tools/types.d.ts +22 -4
  12. package/dist/lib/types.d.ts +278 -1
  13. package/dist/preact/adapter.d.ts +3 -3
  14. package/dist/preact/index.cjs +1 -1
  15. package/dist/preact/index.cjs.map +1 -1
  16. package/dist/preact/index.js +1133 -403
  17. package/dist/preact/index.js.map +1 -1
  18. package/dist/react/adapter.d.ts +1 -1
  19. package/dist/react/index.cjs +1 -1
  20. package/dist/react/index.cjs.map +1 -1
  21. package/dist/react/index.js +1132 -402
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/shared/components/annotation-container.d.ts +13 -2
  24. package/dist/shared/components/annotation-layer.d.ts +6 -2
  25. package/dist/shared/components/annotations/ink.d.ts +3 -3
  26. package/dist/shared/components/annotations/link.d.ts +28 -0
  27. package/dist/shared/components/annotations.d.ts +4 -1
  28. package/dist/shared/components/group-selection-box.d.ts +32 -0
  29. package/dist/shared/components/index.d.ts +1 -0
  30. package/dist/shared/components/text-markup/highlight.d.ts +3 -2
  31. package/dist/shared/components/text-markup/squiggly.d.ts +3 -2
  32. package/dist/shared/components/text-markup/strikeout.d.ts +3 -2
  33. package/dist/shared/components/text-markup/underline.d.ts +3 -2
  34. package/dist/shared/components/types.d.ts +50 -1
  35. package/dist/shared/context/index.d.ts +1 -0
  36. package/dist/shared/context/renderer-registry.d.ts +21 -0
  37. package/dist/shared/index.d.ts +2 -0
  38. package/dist/shared-preact/components/annotation-container.d.ts +13 -2
  39. package/dist/shared-preact/components/annotation-layer.d.ts +6 -2
  40. package/dist/shared-preact/components/annotations/ink.d.ts +3 -3
  41. package/dist/shared-preact/components/annotations/link.d.ts +28 -0
  42. package/dist/shared-preact/components/annotations.d.ts +4 -1
  43. package/dist/shared-preact/components/group-selection-box.d.ts +32 -0
  44. package/dist/shared-preact/components/index.d.ts +1 -0
  45. package/dist/shared-preact/components/text-markup/highlight.d.ts +3 -2
  46. package/dist/shared-preact/components/text-markup/squiggly.d.ts +3 -2
  47. package/dist/shared-preact/components/text-markup/strikeout.d.ts +3 -2
  48. package/dist/shared-preact/components/text-markup/underline.d.ts +3 -2
  49. package/dist/shared-preact/components/types.d.ts +50 -1
  50. package/dist/shared-preact/context/index.d.ts +1 -0
  51. package/dist/shared-preact/context/renderer-registry.d.ts +21 -0
  52. package/dist/shared-preact/index.d.ts +2 -0
  53. package/dist/shared-react/components/annotation-container.d.ts +13 -2
  54. package/dist/shared-react/components/annotation-layer.d.ts +6 -2
  55. package/dist/shared-react/components/annotations/ink.d.ts +3 -3
  56. package/dist/shared-react/components/annotations/link.d.ts +28 -0
  57. package/dist/shared-react/components/annotations.d.ts +4 -1
  58. package/dist/shared-react/components/group-selection-box.d.ts +32 -0
  59. package/dist/shared-react/components/index.d.ts +1 -0
  60. package/dist/shared-react/components/text-markup/highlight.d.ts +3 -2
  61. package/dist/shared-react/components/text-markup/squiggly.d.ts +3 -2
  62. package/dist/shared-react/components/text-markup/strikeout.d.ts +3 -2
  63. package/dist/shared-react/components/text-markup/underline.d.ts +3 -2
  64. package/dist/shared-react/components/types.d.ts +50 -1
  65. package/dist/shared-react/context/index.d.ts +1 -0
  66. package/dist/shared-react/context/renderer-registry.d.ts +21 -0
  67. package/dist/shared-react/index.d.ts +2 -0
  68. package/dist/svelte/components/AnnotationLayer.svelte.d.ts +8 -1
  69. package/dist/svelte/components/Annotations.svelte.d.ts +8 -1
  70. package/dist/svelte/components/GroupSelectionBox.svelte.d.ts +32 -0
  71. package/dist/svelte/components/RendererRegistryProvider.svelte.d.ts +7 -0
  72. package/dist/svelte/components/annotations/Ink.svelte.d.ts +2 -1
  73. package/dist/svelte/components/annotations/Link.svelte.d.ts +24 -0
  74. package/dist/svelte/components/annotations/index.d.ts +1 -0
  75. package/dist/svelte/components/index.d.ts +2 -0
  76. package/dist/svelte/components/text-markup/Highlight.svelte.d.ts +2 -1
  77. package/dist/svelte/components/text-markup/Squiggly.svelte.d.ts +2 -1
  78. package/dist/svelte/components/text-markup/Strikeout.svelte.d.ts +2 -1
  79. package/dist/svelte/components/text-markup/Underline.svelte.d.ts +2 -1
  80. package/dist/svelte/components/types.d.ts +2 -0
  81. package/dist/svelte/context/index.d.ts +2 -0
  82. package/dist/svelte/context/renderer-registry.svelte.d.ts +20 -0
  83. package/dist/svelte/context/types.d.ts +33 -0
  84. package/dist/svelte/index.cjs +1 -1
  85. package/dist/svelte/index.cjs.map +1 -1
  86. package/dist/svelte/index.d.ts +2 -0
  87. package/dist/svelte/index.js +1215 -394
  88. package/dist/svelte/index.js.map +1 -1
  89. package/dist/svelte/types.d.ts +7 -0
  90. package/dist/vue/components/annotation-container.vue.d.ts +3 -1
  91. package/dist/vue/components/annotation-layer.vue.d.ts +31 -5
  92. package/dist/vue/components/annotations/free-text.vue.d.ts +1 -1
  93. package/dist/vue/components/annotations/index.d.ts +1 -0
  94. package/dist/vue/components/annotations/ink.vue.d.ts +2 -2
  95. package/dist/vue/components/annotations/line.vue.d.ts +1 -1
  96. package/dist/vue/components/annotations/link.vue.d.ts +29 -0
  97. package/dist/vue/components/annotations/polygon.vue.d.ts +1 -1
  98. package/dist/vue/components/annotations/polyline.vue.d.ts +1 -1
  99. package/dist/vue/components/annotations/stamp.vue.d.ts +1 -1
  100. package/dist/vue/components/annotations.vue.d.ts +151 -53
  101. package/dist/vue/components/group-selection-box.vue.d.ts +73 -0
  102. package/dist/vue/components/index.d.ts +2 -0
  103. package/dist/vue/components/preview-renderer.vue.d.ts +1 -1
  104. package/dist/vue/components/renderer-registry-provider.vue.d.ts +13 -0
  105. package/dist/vue/components/text-markup/highlight.vue.d.ts +2 -2
  106. package/dist/vue/components/text-markup/squiggly.vue.d.ts +2 -2
  107. package/dist/vue/components/text-markup/strikeout.vue.d.ts +2 -2
  108. package/dist/vue/components/text-markup/underline.vue.d.ts +2 -2
  109. package/dist/vue/context/index.d.ts +2 -0
  110. package/dist/vue/context/renderer-registry.d.ts +26 -0
  111. package/dist/vue/context/types.d.ts +33 -0
  112. package/dist/vue/hooks/use-annotation.d.ts +7 -5
  113. package/dist/vue/index.cjs +1 -1
  114. package/dist/vue/index.cjs.map +1 -1
  115. package/dist/vue/index.d.ts +3 -1
  116. package/dist/vue/index.js +1124 -459
  117. package/dist/vue/index.js.map +1 -1
  118. package/dist/vue/types.d.ts +8 -1
  119. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { clamp, BasePlugin, createBehaviorEmitter } from "@embedpdf/core";
2
- import { PdfAnnotationSubtype, PdfBlendMode, PdfAnnotationBorderStyle, PdfAnnotationLineEnding, PdfVerticalAlignment, PdfTextAlignment, PdfStandardFont, expandRect, rectFromPoints, uuidV4, rotateAndTranslatePoint, PdfAnnotationIcon, PdfPermissionFlag, ignore, PdfTaskHelper, PdfErrorCode, Task } from "@embedpdf/models";
2
+ import { PdfAnnotationSubtype, PdfAnnotationReplyType, PdfBlendMode, PdfAnnotationBorderStyle, PdfAnnotationLineEnding, PdfVerticalAlignment, PdfTextAlignment, PdfStandardFont, expandRect, rectFromPoints, uuidV4, rotateAndTranslatePoint, PdfAnnotationIcon, PdfPermissionFlag, ignore, PdfTaskHelper, PdfErrorCode, Task, TaskStage } from "@embedpdf/models";
3
3
  const ANNOTATION_PLUGIN_ID = "annotation";
4
4
  const manifest = {
5
5
  id: ANNOTATION_PLUGIN_ID,
@@ -21,6 +21,9 @@ const SET_ACTIVE_DOCUMENT = "ANNOTATION/SET_ACTIVE_DOCUMENT";
21
21
  const SET_ANNOTATIONS = "ANNOTATION/SET_ANNOTATIONS";
22
22
  const SELECT_ANNOTATION = "ANNOTATION/SELECT_ANNOTATION";
23
23
  const DESELECT_ANNOTATION = "ANNOTATION/DESELECT_ANNOTATION";
24
+ const ADD_TO_SELECTION = "ANNOTATION/ADD_TO_SELECTION";
25
+ const REMOVE_FROM_SELECTION = "ANNOTATION/REMOVE_FROM_SELECTION";
26
+ const SET_SELECTION = "ANNOTATION/SET_SELECTION";
24
27
  const SET_ACTIVE_TOOL_ID = "ANNOTATION/SET_ACTIVE_TOOL_ID";
25
28
  const CREATE_ANNOTATION = "ANNOTATION/CREATE_ANNOTATION";
26
29
  const PATCH_ANNOTATION = "ANNOTATION/PATCH_ANNOTATION";
@@ -48,6 +51,18 @@ const deselectAnnotation = (documentId) => ({
48
51
  type: DESELECT_ANNOTATION,
49
52
  payload: { documentId }
50
53
  });
54
+ const addToSelection = (documentId, pageIndex, id) => ({
55
+ type: ADD_TO_SELECTION,
56
+ payload: { documentId, pageIndex, id }
57
+ });
58
+ const removeFromSelection = (documentId, id) => ({
59
+ type: REMOVE_FROM_SELECTION,
60
+ payload: { documentId, id }
61
+ });
62
+ const setSelection = (documentId, ids) => ({
63
+ type: SET_SELECTION,
64
+ payload: { documentId, ids }
65
+ });
51
66
  const setActiveToolId = (documentId, toolId) => ({
52
67
  type: SET_ACTIVE_TOOL_ID,
53
68
  payload: { documentId, toolId }
@@ -64,13 +79,13 @@ const deleteAnnotation = (documentId, pageIndex, id) => ({
64
79
  type: DELETE_ANNOTATION,
65
80
  payload: { documentId, pageIndex, id }
66
81
  });
67
- const commitPendingChanges = (documentId) => ({
82
+ const commitPendingChanges = (documentId, committedUids) => ({
68
83
  type: COMMIT_PENDING_CHANGES,
69
- payload: { documentId }
84
+ payload: { documentId, committedUids }
70
85
  });
71
- const purgeAnnotation = (documentId, uid) => ({
86
+ const purgeAnnotation = (documentId, pageIndex, uid) => ({
72
87
  type: PURGE_ANNOTATION,
73
- payload: { documentId, uid }
88
+ payload: { documentId, pageIndex, uid }
74
89
  });
75
90
  const addColorPreset = (c) => ({
76
91
  type: ADD_COLOR_PRESET,
@@ -84,6 +99,9 @@ const addTool = (tool) => ({
84
99
  type: ADD_TOOL,
85
100
  payload: tool
86
101
  });
102
+ function rectsIntersect(a, b) {
103
+ return !(a.origin.x + a.size.width < b.origin.x || b.origin.x + b.size.width < a.origin.x || a.origin.y + a.size.height < b.origin.y || b.origin.y + b.size.height < a.origin.y);
104
+ }
87
105
  function isInk(a) {
88
106
  return a.object.type === PdfAnnotationSubtype.INK;
89
107
  }
@@ -126,8 +144,14 @@ function isStamp(a) {
126
144
  function isText(a) {
127
145
  return a.object.type === PdfAnnotationSubtype.TEXT;
128
146
  }
147
+ function isLink(a) {
148
+ return a.object.type === PdfAnnotationSubtype.LINK;
149
+ }
150
+ function isRedact(a) {
151
+ return a.object.type === PdfAnnotationSubtype.REDACT;
152
+ }
129
153
  function isSidebarAnnotation(a) {
130
- return isTextMarkup(a) || isInk(a) || isSquare(a) || isCircle(a) || isPolygon(a) || isLine(a) || isPolyline(a) || isFreeText(a) || isStamp(a);
154
+ return isTextMarkup(a) || isInk(a) || isSquare(a) || isCircle(a) || isPolygon(a) || isLine(a) || isPolyline(a) || isFreeText(a) || isStamp(a) || isRedact(a);
131
155
  }
132
156
  const getAnnotationsByPageIndex = (s, page) => (s.pages[page] ?? []).map((uid) => s.byUid[uid]);
133
157
  const getAnnotations = (s) => {
@@ -135,17 +159,25 @@ const getAnnotations = (s) => {
135
159
  for (const p of Object.keys(s.pages).map(Number)) out[p] = getAnnotationsByPageIndex(s, p);
136
160
  return out;
137
161
  };
138
- const getSelectedAnnotation = (s) => s.selectedUid ? s.byUid[s.selectedUid] : null;
162
+ const getSelectedAnnotation = (s) => s.selectedUids.length > 0 ? s.byUid[s.selectedUids[0]] ?? null : null;
163
+ const getSelectedAnnotations = (s) => s.selectedUids.map((uid) => s.byUid[uid]).filter((ta) => ta !== void 0);
164
+ const getSelectedAnnotationIds = (s) => s.selectedUids;
139
165
  const getAnnotationByUid = (s, uid) => s.byUid[uid] ?? null;
140
166
  const getSelectedAnnotationByPageIndex = (s, pageIndex) => {
141
- if (!s.selectedUid) return null;
142
167
  const pageUids = s.pages[pageIndex] ?? [];
143
- if (pageUids.includes(s.selectedUid)) {
144
- return s.byUid[s.selectedUid];
168
+ for (const uid of s.selectedUids) {
169
+ if (pageUids.includes(uid)) {
170
+ return s.byUid[uid] ?? null;
171
+ }
145
172
  }
146
173
  return null;
147
174
  };
148
- const isAnnotationSelected = (s, id) => s.selectedUid === id;
175
+ const getSelectedAnnotationsByPageIndex = (s, pageIndex) => {
176
+ const pageUids = new Set(s.pages[pageIndex] ?? []);
177
+ return s.selectedUids.filter((uid) => pageUids.has(uid)).map((uid) => s.byUid[uid]).filter((ta) => ta !== void 0);
178
+ };
179
+ const isAnnotationSelected = (s, id) => s.selectedUids.includes(id);
180
+ const hasMultipleSelected = (s) => s.selectedUids.length > 1;
149
181
  function getToolDefaultsById(state, toolId) {
150
182
  const tool = state.tools.find((t) => t.id === toolId);
151
183
  return tool == null ? void 0 : tool.defaults;
@@ -190,6 +222,101 @@ const getSidebarAnnotationsWithReplies = (s) => {
190
222
  }
191
223
  return out;
192
224
  };
225
+ const getIRTChildIds = (s, parentId) => {
226
+ const children = [];
227
+ for (const uidList of Object.values(s.pages)) {
228
+ for (const uid of uidList) {
229
+ const ta = s.byUid[uid];
230
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId) {
231
+ children.push({ id: ta.object.id, pageIndex: ta.object.pageIndex });
232
+ }
233
+ }
234
+ }
235
+ return children;
236
+ };
237
+ const hasIRTChildren = (s, parentId) => {
238
+ for (const uidList of Object.values(s.pages)) {
239
+ for (const uid of uidList) {
240
+ const ta = s.byUid[uid];
241
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId) {
242
+ return true;
243
+ }
244
+ }
245
+ }
246
+ return false;
247
+ };
248
+ const getIRTChildrenByType = (s, parentId, types) => {
249
+ const children = [];
250
+ for (const uidList of Object.values(s.pages)) {
251
+ for (const uid of uidList) {
252
+ const ta = s.byUid[uid];
253
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId && types.includes(ta.object.type)) {
254
+ children.push(ta);
255
+ }
256
+ }
257
+ }
258
+ return children;
259
+ };
260
+ const getAttachedLinks = (s, parentId) => getIRTChildrenByType(s, parentId, [PdfAnnotationSubtype.LINK]);
261
+ const hasAttachedLinks = (s, parentId) => getAttachedLinks(s, parentId).length > 0;
262
+ const getGroupLeaderId = (s, annotationId) => {
263
+ const ta = s.byUid[annotationId];
264
+ if (!ta) return void 0;
265
+ if (ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group) {
266
+ return ta.object.inReplyToId;
267
+ }
268
+ return annotationId;
269
+ };
270
+ const getGroupMembers = (s, annotationId) => {
271
+ const leaderId = getGroupLeaderId(s, annotationId);
272
+ if (!leaderId) return [];
273
+ const members = [];
274
+ const leader = s.byUid[leaderId];
275
+ if (leader && leader.object.type !== PdfAnnotationSubtype.LINK) {
276
+ members.push(leader);
277
+ }
278
+ for (const uidList of Object.values(s.pages)) {
279
+ for (const uid of uidList) {
280
+ const ta = s.byUid[uid];
281
+ if (ta && ta.object.inReplyToId === leaderId && ta.object.replyType === PdfAnnotationReplyType.Group && ta.object.type !== PdfAnnotationSubtype.LINK) {
282
+ members.push(ta);
283
+ }
284
+ }
285
+ }
286
+ return members;
287
+ };
288
+ const isInGroup = (s, annotationId) => {
289
+ const ta = s.byUid[annotationId];
290
+ if (!ta) return false;
291
+ if (ta.object.type === PdfAnnotationSubtype.LINK) {
292
+ return false;
293
+ }
294
+ if (ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group) {
295
+ return true;
296
+ }
297
+ for (const uidList of Object.values(s.pages)) {
298
+ for (const uid of uidList) {
299
+ const other = s.byUid[uid];
300
+ if (other && other.object.inReplyToId === annotationId && other.object.replyType === PdfAnnotationReplyType.Group && other.object.type !== PdfAnnotationSubtype.LINK) {
301
+ return true;
302
+ }
303
+ }
304
+ }
305
+ return false;
306
+ };
307
+ const getSelectionGroupingAction = (s) => {
308
+ const selected = getSelectedAnnotations(s);
309
+ if (selected.length === 0) return "disabled";
310
+ const firstId = selected[0].object.id;
311
+ if (isInGroup(s, firstId)) {
312
+ const members = getGroupMembers(s, firstId);
313
+ const memberIds = new Set(members.map((m) => m.object.id));
314
+ if (selected.every((ta) => memberIds.has(ta.object.id))) {
315
+ return "ungroup";
316
+ }
317
+ }
318
+ return selected.length >= 2 ? "group" : "disabled";
319
+ };
193
320
  const defaultTools = [
194
321
  // Text Markup Tools
195
322
  {
@@ -200,11 +327,16 @@ const defaultTools = [
200
327
  exclusive: false,
201
328
  textSelection: true,
202
329
  isDraggable: false,
203
- isResizable: false
330
+ isResizable: false,
331
+ // Text markup annotations are anchored to text and should not move/resize in groups
332
+ isGroupDraggable: false,
333
+ isGroupResizable: false
204
334
  },
205
335
  defaults: {
206
336
  type: PdfAnnotationSubtype.HIGHLIGHT,
337
+ strokeColor: "#FFCD45",
207
338
  color: "#FFCD45",
339
+ // deprecated alias
208
340
  opacity: 1,
209
341
  blendMode: PdfBlendMode.Multiply
210
342
  }
@@ -217,11 +349,15 @@ const defaultTools = [
217
349
  exclusive: false,
218
350
  textSelection: true,
219
351
  isDraggable: false,
220
- isResizable: false
352
+ isResizable: false,
353
+ isGroupDraggable: false,
354
+ isGroupResizable: false
221
355
  },
222
356
  defaults: {
223
357
  type: PdfAnnotationSubtype.UNDERLINE,
358
+ strokeColor: "#E44234",
224
359
  color: "#E44234",
360
+ // deprecated alias
225
361
  opacity: 1
226
362
  }
227
363
  },
@@ -231,11 +367,17 @@ const defaultTools = [
231
367
  matchScore: (a) => a.type === PdfAnnotationSubtype.STRIKEOUT ? 1 : 0,
232
368
  interaction: {
233
369
  exclusive: false,
234
- textSelection: true
370
+ textSelection: true,
371
+ isDraggable: false,
372
+ isResizable: false,
373
+ isGroupDraggable: false,
374
+ isGroupResizable: false
235
375
  },
236
376
  defaults: {
237
377
  type: PdfAnnotationSubtype.STRIKEOUT,
378
+ strokeColor: "#E44234",
238
379
  color: "#E44234",
380
+ // deprecated alias
239
381
  opacity: 1
240
382
  }
241
383
  },
@@ -247,11 +389,15 @@ const defaultTools = [
247
389
  exclusive: false,
248
390
  textSelection: true,
249
391
  isDraggable: false,
250
- isResizable: false
392
+ isResizable: false,
393
+ isGroupDraggable: false,
394
+ isGroupResizable: false
251
395
  },
252
396
  defaults: {
253
397
  type: PdfAnnotationSubtype.SQUIGGLY,
398
+ strokeColor: "#E44234",
254
399
  color: "#E44234",
400
+ // deprecated alias
255
401
  opacity: 1
256
402
  }
257
403
  },
@@ -269,7 +415,9 @@ const defaultTools = [
269
415
  },
270
416
  defaults: {
271
417
  type: PdfAnnotationSubtype.INK,
418
+ strokeColor: "#E44234",
272
419
  color: "#E44234",
420
+ // deprecated alias
273
421
  opacity: 1,
274
422
  strokeWidth: 6
275
423
  }
@@ -288,7 +436,9 @@ const defaultTools = [
288
436
  defaults: {
289
437
  type: PdfAnnotationSubtype.INK,
290
438
  intent: "InkHighlight",
439
+ strokeColor: "#FFCD45",
291
440
  color: "#FFCD45",
441
+ // deprecated alias
292
442
  opacity: 1,
293
443
  strokeWidth: 14,
294
444
  blendMode: PdfBlendMode.Multiply
@@ -352,7 +502,10 @@ const defaultTools = [
352
502
  cursor: "crosshair",
353
503
  isDraggable: true,
354
504
  isResizable: false,
355
- lockAspectRatio: false
505
+ // Uses vertex editing when selected individually
506
+ lockAspectRatio: false,
507
+ isGroupResizable: true
508
+ // Scales proportionally in a group
356
509
  },
357
510
  defaults: {
358
511
  type: PdfAnnotationSubtype.LINE,
@@ -376,7 +529,10 @@ const defaultTools = [
376
529
  cursor: "crosshair",
377
530
  isDraggable: true,
378
531
  isResizable: false,
379
- lockAspectRatio: false
532
+ // Uses vertex editing when selected individually
533
+ lockAspectRatio: false,
534
+ isGroupResizable: true
535
+ // Scales proportionally in a group
380
536
  },
381
537
  defaults: {
382
538
  type: PdfAnnotationSubtype.LINE,
@@ -405,7 +561,10 @@ const defaultTools = [
405
561
  cursor: "crosshair",
406
562
  isDraggable: true,
407
563
  isResizable: false,
408
- lockAspectRatio: false
564
+ // Uses vertex editing when selected individually
565
+ lockAspectRatio: false,
566
+ isGroupResizable: true
567
+ // Scales proportionally in a group
409
568
  },
410
569
  defaults: {
411
570
  type: PdfAnnotationSubtype.POLYLINE,
@@ -424,7 +583,10 @@ const defaultTools = [
424
583
  cursor: "crosshair",
425
584
  isDraggable: true,
426
585
  isResizable: false,
427
- lockAspectRatio: false
586
+ // Uses vertex editing when selected individually
587
+ lockAspectRatio: false,
588
+ isGroupResizable: true
589
+ // Scales proportionally in a group
428
590
  },
429
591
  defaults: {
430
592
  type: PdfAnnotationSubtype.POLYGON,
@@ -454,7 +616,10 @@ const defaultTools = [
454
616
  fontFamily: PdfStandardFont.Helvetica,
455
617
  textAlign: PdfTextAlignment.Left,
456
618
  verticalAlign: PdfVerticalAlignment.Top,
619
+ color: "transparent",
620
+ // fill color (matches shape convention)
457
621
  backgroundColor: "transparent",
622
+ // deprecated alias
458
623
  opacity: 1
459
624
  },
460
625
  clickBehavior: {
@@ -492,9 +657,11 @@ const DEFAULT_COLORS = [
492
657
  "#000000",
493
658
  "#FFFFFF"
494
659
  ];
660
+ const computeSelectedUid = (selectedUids) => selectedUids.length === 1 ? selectedUids[0] : null;
495
661
  const initialDocumentState = () => ({
496
662
  pages: {},
497
663
  byUid: {},
664
+ selectedUids: [],
498
665
  selectedUid: null,
499
666
  activeToolId: null,
500
667
  hasPendingChanges: false
@@ -595,7 +762,7 @@ const reducer = (state, action) => {
595
762
  ...state,
596
763
  documents: {
597
764
  ...state.documents,
598
- [documentId]: { ...docState, selectedUid: id }
765
+ [documentId]: { ...docState, selectedUids: [id], selectedUid: id }
599
766
  }
600
767
  };
601
768
  }
@@ -607,7 +774,58 @@ const reducer = (state, action) => {
607
774
  ...state,
608
775
  documents: {
609
776
  ...state.documents,
610
- [documentId]: { ...docState, selectedUid: null }
777
+ [documentId]: { ...docState, selectedUids: [], selectedUid: null }
778
+ }
779
+ };
780
+ }
781
+ case ADD_TO_SELECTION: {
782
+ const { documentId, id } = action.payload;
783
+ const docState = state.documents[documentId];
784
+ if (!docState) return state;
785
+ if (docState.selectedUids.includes(id)) return state;
786
+ const newSelectedUids = [...docState.selectedUids, id];
787
+ return {
788
+ ...state,
789
+ documents: {
790
+ ...state.documents,
791
+ [documentId]: {
792
+ ...docState,
793
+ selectedUids: newSelectedUids,
794
+ selectedUid: computeSelectedUid(newSelectedUids)
795
+ }
796
+ }
797
+ };
798
+ }
799
+ case REMOVE_FROM_SELECTION: {
800
+ const { documentId, id } = action.payload;
801
+ const docState = state.documents[documentId];
802
+ if (!docState) return state;
803
+ const newSelectedUids = docState.selectedUids.filter((uid) => uid !== id);
804
+ return {
805
+ ...state,
806
+ documents: {
807
+ ...state.documents,
808
+ [documentId]: {
809
+ ...docState,
810
+ selectedUids: newSelectedUids,
811
+ selectedUid: computeSelectedUid(newSelectedUids)
812
+ }
813
+ }
814
+ };
815
+ }
816
+ case SET_SELECTION: {
817
+ const { documentId, ids } = action.payload;
818
+ const docState = state.documents[documentId];
819
+ if (!docState) return state;
820
+ return {
821
+ ...state,
822
+ documents: {
823
+ ...state.documents,
824
+ [documentId]: {
825
+ ...docState,
826
+ selectedUids: ids,
827
+ selectedUid: computeSelectedUid(ids)
828
+ }
611
829
  }
612
830
  };
613
831
  }
@@ -683,34 +901,50 @@ const reducer = (state, action) => {
683
901
  };
684
902
  }
685
903
  case COMMIT_PENDING_CHANGES: {
686
- const { documentId } = action.payload;
904
+ const { documentId, committedUids } = action.payload;
687
905
  const docState = state.documents[documentId];
688
906
  if (!docState) return state;
907
+ const committedSet = new Set(committedUids);
689
908
  const cleaned = {};
909
+ let stillHasPending = false;
690
910
  for (const [uid, ta] of Object.entries(docState.byUid)) {
691
- cleaned[uid] = {
692
- ...ta,
693
- commitState: ta.commitState === "dirty" || ta.commitState === "new" ? "synced" : ta.commitState
694
- };
911
+ if (committedSet.has(uid)) {
912
+ cleaned[uid] = {
913
+ ...ta,
914
+ commitState: ta.commitState === "dirty" || ta.commitState === "new" ? "synced" : ta.commitState
915
+ };
916
+ } else {
917
+ cleaned[uid] = ta;
918
+ if (ta.commitState === "new" || ta.commitState === "dirty" || ta.commitState === "deleted") {
919
+ stillHasPending = true;
920
+ }
921
+ }
695
922
  }
696
923
  return {
697
924
  ...state,
698
925
  documents: {
699
926
  ...state.documents,
700
- [documentId]: { ...docState, byUid: cleaned, hasPendingChanges: false }
927
+ [documentId]: { ...docState, byUid: cleaned, hasPendingChanges: stillHasPending }
701
928
  }
702
929
  };
703
930
  }
704
931
  case PURGE_ANNOTATION: {
705
- const { documentId, uid } = action.payload;
932
+ const { documentId, pageIndex, uid } = action.payload;
706
933
  const docState = state.documents[documentId];
707
- if (!docState) return state;
934
+ if (!docState || !docState.byUid[uid]) return state;
708
935
  const { [uid]: _gone, ...rest } = docState.byUid;
709
936
  return {
710
937
  ...state,
711
938
  documents: {
712
939
  ...state.documents,
713
- [documentId]: { ...docState, byUid: rest }
940
+ [documentId]: {
941
+ ...docState,
942
+ pages: {
943
+ ...docState.pages,
944
+ [pageIndex]: (docState.pages[pageIndex] ?? []).filter((u) => u !== uid)
945
+ },
946
+ byUid: rest
947
+ }
714
948
  }
715
949
  };
716
950
  }
@@ -763,7 +997,7 @@ const inkHandlerFactory = {
763
997
  return {
764
998
  ...tool.defaults,
765
999
  strokeWidth: tool.defaults.strokeWidth ?? 1,
766
- color: tool.defaults.color ?? "#000000",
1000
+ strokeColor: tool.defaults.strokeColor ?? tool.defaults.color ?? "#000000",
767
1001
  opacity: tool.defaults.opacity ?? 1,
768
1002
  flags: tool.defaults.flags ?? ["print"]
769
1003
  };
@@ -899,7 +1133,7 @@ const freeTextHandlerFactory = {
899
1133
  opacity: tool.defaults.opacity ?? 1,
900
1134
  fontSize: tool.defaults.fontSize ?? 12,
901
1135
  fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
902
- backgroundColor: tool.defaults.backgroundColor ?? "transparent",
1136
+ color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
903
1137
  textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
904
1138
  verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
905
1139
  contents: tool.defaults.contents ?? "Insert text here",
@@ -1435,7 +1669,10 @@ const polylineHandlerFactory = {
1435
1669
  };
1436
1670
  };
1437
1671
  return {
1438
- onClick: (pos) => {
1672
+ onClick: (pos, evt) => {
1673
+ if (evt.metaKey || evt.ctrlKey) {
1674
+ return;
1675
+ }
1439
1676
  const clampedPos = clampToPage(pos);
1440
1677
  const vertices = getVertices();
1441
1678
  const lastVertex = vertices[vertices.length - 1];
@@ -1537,7 +1774,10 @@ const polygonHandlerFactory = {
1537
1774
  };
1538
1775
  };
1539
1776
  return {
1540
- onClick: (pos) => {
1777
+ onClick: (pos, evt) => {
1778
+ if (evt.metaKey || evt.ctrlKey) {
1779
+ return;
1780
+ }
1541
1781
  const clampedPos = clampToPage(pos);
1542
1782
  if (isInsideStartHandle(clampedPos) && getVertices().length >= 3) {
1543
1783
  commitPolygon();
@@ -2234,11 +2474,16 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2234
2474
  this.pendingContexts = /* @__PURE__ */ new Map();
2235
2475
  this.isInitialLoadComplete = /* @__PURE__ */ new Map();
2236
2476
  this.importQueue = /* @__PURE__ */ new Map();
2477
+ this.commitInProgress = /* @__PURE__ */ new Map();
2237
2478
  this.handlerFactories = /* @__PURE__ */ new Map();
2238
2479
  this.activeTool$ = createBehaviorEmitter();
2239
2480
  this.events$ = createBehaviorEmitter();
2240
2481
  this.toolsChange$ = createBehaviorEmitter();
2241
2482
  this.patchRegistry = new PatchRegistry();
2483
+ this.unifiedDragStates = /* @__PURE__ */ new Map();
2484
+ this.unifiedDrag$ = createBehaviorEmitter();
2485
+ this.unifiedResizeStates = /* @__PURE__ */ new Map();
2486
+ this.unifiedResize$ = createBehaviorEmitter();
2242
2487
  this.config = config;
2243
2488
  this.selection = ((_a = registry.getPlugin("selection")) == null ? void 0 : _a.provides()) ?? null;
2244
2489
  this.history = ((_b = registry.getPlugin("history")) == null ? void 0 : _b.provides()) ?? null;
@@ -2268,7 +2513,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2268
2513
  if (this.selection) {
2269
2514
  for (const tool of this.state.tools) {
2270
2515
  if (tool.interaction.textSelection) {
2271
- this.selection.enableForMode(tool.interaction.mode ?? tool.id);
2516
+ this.selection.enableForMode(tool.interaction.mode ?? tool.id, { showRects: false });
2272
2517
  }
2273
2518
  }
2274
2519
  }
@@ -2301,7 +2546,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2301
2546
  this.patchRegistry.register(PdfAnnotationSubtype.POLYGON, patchPolygon);
2302
2547
  }
2303
2548
  async initialize() {
2304
- var _a, _b;
2549
+ var _a, _b, _c;
2305
2550
  this.state.tools.forEach((tool) => this.registerInteractionForTool(tool));
2306
2551
  if (this.history) {
2307
2552
  this.history.onHistoryChange((event) => {
@@ -2318,8 +2563,28 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2318
2563
  this.dispatch(setActiveToolId(s.documentId, newToolId));
2319
2564
  }
2320
2565
  });
2321
- (_b = this.selection) == null ? void 0 : _b.onEndSelection(({ documentId }) => {
2322
- var _a2, _b2, _c;
2566
+ (_b = this.selection) == null ? void 0 : _b.onMarqueeEnd(({ documentId, pageIndex, rect }) => {
2567
+ const docState = this.state.documents[documentId];
2568
+ if (!docState) return;
2569
+ const pageAnnotations = (docState.pages[pageIndex] ?? []).map((uid) => docState.byUid[uid]).filter((ta) => ta !== void 0).filter((ta) => !isLink(ta));
2570
+ const selectedIds = pageAnnotations.filter((ta) => rectsIntersect(rect, ta.object.rect)).map((ta) => ta.object.id);
2571
+ if (selectedIds.length > 0) {
2572
+ const expandedIds = /* @__PURE__ */ new Set();
2573
+ for (const id of selectedIds) {
2574
+ if (this.isInGroupMethod(id, documentId)) {
2575
+ const members = this.getGroupMembersMethod(id, documentId);
2576
+ for (const member of members) {
2577
+ expandedIds.add(member.object.id);
2578
+ }
2579
+ } else {
2580
+ expandedIds.add(id);
2581
+ }
2582
+ }
2583
+ this.setSelectionMethod([...expandedIds], documentId);
2584
+ }
2585
+ });
2586
+ (_c = this.selection) == null ? void 0 : _c.onEndSelection(({ documentId }) => {
2587
+ var _a2, _b2, _c2;
2323
2588
  if (!this.checkPermission(documentId, PdfPermissionFlag.ModifyAnnotations)) {
2324
2589
  return;
2325
2590
  }
@@ -2355,7 +2620,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2355
2620
  }
2356
2621
  }, ignore);
2357
2622
  }
2358
- (_c = this.selection) == null ? void 0 : _c.clear();
2623
+ (_c2 = this.selection) == null ? void 0 : _c2.clear();
2359
2624
  });
2360
2625
  }
2361
2626
  registerInteractionForTool(tool) {
@@ -2375,15 +2640,33 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2375
2640
  getState: () => this.getDocumentState(),
2376
2641
  getPageAnnotations: (options) => this.getPageAnnotations(options),
2377
2642
  getSelectedAnnotation: () => this.getSelectedAnnotation(),
2643
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(),
2644
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(),
2378
2645
  getAnnotationById: (id) => this.getAnnotationById(id),
2379
2646
  selectAnnotation: (pageIndex, id) => this.selectAnnotation(pageIndex, id),
2647
+ toggleSelection: (pageIndex, id) => this.toggleSelectionMethod(pageIndex, id),
2648
+ addToSelection: (pageIndex, id) => this.addToSelectionMethod(pageIndex, id),
2649
+ removeFromSelection: (id) => this.removeFromSelectionMethod(id),
2650
+ setSelection: (ids) => this.setSelectionMethod(ids),
2380
2651
  deselectAnnotation: () => this.deselectAnnotation(),
2381
2652
  importAnnotations: (items) => this.importAnnotations(items),
2382
2653
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx),
2383
2654
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch),
2655
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches),
2384
2656
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id),
2657
+ deleteAnnotations: (annotations, documentId) => this.deleteAnnotationsMethod(annotations, documentId),
2658
+ purgeAnnotation: (pageIndex, id, documentId) => this.purgeAnnotationMethod(pageIndex, id, documentId),
2385
2659
  renderAnnotation: (options) => this.renderAnnotation(options),
2386
2660
  commit: () => this.commit(),
2661
+ // Attached links (IRT link children)
2662
+ getAttachedLinks: (id, documentId) => this.getAttachedLinksMethod(id, documentId),
2663
+ hasAttachedLinks: (id, documentId) => this.hasAttachedLinksMethod(id, documentId),
2664
+ deleteAttachedLinks: (id, documentId) => this.deleteAttachedLinksMethod(id, documentId),
2665
+ // Annotation grouping (RT = Group)
2666
+ groupAnnotations: (documentId) => this.groupAnnotationsMethod(documentId),
2667
+ ungroupAnnotations: (id, documentId) => this.ungroupAnnotationsMethod(id, documentId),
2668
+ getGroupMembers: (id, documentId) => this.getGroupMembersMethod(id, documentId),
2669
+ isInGroup: (id, documentId) => this.isInGroupMethod(id, documentId),
2387
2670
  // Document-scoped operations
2388
2671
  forDocument: (documentId) => this.createAnnotationScope(documentId),
2389
2672
  // Global operations
@@ -2414,8 +2697,14 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2414
2697
  getState: () => this.getDocumentState(documentId),
2415
2698
  getPageAnnotations: (options) => this.getPageAnnotations(options, documentId),
2416
2699
  getSelectedAnnotation: () => this.getSelectedAnnotation(documentId),
2700
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(documentId),
2701
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(documentId),
2417
2702
  getAnnotationById: (id) => this.getAnnotationById(id, documentId),
2418
2703
  selectAnnotation: (pageIndex, id) => this.selectAnnotation(pageIndex, id, documentId),
2704
+ toggleSelection: (pageIndex, id) => this.toggleSelectionMethod(pageIndex, id, documentId),
2705
+ addToSelection: (pageIndex, id) => this.addToSelectionMethod(pageIndex, id, documentId),
2706
+ removeFromSelection: (id) => this.removeFromSelectionMethod(id, documentId),
2707
+ setSelection: (ids) => this.setSelectionMethod(ids, documentId),
2419
2708
  deselectAnnotation: () => this.deselectAnnotation(documentId),
2420
2709
  getActiveTool: () => this.getActiveTool(documentId),
2421
2710
  setActiveTool: (toolId) => this.setActiveTool(toolId, documentId),
@@ -2423,9 +2712,20 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2423
2712
  importAnnotations: (items) => this.importAnnotations(items, documentId),
2424
2713
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx, documentId),
2425
2714
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch, documentId),
2715
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches, documentId),
2426
2716
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id, documentId),
2717
+ deleteAnnotations: (annotations) => this.deleteAnnotationsMethod(annotations, documentId),
2718
+ purgeAnnotation: (pageIndex, id) => this.purgeAnnotationMethod(pageIndex, id, documentId),
2427
2719
  renderAnnotation: (options) => this.renderAnnotation(options, documentId),
2428
2720
  commit: () => this.commit(documentId),
2721
+ getAttachedLinks: (id) => this.getAttachedLinksMethod(id, documentId),
2722
+ hasAttachedLinks: (id) => this.hasAttachedLinksMethod(id, documentId),
2723
+ deleteAttachedLinks: (id) => this.deleteAttachedLinksMethod(id, documentId),
2724
+ groupAnnotations: () => this.groupAnnotationsMethod(documentId),
2725
+ ungroupAnnotations: (id) => this.ungroupAnnotationsMethod(id, documentId),
2726
+ getGroupMembers: (id) => this.getGroupMembersMethod(id, documentId),
2727
+ isInGroup: (id) => this.isInGroupMethod(id, documentId),
2728
+ getGroupingAction: () => this.getGroupingActionMethod(documentId),
2429
2729
  onStateChange: (listener) => this.state$.on((event) => {
2430
2730
  if (event.documentId === documentId) listener(event.state);
2431
2731
  }),
@@ -2658,7 +2958,8 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2658
2958
  pageIndex,
2659
2959
  committed: false
2660
2960
  });
2661
- }
2961
+ },
2962
+ metadata: { annotationIds: [id] }
2662
2963
  };
2663
2964
  const historyScope = this.history.forDocument(docId);
2664
2965
  historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
@@ -2719,7 +3020,8 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2719
3020
  patch: originalPatch,
2720
3021
  committed: false
2721
3022
  });
2722
- }
3023
+ },
3024
+ metadata: { annotationIds: [id] }
2723
3025
  };
2724
3026
  const historyScope = this.history.forDocument(docId);
2725
3027
  historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
@@ -2738,7 +3040,26 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2738
3040
  const docState = this.getDocumentState(docId);
2739
3041
  const originalAnnotation = (_a = docState.byUid[id]) == null ? void 0 : _a.object;
2740
3042
  if (!originalAnnotation) return;
3043
+ const irtChildren = getIRTChildIds(docState, id);
3044
+ const childAnnotations = irtChildren.map((child) => {
3045
+ var _a2;
3046
+ return (_a2 = docState.byUid[child.id]) == null ? void 0 : _a2.object;
3047
+ }).filter((obj) => obj !== void 0);
2741
3048
  const execute = () => {
3049
+ var _a2;
3050
+ for (const child of irtChildren) {
3051
+ const childObj = (_a2 = docState.byUid[child.id]) == null ? void 0 : _a2.object;
3052
+ if (childObj) {
3053
+ this.dispatch(deleteAnnotation(docId, child.pageIndex, child.id));
3054
+ this.events$.emit({
3055
+ type: "delete",
3056
+ documentId: docId,
3057
+ annotation: childObj,
3058
+ pageIndex: child.pageIndex,
3059
+ committed: false
3060
+ });
3061
+ }
3062
+ }
2742
3063
  this.dispatch(deselectAnnotation(docId));
2743
3064
  this.dispatch(deleteAnnotation(docId, pageIndex, id));
2744
3065
  this.events$.emit({
@@ -2765,19 +3086,709 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2765
3086
  pageIndex,
2766
3087
  committed: false
2767
3088
  });
2768
- }
3089
+ for (const childObj of childAnnotations) {
3090
+ this.dispatch(createAnnotation(docId, childObj.pageIndex, childObj));
3091
+ this.events$.emit({
3092
+ type: "create",
3093
+ documentId: docId,
3094
+ annotation: childObj,
3095
+ pageIndex: childObj.pageIndex,
3096
+ committed: false
3097
+ });
3098
+ }
3099
+ },
3100
+ metadata: { annotationIds: [id, ...irtChildren.map((c) => c.id)] }
2769
3101
  };
2770
3102
  const historyScope = this.history.forDocument(docId);
2771
3103
  historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
2772
3104
  }
3105
+ deleteAnnotationsMethod(annotations, documentId) {
3106
+ for (const { pageIndex, id } of annotations) {
3107
+ this.deleteAnnotation(pageIndex, id, documentId);
3108
+ }
3109
+ }
3110
+ purgeAnnotationMethod(pageIndex, id, documentId) {
3111
+ const docId = documentId ?? this.getActiveDocumentId();
3112
+ this.dispatch(purgeAnnotation(docId, pageIndex, id));
3113
+ }
2773
3114
  selectAnnotation(pageIndex, id, documentId) {
2774
3115
  const docId = documentId ?? this.getActiveDocumentId();
2775
- this.dispatch(selectAnnotation(docId, pageIndex, id));
3116
+ if (this.isInGroupMethod(id, docId)) {
3117
+ const members = this.getGroupMembersMethod(id, docId);
3118
+ const memberIds = members.map((m) => m.object.id);
3119
+ this.dispatch(setSelection(docId, memberIds));
3120
+ } else {
3121
+ this.dispatch(selectAnnotation(docId, pageIndex, id));
3122
+ }
2776
3123
  }
2777
3124
  deselectAnnotation(documentId) {
2778
3125
  const docId = documentId ?? this.getActiveDocumentId();
2779
3126
  this.dispatch(deselectAnnotation(docId));
2780
3127
  }
3128
+ // ─────────────────────────────────────────────────────────
3129
+ // Multi-Select Methods
3130
+ // ─────────────────────────────────────────────────────────
3131
+ getSelectedAnnotationsMethod(documentId) {
3132
+ return getSelectedAnnotations(this.getDocumentState(documentId));
3133
+ }
3134
+ getSelectedAnnotationIdsMethod(documentId) {
3135
+ return getSelectedAnnotationIds(this.getDocumentState(documentId));
3136
+ }
3137
+ toggleSelectionMethod(pageIndex, id, documentId) {
3138
+ const docId = documentId ?? this.getActiveDocumentId();
3139
+ const docState = this.getDocumentState(docId);
3140
+ if (docState.selectedUids.includes(id)) {
3141
+ this.dispatch(removeFromSelection(docId, id));
3142
+ } else {
3143
+ if (this.isInGroupMethod(id, docId)) {
3144
+ const members = this.getGroupMembersMethod(id, docId);
3145
+ for (const member of members) {
3146
+ if (!docState.selectedUids.includes(member.object.id)) {
3147
+ this.dispatch(addToSelection(docId, member.object.pageIndex, member.object.id));
3148
+ }
3149
+ }
3150
+ } else {
3151
+ this.dispatch(addToSelection(docId, pageIndex, id));
3152
+ }
3153
+ }
3154
+ }
3155
+ addToSelectionMethod(pageIndex, id, documentId) {
3156
+ const docId = documentId ?? this.getActiveDocumentId();
3157
+ this.dispatch(addToSelection(docId, pageIndex, id));
3158
+ }
3159
+ removeFromSelectionMethod(id, documentId) {
3160
+ const docId = documentId ?? this.getActiveDocumentId();
3161
+ this.dispatch(removeFromSelection(docId, id));
3162
+ }
3163
+ setSelectionMethod(ids, documentId) {
3164
+ const docId = documentId ?? this.getActiveDocumentId();
3165
+ this.dispatch(setSelection(docId, ids));
3166
+ }
3167
+ // ─────────────────────────────────────────────────────────
3168
+ // Attached Links Methods
3169
+ // ─────────────────────────────────────────────────────────
3170
+ getAttachedLinksMethod(annotationId, documentId) {
3171
+ return getAttachedLinks(this.getDocumentState(documentId), annotationId);
3172
+ }
3173
+ hasAttachedLinksMethod(annotationId, documentId) {
3174
+ return this.getAttachedLinksMethod(annotationId, documentId).length > 0;
3175
+ }
3176
+ deleteAttachedLinksMethod(annotationId, documentId) {
3177
+ const links = this.getAttachedLinksMethod(annotationId, documentId);
3178
+ for (const link of links) {
3179
+ this.deleteAnnotation(link.object.pageIndex, link.object.id, documentId);
3180
+ }
3181
+ }
3182
+ // ─────────────────────────────────────────────────────────
3183
+ // Annotation Grouping Methods
3184
+ // ─────────────────────────────────────────────────────────
3185
+ /**
3186
+ * Group the currently selected annotations.
3187
+ * The first selected annotation becomes the group leader.
3188
+ * All other selected annotations get their IRT set to the leader's ID with RT = Group.
3189
+ */
3190
+ groupAnnotationsMethod(documentId) {
3191
+ const docId = documentId ?? this.getActiveDocumentId();
3192
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3193
+ this.logger.debug(
3194
+ "AnnotationPlugin",
3195
+ "GroupAnnotations",
3196
+ `Cannot group annotations: document ${docId} lacks ModifyAnnotations permission`
3197
+ );
3198
+ return;
3199
+ }
3200
+ const selected = this.getSelectedAnnotationsMethod(docId);
3201
+ if (selected.length < 2) {
3202
+ this.logger.debug(
3203
+ "AnnotationPlugin",
3204
+ "GroupAnnotations",
3205
+ "Need at least 2 annotations to group"
3206
+ );
3207
+ return;
3208
+ }
3209
+ const leader = selected[0];
3210
+ const members = selected.slice(1);
3211
+ const patches = members.map((ta) => ({
3212
+ pageIndex: ta.object.pageIndex,
3213
+ id: ta.object.id,
3214
+ patch: {
3215
+ inReplyToId: leader.object.id,
3216
+ replyType: PdfAnnotationReplyType.Group
3217
+ }
3218
+ }));
3219
+ this.updateAnnotationsMethod(patches, docId);
3220
+ }
3221
+ /**
3222
+ * Ungroup all annotations in the group containing the specified annotation.
3223
+ * Clears IRT and RT from all group members (the leader doesn't have them).
3224
+ */
3225
+ ungroupAnnotationsMethod(annotationId, documentId) {
3226
+ const docId = documentId ?? this.getActiveDocumentId();
3227
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3228
+ this.logger.debug(
3229
+ "AnnotationPlugin",
3230
+ "UngroupAnnotations",
3231
+ `Cannot ungroup annotations: document ${docId} lacks ModifyAnnotations permission`
3232
+ );
3233
+ return;
3234
+ }
3235
+ const members = this.getGroupMembersMethod(annotationId, docId);
3236
+ const patches = members.filter((ta) => ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group).map((ta) => ({
3237
+ pageIndex: ta.object.pageIndex,
3238
+ id: ta.object.id,
3239
+ patch: {
3240
+ inReplyToId: void 0,
3241
+ replyType: void 0
3242
+ }
3243
+ }));
3244
+ if (patches.length > 0) {
3245
+ this.updateAnnotationsMethod(patches, docId);
3246
+ }
3247
+ }
3248
+ /**
3249
+ * Get all annotations in the same group as the specified annotation.
3250
+ */
3251
+ getGroupMembersMethod(annotationId, documentId) {
3252
+ return getGroupMembers(this.getDocumentState(documentId), annotationId);
3253
+ }
3254
+ /**
3255
+ * Check if an annotation is part of a group.
3256
+ */
3257
+ isInGroupMethod(annotationId, documentId) {
3258
+ return isInGroup(this.getDocumentState(documentId), annotationId);
3259
+ }
3260
+ /**
3261
+ * Get the available grouping action for the current selection.
3262
+ */
3263
+ getGroupingActionMethod(documentId) {
3264
+ return getSelectionGroupingAction(this.getDocumentState(documentId));
3265
+ }
3266
+ // ─────────────────────────────────────────────────────────
3267
+ // Multi-Drag Coordination (Internal API for framework components)
3268
+ // ─────────────────────────────────────────────────────────
3269
+ /**
3270
+ * Compute combined constraints from all selected annotations.
3271
+ * This finds the "weakest link" in each direction - the annotation with the least
3272
+ * room to move determines the group's limit.
3273
+ */
3274
+ computeCombinedConstraints(annotations) {
3275
+ let maxUp = Infinity;
3276
+ let maxDown = Infinity;
3277
+ let maxLeft = Infinity;
3278
+ let maxRight = Infinity;
3279
+ for (const anno of annotations) {
3280
+ const upLimit = anno.rect.origin.y;
3281
+ const downLimit = anno.pageSize.height - (anno.rect.origin.y + anno.rect.size.height);
3282
+ const leftLimit = anno.rect.origin.x;
3283
+ const rightLimit = anno.pageSize.width - (anno.rect.origin.x + anno.rect.size.width);
3284
+ maxUp = Math.min(maxUp, upLimit);
3285
+ maxDown = Math.min(maxDown, downLimit);
3286
+ maxLeft = Math.min(maxLeft, leftLimit);
3287
+ maxRight = Math.min(maxRight, rightLimit);
3288
+ }
3289
+ if (!isFinite(maxUp)) maxUp = 0;
3290
+ if (!isFinite(maxDown)) maxDown = 0;
3291
+ if (!isFinite(maxLeft)) maxLeft = 0;
3292
+ if (!isFinite(maxRight)) maxRight = 0;
3293
+ return { maxUp, maxDown, maxLeft, maxRight };
3294
+ }
3295
+ /**
3296
+ * Clamp a delta to the combined constraints.
3297
+ * Negative y = moving up, positive y = moving down
3298
+ * Negative x = moving left, positive x = moving right
3299
+ */
3300
+ clampDelta(rawDelta, constraints) {
3301
+ return {
3302
+ x: Math.max(-constraints.maxLeft, Math.min(constraints.maxRight, rawDelta.x)),
3303
+ y: Math.max(-constraints.maxUp, Math.min(constraints.maxDown, rawDelta.y))
3304
+ };
3305
+ }
3306
+ // ─────────────────────────────────────────────────────────
3307
+ // Unified Drag API (Plugin owns all logic - framework just calls these)
3308
+ // ─────────────────────────────────────────────────────────
3309
+ /**
3310
+ * Start a unified drag operation.
3311
+ * The plugin automatically expands the selection to include attached links.
3312
+ * Framework components should call this instead of building their own logic.
3313
+ *
3314
+ * @param documentId - The document ID
3315
+ * @param options - Drag options (annotationIds and pageSize)
3316
+ */
3317
+ startDrag(documentId, options) {
3318
+ const { annotationIds, pageSize } = options;
3319
+ const attachedLinkIds = [];
3320
+ for (const id of annotationIds) {
3321
+ const links = this.getAttachedLinksMethod(id, documentId);
3322
+ for (const link of links) {
3323
+ if (!attachedLinkIds.includes(link.object.id)) {
3324
+ attachedLinkIds.push(link.object.id);
3325
+ }
3326
+ }
3327
+ }
3328
+ const allParticipantIds = [...annotationIds, ...attachedLinkIds];
3329
+ const originalRects = /* @__PURE__ */ new Map();
3330
+ const constraints = [];
3331
+ for (const id of allParticipantIds) {
3332
+ const ta = this.getAnnotationById(id, documentId);
3333
+ if (ta) {
3334
+ originalRects.set(id, { ...ta.object.rect });
3335
+ constraints.push({
3336
+ id,
3337
+ rect: ta.object.rect,
3338
+ pageIndex: ta.object.pageIndex,
3339
+ pageSize
3340
+ });
3341
+ }
3342
+ }
3343
+ const combinedConstraints = this.computeCombinedConstraints(constraints);
3344
+ const state = {
3345
+ documentId,
3346
+ isDragging: true,
3347
+ primaryIds: annotationIds,
3348
+ attachedLinkIds,
3349
+ allParticipantIds,
3350
+ originalRects,
3351
+ delta: { x: 0, y: 0 },
3352
+ combinedConstraints
3353
+ };
3354
+ this.unifiedDragStates.set(documentId, state);
3355
+ this.unifiedDrag$.emit({ documentId, type: "start", state, previewPatches: {} });
3356
+ }
3357
+ /**
3358
+ * Compute preview patches for all drag participants.
3359
+ * Uses transformAnnotation to properly handle vertices, inkList, etc.
3360
+ */
3361
+ computeDragPreviewPatches(state, documentId) {
3362
+ const previewPatches = {};
3363
+ for (const id of state.allParticipantIds) {
3364
+ const ta = this.getAnnotationById(id, documentId);
3365
+ if (!ta) continue;
3366
+ const originalRect = state.originalRects.get(id);
3367
+ if (!originalRect) continue;
3368
+ const newRect = {
3369
+ ...originalRect,
3370
+ origin: {
3371
+ x: originalRect.origin.x + state.delta.x,
3372
+ y: originalRect.origin.y + state.delta.y
3373
+ }
3374
+ };
3375
+ previewPatches[id] = this.transformAnnotation(ta.object, {
3376
+ type: "move",
3377
+ changes: { rect: newRect }
3378
+ });
3379
+ }
3380
+ return previewPatches;
3381
+ }
3382
+ /**
3383
+ * Update the drag delta during a unified drag operation.
3384
+ * Returns the clamped delta synchronously for the caller's preview.
3385
+ *
3386
+ * @param documentId - The document ID
3387
+ * @param rawDelta - The unconstrained delta from the drag gesture
3388
+ * @returns The clamped delta
3389
+ */
3390
+ updateDrag(documentId, rawDelta) {
3391
+ const state = this.unifiedDragStates.get(documentId);
3392
+ if (!(state == null ? void 0 : state.isDragging)) {
3393
+ return { x: 0, y: 0 };
3394
+ }
3395
+ const clampedDelta = this.clampDelta(rawDelta, state.combinedConstraints);
3396
+ const newState = {
3397
+ ...state,
3398
+ delta: clampedDelta
3399
+ };
3400
+ this.unifiedDragStates.set(documentId, newState);
3401
+ const previewPatches = this.computeDragPreviewPatches(newState, documentId);
3402
+ this.unifiedDrag$.emit({ documentId, type: "update", state: newState, previewPatches });
3403
+ return clampedDelta;
3404
+ }
3405
+ /**
3406
+ * Commit the drag - plugin builds and applies ALL patches.
3407
+ * This is the key method that centralizes patch building in the plugin.
3408
+ *
3409
+ * @param documentId - The document ID
3410
+ */
3411
+ commitDrag(documentId) {
3412
+ const state = this.unifiedDragStates.get(documentId);
3413
+ if (!state) return;
3414
+ const finalDelta = state.delta;
3415
+ if (finalDelta.x !== 0 || finalDelta.y !== 0) {
3416
+ const patches = [];
3417
+ for (const id of state.allParticipantIds) {
3418
+ const ta = this.getAnnotationById(id, documentId);
3419
+ if (!ta) continue;
3420
+ const originalRect = state.originalRects.get(id) ?? ta.object.rect;
3421
+ const newRect = {
3422
+ ...originalRect,
3423
+ origin: {
3424
+ x: originalRect.origin.x + finalDelta.x,
3425
+ y: originalRect.origin.y + finalDelta.y
3426
+ }
3427
+ };
3428
+ const patch = this.transformAnnotation(ta.object, {
3429
+ type: "move",
3430
+ changes: { rect: newRect }
3431
+ });
3432
+ patches.push({ pageIndex: ta.object.pageIndex, id, patch });
3433
+ }
3434
+ if (patches.length > 0) {
3435
+ this.updateAnnotationsMethod(patches, documentId);
3436
+ }
3437
+ }
3438
+ const endPatches = this.computeDragPreviewPatches(state, documentId);
3439
+ this.unifiedDrag$.emit({
3440
+ documentId,
3441
+ type: "end",
3442
+ state: { ...state, isDragging: false },
3443
+ previewPatches: endPatches
3444
+ });
3445
+ this.unifiedDragStates.delete(documentId);
3446
+ }
3447
+ /**
3448
+ * Cancel the drag without committing.
3449
+ *
3450
+ * @param documentId - The document ID
3451
+ */
3452
+ cancelDrag(documentId) {
3453
+ const state = this.unifiedDragStates.get(documentId);
3454
+ if (!state) return;
3455
+ this.unifiedDrag$.emit({
3456
+ documentId,
3457
+ type: "cancel",
3458
+ state: { ...state, isDragging: false, delta: { x: 0, y: 0 } },
3459
+ previewPatches: {}
3460
+ });
3461
+ this.unifiedDragStates.delete(documentId);
3462
+ }
3463
+ /**
3464
+ * Get the current unified drag state for a document.
3465
+ */
3466
+ getDragState(documentId) {
3467
+ return this.unifiedDragStates.get(documentId) ?? null;
3468
+ }
3469
+ /**
3470
+ * Subscribe to unified drag state changes.
3471
+ * Framework components use this for preview updates.
3472
+ */
3473
+ get onDragChange() {
3474
+ return this.unifiedDrag$.on;
3475
+ }
3476
+ // ─────────────────────────────────────────────────────────
3477
+ // Unified Resize API (Plugin owns all logic - framework just calls these)
3478
+ // ─────────────────────────────────────────────────────────
3479
+ /**
3480
+ * Compute the union bounding box of multiple rects.
3481
+ */
3482
+ computeUnifiedGroupBoundingBox(rects) {
3483
+ if (rects.length === 0) {
3484
+ return { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } };
3485
+ }
3486
+ let minX = Infinity;
3487
+ let minY = Infinity;
3488
+ let maxX = -Infinity;
3489
+ let maxY = -Infinity;
3490
+ for (const rect of rects) {
3491
+ minX = Math.min(minX, rect.origin.x);
3492
+ minY = Math.min(minY, rect.origin.y);
3493
+ maxX = Math.max(maxX, rect.origin.x + rect.size.width);
3494
+ maxY = Math.max(maxY, rect.origin.y + rect.size.height);
3495
+ }
3496
+ return {
3497
+ origin: { x: minX, y: minY },
3498
+ size: { width: maxX - minX, height: maxY - minY }
3499
+ };
3500
+ }
3501
+ /**
3502
+ * Compute relative positions for annotations within a group bounding box.
3503
+ */
3504
+ computeUnifiedRelativePositions(annotations, groupBox) {
3505
+ return annotations.map((anno) => ({
3506
+ id: anno.id,
3507
+ originalRect: anno.rect,
3508
+ pageIndex: anno.pageIndex,
3509
+ isAttachedLink: anno.isAttachedLink,
3510
+ parentId: anno.parentId,
3511
+ relativeX: groupBox.size.width > 0 ? (anno.rect.origin.x - groupBox.origin.x) / groupBox.size.width : 0,
3512
+ relativeY: groupBox.size.height > 0 ? (anno.rect.origin.y - groupBox.origin.y) / groupBox.size.height : 0,
3513
+ relativeWidth: groupBox.size.width > 0 ? anno.rect.size.width / groupBox.size.width : 1,
3514
+ relativeHeight: groupBox.size.height > 0 ? anno.rect.size.height / groupBox.size.height : 1
3515
+ }));
3516
+ }
3517
+ /**
3518
+ * Compute new rects for all annotations based on the new group bounding box.
3519
+ */
3520
+ computeUnifiedResizedRects(participatingAnnotations, newGroupBox, minSize = 10) {
3521
+ const result = /* @__PURE__ */ new Map();
3522
+ for (const anno of participatingAnnotations) {
3523
+ const newWidth = Math.max(minSize, anno.relativeWidth * newGroupBox.size.width);
3524
+ const newHeight = Math.max(minSize, anno.relativeHeight * newGroupBox.size.height);
3525
+ result.set(anno.id, {
3526
+ origin: {
3527
+ x: newGroupBox.origin.x + anno.relativeX * newGroupBox.size.width,
3528
+ y: newGroupBox.origin.y + anno.relativeY * newGroupBox.size.height
3529
+ },
3530
+ size: {
3531
+ width: newWidth,
3532
+ height: newHeight
3533
+ }
3534
+ });
3535
+ }
3536
+ return result;
3537
+ }
3538
+ /**
3539
+ * Compute preview patches for all resize participants.
3540
+ * Uses transformAnnotation to properly handle vertices, inkList, etc.
3541
+ */
3542
+ computeResizePreviewPatches(computedRects, documentId) {
3543
+ const previewPatches = {};
3544
+ for (const [id, newRect] of computedRects) {
3545
+ const ta = this.getAnnotationById(id, documentId);
3546
+ if (!ta) continue;
3547
+ previewPatches[id] = this.transformAnnotation(ta.object, {
3548
+ type: "resize",
3549
+ changes: { rect: newRect }
3550
+ });
3551
+ }
3552
+ return previewPatches;
3553
+ }
3554
+ /**
3555
+ * Start a unified resize operation.
3556
+ * The plugin automatically expands the selection to include attached links.
3557
+ *
3558
+ * @param documentId - The document ID
3559
+ * @param options - Resize options
3560
+ */
3561
+ startResize(documentId, options) {
3562
+ const { annotationIds, pageSize, resizeHandle } = options;
3563
+ const attachedLinkIds = [];
3564
+ const annotationsWithLinks = [];
3565
+ for (const id of annotationIds) {
3566
+ const ta = this.getAnnotationById(id, documentId);
3567
+ if (ta) {
3568
+ annotationsWithLinks.push({
3569
+ id,
3570
+ rect: ta.object.rect,
3571
+ pageIndex: ta.object.pageIndex,
3572
+ isAttachedLink: false
3573
+ });
3574
+ const links = this.getAttachedLinksMethod(id, documentId);
3575
+ for (const link of links) {
3576
+ if (!attachedLinkIds.includes(link.object.id)) {
3577
+ attachedLinkIds.push(link.object.id);
3578
+ annotationsWithLinks.push({
3579
+ id: link.object.id,
3580
+ rect: link.object.rect,
3581
+ pageIndex: link.object.pageIndex,
3582
+ isAttachedLink: true,
3583
+ parentId: id
3584
+ });
3585
+ }
3586
+ }
3587
+ }
3588
+ }
3589
+ const allParticipantIds = [...annotationIds, ...attachedLinkIds];
3590
+ const rects = annotationsWithLinks.map((a) => a.rect);
3591
+ const groupBox = this.computeUnifiedGroupBoundingBox(rects);
3592
+ const participatingAnnotations = this.computeUnifiedRelativePositions(
3593
+ annotationsWithLinks,
3594
+ groupBox
3595
+ );
3596
+ const computedRects = this.computeUnifiedResizedRects(participatingAnnotations, groupBox);
3597
+ const state = {
3598
+ documentId,
3599
+ isResizing: true,
3600
+ primaryIds: annotationIds,
3601
+ attachedLinkIds,
3602
+ allParticipantIds,
3603
+ originalGroupBox: groupBox,
3604
+ currentGroupBox: groupBox,
3605
+ participatingAnnotations,
3606
+ resizeHandle,
3607
+ computedRects
3608
+ };
3609
+ this.unifiedResizeStates.set(documentId, state);
3610
+ const startPatches = this.computeResizePreviewPatches(computedRects, documentId);
3611
+ this.unifiedResize$.emit({
3612
+ documentId,
3613
+ type: "start",
3614
+ state,
3615
+ computedRects: Object.fromEntries(computedRects),
3616
+ previewPatches: startPatches
3617
+ });
3618
+ }
3619
+ /**
3620
+ * Update the resize with a new group bounding box.
3621
+ * Returns the computed rects synchronously for immediate preview use.
3622
+ *
3623
+ * @param documentId - The document ID
3624
+ * @param newGroupBox - The new group bounding box
3625
+ * @returns Record of annotation ID to new rect
3626
+ */
3627
+ updateResize(documentId, newGroupBox) {
3628
+ const state = this.unifiedResizeStates.get(documentId);
3629
+ if (!(state == null ? void 0 : state.isResizing)) {
3630
+ return {};
3631
+ }
3632
+ const computedRects = this.computeUnifiedResizedRects(
3633
+ state.participatingAnnotations,
3634
+ newGroupBox
3635
+ );
3636
+ const newState = {
3637
+ ...state,
3638
+ currentGroupBox: newGroupBox,
3639
+ computedRects
3640
+ };
3641
+ this.unifiedResizeStates.set(documentId, newState);
3642
+ const computedRectsObj = Object.fromEntries(computedRects);
3643
+ const previewPatches = this.computeResizePreviewPatches(computedRects, documentId);
3644
+ this.unifiedResize$.emit({
3645
+ documentId,
3646
+ type: "update",
3647
+ state: newState,
3648
+ computedRects: computedRectsObj,
3649
+ previewPatches
3650
+ });
3651
+ return computedRectsObj;
3652
+ }
3653
+ /**
3654
+ * Commit the resize - plugin builds and applies ALL patches.
3655
+ *
3656
+ * @param documentId - The document ID
3657
+ */
3658
+ commitResize(documentId) {
3659
+ const state = this.unifiedResizeStates.get(documentId);
3660
+ if (!state) return;
3661
+ const computedRects = this.computeUnifiedResizedRects(
3662
+ state.participatingAnnotations,
3663
+ state.currentGroupBox
3664
+ );
3665
+ const patches = [];
3666
+ for (const [id, newRect] of computedRects) {
3667
+ const ta = this.getAnnotationById(id, documentId);
3668
+ if (!ta) continue;
3669
+ const patch = this.transformAnnotation(ta.object, {
3670
+ type: "resize",
3671
+ changes: { rect: newRect }
3672
+ });
3673
+ patches.push({ pageIndex: ta.object.pageIndex, id, patch });
3674
+ }
3675
+ if (patches.length > 0) {
3676
+ this.updateAnnotationsMethod(patches, documentId);
3677
+ }
3678
+ const endPatches = this.computeResizePreviewPatches(computedRects, documentId);
3679
+ this.unifiedResize$.emit({
3680
+ documentId,
3681
+ type: "end",
3682
+ state: { ...state, isResizing: false },
3683
+ computedRects: Object.fromEntries(computedRects),
3684
+ previewPatches: endPatches
3685
+ });
3686
+ this.unifiedResizeStates.delete(documentId);
3687
+ }
3688
+ /**
3689
+ * Cancel the resize without committing.
3690
+ *
3691
+ * @param documentId - The document ID
3692
+ */
3693
+ cancelResize(documentId) {
3694
+ const state = this.unifiedResizeStates.get(documentId);
3695
+ if (!state) return;
3696
+ const originalRects = this.computeUnifiedResizedRects(
3697
+ state.participatingAnnotations,
3698
+ state.originalGroupBox
3699
+ );
3700
+ this.unifiedResize$.emit({
3701
+ documentId,
3702
+ type: "cancel",
3703
+ state: { ...state, isResizing: false, currentGroupBox: state.originalGroupBox },
3704
+ computedRects: Object.fromEntries(originalRects),
3705
+ previewPatches: {}
3706
+ });
3707
+ this.unifiedResizeStates.delete(documentId);
3708
+ }
3709
+ /**
3710
+ * Get the current unified resize state for a document.
3711
+ */
3712
+ getResizeState(documentId) {
3713
+ return this.unifiedResizeStates.get(documentId) ?? null;
3714
+ }
3715
+ /**
3716
+ * Subscribe to unified resize state changes.
3717
+ * Framework components use this for preview updates.
3718
+ */
3719
+ get onResizeChange() {
3720
+ return this.unifiedResize$.on;
3721
+ }
3722
+ updateAnnotationsMethod(patches, documentId) {
3723
+ const docId = documentId ?? this.getActiveDocumentId();
3724
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3725
+ this.logger.debug(
3726
+ "AnnotationPlugin",
3727
+ "UpdateAnnotations",
3728
+ `Cannot update annotations: document ${docId} lacks ModifyAnnotations permission`
3729
+ );
3730
+ return;
3731
+ }
3732
+ const docState = this.getDocumentState(docId);
3733
+ const patchData = patches.map(({ pageIndex, id, patch }) => {
3734
+ var _a;
3735
+ const originalObject = (_a = docState.byUid[id]) == null ? void 0 : _a.object;
3736
+ if (!originalObject) return null;
3737
+ const finalPatch = this.buildPatch(originalObject, {
3738
+ ...patch,
3739
+ author: patch.author ?? this.config.annotationAuthor
3740
+ });
3741
+ return { pageIndex, id, patch: finalPatch, originalObject };
3742
+ }).filter((p) => p !== null);
3743
+ if (patchData.length === 0) return;
3744
+ const execute = () => {
3745
+ for (const { pageIndex, id, patch, originalObject } of patchData) {
3746
+ this.dispatch(patchAnnotation(docId, pageIndex, id, patch));
3747
+ this.events$.emit({
3748
+ type: "update",
3749
+ documentId: docId,
3750
+ annotation: originalObject,
3751
+ pageIndex,
3752
+ patch,
3753
+ committed: false
3754
+ });
3755
+ }
3756
+ };
3757
+ if (!this.history) {
3758
+ execute();
3759
+ if (this.config.autoCommit !== false) {
3760
+ this.commit(docId);
3761
+ }
3762
+ return;
3763
+ }
3764
+ const undoData = patchData.map(({ pageIndex, id, patch, originalObject }) => ({
3765
+ pageIndex,
3766
+ id,
3767
+ originalPatch: Object.fromEntries(
3768
+ Object.keys(patch).map((key) => [key, originalObject[key]])
3769
+ ),
3770
+ originalObject
3771
+ }));
3772
+ const command = {
3773
+ execute,
3774
+ undo: () => {
3775
+ for (const { pageIndex, id, originalPatch, originalObject } of undoData) {
3776
+ this.dispatch(patchAnnotation(docId, pageIndex, id, originalPatch));
3777
+ this.events$.emit({
3778
+ type: "update",
3779
+ documentId: docId,
3780
+ annotation: originalObject,
3781
+ pageIndex,
3782
+ patch: originalPatch,
3783
+ committed: false
3784
+ });
3785
+ }
3786
+ },
3787
+ metadata: { annotationIds: patchData.map((p) => p.id) }
3788
+ };
3789
+ const historyScope = this.history.forDocument(docId);
3790
+ historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
3791
+ }
2781
3792
  getActiveTool(documentId) {
2782
3793
  const docState = this.getDocumentState(documentId);
2783
3794
  if (!docState.activeToolId) return null;
@@ -2819,87 +3830,185 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2819
3830
  }
2820
3831
  return bestTool;
2821
3832
  }
2822
- commit(documentId) {
2823
- const task = new Task();
2824
- const docId = documentId ?? this.getActiveDocumentId();
3833
+ /**
3834
+ * Collects all pending annotation changes for a document into a batch.
3835
+ * This separates the "what to commit" from "how to commit" for cleaner code.
3836
+ */
3837
+ collectPendingChanges(docId, doc) {
2825
3838
  const docState = this.getDocumentState(docId);
2826
- if (!docState.hasPendingChanges) return PdfTaskHelper.resolve(true);
2827
- const coreDocState = this.getCoreDocument(docId);
2828
- const doc = coreDocState == null ? void 0 : coreDocState.document;
2829
- if (!doc)
2830
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
2831
3839
  const contexts = this.pendingContexts.get(docId);
2832
- if (!contexts) return PdfTaskHelper.resolve(true);
2833
- const creations = [];
2834
- const updates = [];
2835
- const deletions = [];
3840
+ const batch = {
3841
+ creations: [],
3842
+ updates: [],
3843
+ deletions: [],
3844
+ committedUids: [],
3845
+ isEmpty: true
3846
+ };
2836
3847
  for (const [uid, ta] of Object.entries(docState.byUid)) {
2837
3848
  if (ta.commitState === "synced") continue;
2838
3849
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2839
3850
  if (!page) continue;
3851
+ batch.committedUids.push(uid);
3852
+ batch.isEmpty = false;
2840
3853
  switch (ta.commitState) {
2841
3854
  case "new":
2842
- const ctx = contexts.get(ta.object.id);
2843
- const task2 = this.engine.createPageAnnotation(doc, page, ta.object, ctx);
2844
- task2.wait(() => {
2845
- this.events$.emit({
2846
- type: "create",
2847
- documentId: docId,
2848
- annotation: ta.object,
2849
- pageIndex: ta.object.pageIndex,
2850
- ctx,
2851
- committed: true
2852
- });
2853
- contexts.delete(ta.object.id);
2854
- }, ignore);
2855
- creations.push(task2);
3855
+ batch.creations.push({
3856
+ uid,
3857
+ ta,
3858
+ ctx: contexts == null ? void 0 : contexts.get(ta.object.id)
3859
+ });
2856
3860
  break;
2857
3861
  case "dirty":
2858
- const updateTask = this.engine.updatePageAnnotation(doc, page, ta.object);
2859
- updateTask.wait(() => {
2860
- this.events$.emit({
2861
- type: "update",
2862
- documentId: docId,
2863
- annotation: ta.object,
2864
- pageIndex: ta.object.pageIndex,
2865
- patch: ta.object,
2866
- committed: true
2867
- });
2868
- }, ignore);
2869
- updates.push(updateTask);
3862
+ batch.updates.push({ uid, ta });
2870
3863
  break;
2871
3864
  case "deleted":
2872
- deletions.push({ ta, uid });
3865
+ batch.deletions.push({ uid, ta });
2873
3866
  break;
2874
3867
  }
2875
3868
  }
2876
- const deletionTasks = [];
2877
- for (const { ta, uid } of deletions) {
3869
+ return batch;
3870
+ }
3871
+ /**
3872
+ * Executes a batch of pending changes by creating engine tasks.
3873
+ * Returns a task that resolves when all operations complete.
3874
+ */
3875
+ executeCommitBatch(docId, doc, batch) {
3876
+ const task = new Task();
3877
+ const contexts = this.pendingContexts.get(docId);
3878
+ const pendingOps = [];
3879
+ for (const { uid, ta, ctx } of batch.creations) {
2878
3880
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2879
- if (ta.commitState === "deleted" && ta.object.id) {
2880
- const task2 = new Task();
3881
+ if (!page) continue;
3882
+ const createTask = this.engine.createPageAnnotation(doc, page, ta.object, ctx);
3883
+ pendingOps.push({ type: "create", task: createTask, ta, uid, ctx });
3884
+ }
3885
+ for (const { uid, ta } of batch.updates) {
3886
+ const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
3887
+ if (!page) continue;
3888
+ const updateTask = this.engine.updatePageAnnotation(doc, page, ta.object);
3889
+ pendingOps.push({ type: "update", task: updateTask, ta, uid });
3890
+ }
3891
+ for (const { uid, ta } of batch.deletions) {
3892
+ const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
3893
+ if (!page) continue;
3894
+ if (ta.object.id) {
3895
+ const deleteTask = new Task();
2881
3896
  const removeTask = this.engine.removePageAnnotation(doc, page, ta.object);
2882
- removeTask.wait(() => {
2883
- this.dispatch(purgeAnnotation(docId, uid));
3897
+ removeTask.wait(() => deleteTask.resolve(true), deleteTask.fail);
3898
+ pendingOps.push({ type: "delete", task: deleteTask, ta, uid });
3899
+ } else {
3900
+ this.dispatch(purgeAnnotation(docId, ta.object.pageIndex, uid));
3901
+ }
3902
+ }
3903
+ const allTasks = pendingOps.map((op) => op.task);
3904
+ Task.allSettled(allTasks).wait(
3905
+ () => {
3906
+ this.emitCommitEvents(docId, pendingOps, contexts);
3907
+ this.dispatch(commitPendingChanges(docId, batch.committedUids));
3908
+ task.resolve(true);
3909
+ },
3910
+ (error) => task.fail(error)
3911
+ );
3912
+ return task;
3913
+ }
3914
+ /**
3915
+ * Emits commit events for all completed operations.
3916
+ * Centralizes event emission for cleaner separation of concerns.
3917
+ */
3918
+ emitCommitEvents(docId, operations, contexts) {
3919
+ for (const op of operations) {
3920
+ if (op.task.state.stage !== TaskStage.Resolved) continue;
3921
+ switch (op.type) {
3922
+ case "create":
3923
+ this.events$.emit({
3924
+ type: "create",
3925
+ documentId: docId,
3926
+ annotation: op.ta.object,
3927
+ pageIndex: op.ta.object.pageIndex,
3928
+ ctx: op.ctx,
3929
+ committed: true
3930
+ });
3931
+ contexts == null ? void 0 : contexts.delete(op.ta.object.id);
3932
+ break;
3933
+ case "update":
3934
+ this.events$.emit({
3935
+ type: "update",
3936
+ documentId: docId,
3937
+ annotation: op.ta.object,
3938
+ pageIndex: op.ta.object.pageIndex,
3939
+ patch: op.ta.object,
3940
+ committed: true
3941
+ });
3942
+ break;
3943
+ case "delete":
3944
+ this.dispatch(purgeAnnotation(docId, op.ta.object.pageIndex, op.uid));
2884
3945
  this.events$.emit({
2885
3946
  type: "delete",
2886
3947
  documentId: docId,
2887
- annotation: ta.object,
2888
- pageIndex: ta.object.pageIndex,
3948
+ annotation: op.ta.object,
3949
+ pageIndex: op.ta.object.pageIndex,
2889
3950
  committed: true
2890
3951
  });
2891
- task2.resolve(true);
2892
- }, task2.fail);
2893
- deletionTasks.push(task2);
2894
- } else {
2895
- this.dispatch(purgeAnnotation(docId, uid));
3952
+ break;
2896
3953
  }
2897
3954
  }
2898
- const allWriteTasks = [...creations, ...updates, ...deletionTasks];
2899
- Task.allSettled(allWriteTasks).wait(() => {
2900
- this.dispatch(commitPendingChanges(docId));
2901
- task.resolve(true);
2902
- }, task.fail);
3955
+ }
3956
+ /**
3957
+ * Attempts to acquire the commit lock for a document.
3958
+ * Returns true if acquired, false if a commit is already in progress.
3959
+ */
3960
+ acquireCommitLock(docId) {
3961
+ if (this.commitInProgress.get(docId)) {
3962
+ return false;
3963
+ }
3964
+ this.commitInProgress.set(docId, true);
3965
+ return true;
3966
+ }
3967
+ /**
3968
+ * Releases the commit lock for a document.
3969
+ */
3970
+ releaseCommitLock(docId) {
3971
+ this.commitInProgress.set(docId, false);
3972
+ }
3973
+ commit(documentId) {
3974
+ const docId = documentId ?? this.getActiveDocumentId();
3975
+ const docState = this.getDocumentState(docId);
3976
+ if (!docState.hasPendingChanges) {
3977
+ return PdfTaskHelper.resolve(true);
3978
+ }
3979
+ if (!this.acquireCommitLock(docId)) {
3980
+ return PdfTaskHelper.resolve(true);
3981
+ }
3982
+ const coreDocState = this.getCoreDocument(docId);
3983
+ const doc = coreDocState == null ? void 0 : coreDocState.document;
3984
+ if (!doc) {
3985
+ this.releaseCommitLock(docId);
3986
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
3987
+ }
3988
+ const batch = this.collectPendingChanges(docId, doc);
3989
+ if (batch.isEmpty) {
3990
+ this.releaseCommitLock(docId);
3991
+ return PdfTaskHelper.resolve(true);
3992
+ }
3993
+ const task = new Task();
3994
+ this.executeCommitBatch(docId, doc, batch).wait(
3995
+ () => {
3996
+ this.releaseCommitLock(docId);
3997
+ const updatedDocState = this.getDocumentState(docId);
3998
+ if (updatedDocState.hasPendingChanges) {
3999
+ this.commit(docId).wait(
4000
+ (result) => task.resolve(result),
4001
+ (error) => task.fail(error)
4002
+ );
4003
+ } else {
4004
+ task.resolve(true);
4005
+ }
4006
+ },
4007
+ (error) => {
4008
+ this.releaseCommitLock(docId);
4009
+ task.fail(error);
4010
+ }
4011
+ );
2903
4012
  return task;
2904
4013
  }
2905
4014
  /**
@@ -2916,6 +4025,11 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2916
4025
  };
2917
4026
  _AnnotationPlugin.id = "annotation";
2918
4027
  let AnnotationPlugin = _AnnotationPlugin;
4028
+ function resolveInteractionProp(prop, annotation, defaultValue) {
4029
+ if (prop === void 0) return defaultValue;
4030
+ if (typeof prop === "function") return prop(annotation);
4031
+ return prop;
4032
+ }
2919
4033
  function createToolPredicate(id) {
2920
4034
  return (tool) => {
2921
4035
  return (tool == null ? void 0 : tool.id) === id;
@@ -2948,11 +4062,23 @@ export {
2948
4062
  getAnnotationByUid,
2949
4063
  getAnnotations,
2950
4064
  getAnnotationsByPageIndex,
4065
+ getAttachedLinks,
4066
+ getGroupLeaderId,
4067
+ getGroupMembers,
4068
+ getIRTChildIds,
4069
+ getIRTChildrenByType,
2951
4070
  getSelectedAnnotation,
2952
4071
  getSelectedAnnotationByPageIndex,
4072
+ getSelectedAnnotationIds,
4073
+ getSelectedAnnotations,
4074
+ getSelectedAnnotationsByPageIndex,
4075
+ getSelectionGroupingAction,
2953
4076
  getSidebarAnnotationsWithReplies,
2954
4077
  getSidebarAnnotationsWithRepliesGroupedByPage,
2955
4078
  getToolDefaultsById,
4079
+ hasAttachedLinks,
4080
+ hasIRTChildren,
4081
+ hasMultipleSelected,
2956
4082
  initialDocumentState,
2957
4083
  initialState,
2958
4084
  isAnnotationSelected,
@@ -2962,15 +4088,18 @@ export {
2962
4088
  isFreeTextTool,
2963
4089
  isHighlight,
2964
4090
  isHighlightTool,
4091
+ isInGroup,
2965
4092
  isInk,
2966
4093
  isInkHighlighterTool,
2967
4094
  isInkTool,
2968
4095
  isLine,
2969
4096
  isLineTool,
4097
+ isLink,
2970
4098
  isPolygon,
2971
4099
  isPolygonTool,
2972
4100
  isPolyline,
2973
4101
  isPolylineTool,
4102
+ isRedact,
2974
4103
  isSidebarAnnotation,
2975
4104
  isSquare,
2976
4105
  isSquareTool,
@@ -2985,6 +4114,8 @@ export {
2985
4114
  isUnderline,
2986
4115
  isUnderlineTool,
2987
4116
  manifest,
2988
- index as patching
4117
+ index as patching,
4118
+ rectsIntersect,
4119
+ resolveInteractionProp
2989
4120
  };
2990
4121
  //# sourceMappingURL=index.js.map