@embedpdf/plugin-annotation 2.1.2 → 2.3.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 (97) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1232 -93
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/actions.d.ts +31 -2
  6. package/dist/lib/annotation-plugin.d.ts +177 -2
  7. package/dist/lib/handlers/types.d.ts +1 -1
  8. package/dist/lib/helpers.d.ts +6 -1
  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 +7 -1
  12. package/dist/lib/types.d.ts +265 -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 +867 -406
  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 +867 -406
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/shared/components/annotation-container.d.ts +8 -1
  24. package/dist/shared/components/annotation-layer.d.ts +4 -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 +2 -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 +7 -0
  35. package/dist/shared-preact/components/annotation-container.d.ts +8 -1
  36. package/dist/shared-preact/components/annotation-layer.d.ts +4 -2
  37. package/dist/shared-preact/components/annotations/ink.d.ts +3 -3
  38. package/dist/shared-preact/components/annotations/link.d.ts +28 -0
  39. package/dist/shared-preact/components/annotations.d.ts +2 -1
  40. package/dist/shared-preact/components/group-selection-box.d.ts +32 -0
  41. package/dist/shared-preact/components/index.d.ts +1 -0
  42. package/dist/shared-preact/components/text-markup/highlight.d.ts +3 -2
  43. package/dist/shared-preact/components/text-markup/squiggly.d.ts +3 -2
  44. package/dist/shared-preact/components/text-markup/strikeout.d.ts +3 -2
  45. package/dist/shared-preact/components/text-markup/underline.d.ts +3 -2
  46. package/dist/shared-preact/components/types.d.ts +7 -0
  47. package/dist/shared-react/components/annotation-container.d.ts +8 -1
  48. package/dist/shared-react/components/annotation-layer.d.ts +4 -2
  49. package/dist/shared-react/components/annotations/ink.d.ts +3 -3
  50. package/dist/shared-react/components/annotations/link.d.ts +28 -0
  51. package/dist/shared-react/components/annotations.d.ts +2 -1
  52. package/dist/shared-react/components/group-selection-box.d.ts +32 -0
  53. package/dist/shared-react/components/index.d.ts +1 -0
  54. package/dist/shared-react/components/text-markup/highlight.d.ts +3 -2
  55. package/dist/shared-react/components/text-markup/squiggly.d.ts +3 -2
  56. package/dist/shared-react/components/text-markup/strikeout.d.ts +3 -2
  57. package/dist/shared-react/components/text-markup/underline.d.ts +3 -2
  58. package/dist/shared-react/components/types.d.ts +7 -0
  59. package/dist/svelte/components/AnnotationLayer.svelte.d.ts +5 -1
  60. package/dist/svelte/components/Annotations.svelte.d.ts +5 -1
  61. package/dist/svelte/components/GroupSelectionBox.svelte.d.ts +32 -0
  62. package/dist/svelte/components/annotations/Ink.svelte.d.ts +2 -1
  63. package/dist/svelte/components/annotations/Link.svelte.d.ts +24 -0
  64. package/dist/svelte/components/annotations/index.d.ts +1 -0
  65. package/dist/svelte/components/index.d.ts +1 -0
  66. package/dist/svelte/components/text-markup/Highlight.svelte.d.ts +2 -1
  67. package/dist/svelte/components/text-markup/Squiggly.svelte.d.ts +2 -1
  68. package/dist/svelte/components/text-markup/Strikeout.svelte.d.ts +2 -1
  69. package/dist/svelte/components/text-markup/Underline.svelte.d.ts +2 -1
  70. package/dist/svelte/components/types.d.ts +2 -0
  71. package/dist/svelte/index.cjs +1 -1
  72. package/dist/svelte/index.cjs.map +1 -1
  73. package/dist/svelte/index.js +912 -258
  74. package/dist/svelte/index.js.map +1 -1
  75. package/dist/svelte/types.d.ts +7 -0
  76. package/dist/vue/components/annotation-container.vue.d.ts +2 -0
  77. package/dist/vue/components/annotation-layer.vue.d.ts +28 -5
  78. package/dist/vue/components/annotations/index.d.ts +1 -0
  79. package/dist/vue/components/annotations/ink.vue.d.ts +2 -2
  80. package/dist/vue/components/annotations/line.vue.d.ts +1 -1
  81. package/dist/vue/components/annotations/link.vue.d.ts +29 -0
  82. package/dist/vue/components/annotations/polygon.vue.d.ts +1 -1
  83. package/dist/vue/components/annotations/polyline.vue.d.ts +1 -1
  84. package/dist/vue/components/annotations.vue.d.ts +65 -1
  85. package/dist/vue/components/group-selection-box.vue.d.ts +73 -0
  86. package/dist/vue/components/index.d.ts +1 -0
  87. package/dist/vue/components/text-markup/highlight.vue.d.ts +2 -2
  88. package/dist/vue/components/text-markup/squiggly.vue.d.ts +2 -2
  89. package/dist/vue/components/text-markup/strikeout.vue.d.ts +2 -2
  90. package/dist/vue/components/text-markup/underline.vue.d.ts +2 -2
  91. package/dist/vue/hooks/use-annotation.d.ts +2 -0
  92. package/dist/vue/index.cjs +1 -1
  93. package/dist/vue/index.cjs.map +1 -1
  94. package/dist/vue/index.js +962 -409
  95. package/dist/vue/index.js.map +1 -1
  96. package/dist/vue/types.d.ts +7 -0
  97. 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, 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,9 +79,9 @@ 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
86
  const purgeAnnotation = (documentId, uid) => ({
72
87
  type: PURGE_ANNOTATION,
@@ -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,6 +144,9 @@ 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
+ }
129
150
  function isSidebarAnnotation(a) {
130
151
  return isTextMarkup(a) || isInk(a) || isSquare(a) || isCircle(a) || isPolygon(a) || isLine(a) || isPolyline(a) || isFreeText(a) || isStamp(a);
131
152
  }
@@ -135,17 +156,25 @@ const getAnnotations = (s) => {
135
156
  for (const p of Object.keys(s.pages).map(Number)) out[p] = getAnnotationsByPageIndex(s, p);
136
157
  return out;
137
158
  };
138
- const getSelectedAnnotation = (s) => s.selectedUid ? s.byUid[s.selectedUid] : null;
159
+ const getSelectedAnnotation = (s) => s.selectedUids.length > 0 ? s.byUid[s.selectedUids[0]] ?? null : null;
160
+ const getSelectedAnnotations = (s) => s.selectedUids.map((uid) => s.byUid[uid]).filter((ta) => ta !== void 0);
161
+ const getSelectedAnnotationIds = (s) => s.selectedUids;
139
162
  const getAnnotationByUid = (s, uid) => s.byUid[uid] ?? null;
140
163
  const getSelectedAnnotationByPageIndex = (s, pageIndex) => {
141
- if (!s.selectedUid) return null;
142
164
  const pageUids = s.pages[pageIndex] ?? [];
143
- if (pageUids.includes(s.selectedUid)) {
144
- return s.byUid[s.selectedUid];
165
+ for (const uid of s.selectedUids) {
166
+ if (pageUids.includes(uid)) {
167
+ return s.byUid[uid] ?? null;
168
+ }
145
169
  }
146
170
  return null;
147
171
  };
148
- const isAnnotationSelected = (s, id) => s.selectedUid === id;
172
+ const getSelectedAnnotationsByPageIndex = (s, pageIndex) => {
173
+ const pageUids = new Set(s.pages[pageIndex] ?? []);
174
+ return s.selectedUids.filter((uid) => pageUids.has(uid)).map((uid) => s.byUid[uid]).filter((ta) => ta !== void 0);
175
+ };
176
+ const isAnnotationSelected = (s, id) => s.selectedUids.includes(id);
177
+ const hasMultipleSelected = (s) => s.selectedUids.length > 1;
149
178
  function getToolDefaultsById(state, toolId) {
150
179
  const tool = state.tools.find((t) => t.id === toolId);
151
180
  return tool == null ? void 0 : tool.defaults;
@@ -190,6 +219,101 @@ const getSidebarAnnotationsWithReplies = (s) => {
190
219
  }
191
220
  return out;
192
221
  };
222
+ const getIRTChildIds = (s, parentId) => {
223
+ const children = [];
224
+ for (const uidList of Object.values(s.pages)) {
225
+ for (const uid of uidList) {
226
+ const ta = s.byUid[uid];
227
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId) {
228
+ children.push({ id: ta.object.id, pageIndex: ta.object.pageIndex });
229
+ }
230
+ }
231
+ }
232
+ return children;
233
+ };
234
+ const hasIRTChildren = (s, parentId) => {
235
+ for (const uidList of Object.values(s.pages)) {
236
+ for (const uid of uidList) {
237
+ const ta = s.byUid[uid];
238
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId) {
239
+ return true;
240
+ }
241
+ }
242
+ }
243
+ return false;
244
+ };
245
+ const getIRTChildrenByType = (s, parentId, types) => {
246
+ const children = [];
247
+ for (const uidList of Object.values(s.pages)) {
248
+ for (const uid of uidList) {
249
+ const ta = s.byUid[uid];
250
+ if (ta && "inReplyToId" in ta.object && ta.object.inReplyToId === parentId && types.includes(ta.object.type)) {
251
+ children.push(ta);
252
+ }
253
+ }
254
+ }
255
+ return children;
256
+ };
257
+ const getAttachedLinks = (s, parentId) => getIRTChildrenByType(s, parentId, [PdfAnnotationSubtype.LINK]);
258
+ const hasAttachedLinks = (s, parentId) => getAttachedLinks(s, parentId).length > 0;
259
+ const getGroupLeaderId = (s, annotationId) => {
260
+ const ta = s.byUid[annotationId];
261
+ if (!ta) return void 0;
262
+ if (ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group) {
263
+ return ta.object.inReplyToId;
264
+ }
265
+ return annotationId;
266
+ };
267
+ const getGroupMembers = (s, annotationId) => {
268
+ const leaderId = getGroupLeaderId(s, annotationId);
269
+ if (!leaderId) return [];
270
+ const members = [];
271
+ const leader = s.byUid[leaderId];
272
+ if (leader && leader.object.type !== PdfAnnotationSubtype.LINK) {
273
+ members.push(leader);
274
+ }
275
+ for (const uidList of Object.values(s.pages)) {
276
+ for (const uid of uidList) {
277
+ const ta = s.byUid[uid];
278
+ if (ta && ta.object.inReplyToId === leaderId && ta.object.replyType === PdfAnnotationReplyType.Group && ta.object.type !== PdfAnnotationSubtype.LINK) {
279
+ members.push(ta);
280
+ }
281
+ }
282
+ }
283
+ return members;
284
+ };
285
+ const isInGroup = (s, annotationId) => {
286
+ const ta = s.byUid[annotationId];
287
+ if (!ta) return false;
288
+ if (ta.object.type === PdfAnnotationSubtype.LINK) {
289
+ return false;
290
+ }
291
+ if (ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group) {
292
+ return true;
293
+ }
294
+ for (const uidList of Object.values(s.pages)) {
295
+ for (const uid of uidList) {
296
+ const other = s.byUid[uid];
297
+ if (other && other.object.inReplyToId === annotationId && other.object.replyType === PdfAnnotationReplyType.Group && other.object.type !== PdfAnnotationSubtype.LINK) {
298
+ return true;
299
+ }
300
+ }
301
+ }
302
+ return false;
303
+ };
304
+ const getSelectionGroupingAction = (s) => {
305
+ const selected = getSelectedAnnotations(s);
306
+ if (selected.length === 0) return "disabled";
307
+ const firstId = selected[0].object.id;
308
+ if (isInGroup(s, firstId)) {
309
+ const members = getGroupMembers(s, firstId);
310
+ const memberIds = new Set(members.map((m) => m.object.id));
311
+ if (selected.every((ta) => memberIds.has(ta.object.id))) {
312
+ return "ungroup";
313
+ }
314
+ }
315
+ return selected.length >= 2 ? "group" : "disabled";
316
+ };
193
317
  const defaultTools = [
194
318
  // Text Markup Tools
195
319
  {
@@ -200,11 +324,16 @@ const defaultTools = [
200
324
  exclusive: false,
201
325
  textSelection: true,
202
326
  isDraggable: false,
203
- isResizable: false
327
+ isResizable: false,
328
+ // Text markup annotations are anchored to text and should not move/resize in groups
329
+ isGroupDraggable: false,
330
+ isGroupResizable: false
204
331
  },
205
332
  defaults: {
206
333
  type: PdfAnnotationSubtype.HIGHLIGHT,
334
+ strokeColor: "#FFCD45",
207
335
  color: "#FFCD45",
336
+ // deprecated alias
208
337
  opacity: 1,
209
338
  blendMode: PdfBlendMode.Multiply
210
339
  }
@@ -217,11 +346,15 @@ const defaultTools = [
217
346
  exclusive: false,
218
347
  textSelection: true,
219
348
  isDraggable: false,
220
- isResizable: false
349
+ isResizable: false,
350
+ isGroupDraggable: false,
351
+ isGroupResizable: false
221
352
  },
222
353
  defaults: {
223
354
  type: PdfAnnotationSubtype.UNDERLINE,
355
+ strokeColor: "#E44234",
224
356
  color: "#E44234",
357
+ // deprecated alias
225
358
  opacity: 1
226
359
  }
227
360
  },
@@ -231,11 +364,17 @@ const defaultTools = [
231
364
  matchScore: (a) => a.type === PdfAnnotationSubtype.STRIKEOUT ? 1 : 0,
232
365
  interaction: {
233
366
  exclusive: false,
234
- textSelection: true
367
+ textSelection: true,
368
+ isDraggable: false,
369
+ isResizable: false,
370
+ isGroupDraggable: false,
371
+ isGroupResizable: false
235
372
  },
236
373
  defaults: {
237
374
  type: PdfAnnotationSubtype.STRIKEOUT,
375
+ strokeColor: "#E44234",
238
376
  color: "#E44234",
377
+ // deprecated alias
239
378
  opacity: 1
240
379
  }
241
380
  },
@@ -247,11 +386,15 @@ const defaultTools = [
247
386
  exclusive: false,
248
387
  textSelection: true,
249
388
  isDraggable: false,
250
- isResizable: false
389
+ isResizable: false,
390
+ isGroupDraggable: false,
391
+ isGroupResizable: false
251
392
  },
252
393
  defaults: {
253
394
  type: PdfAnnotationSubtype.SQUIGGLY,
395
+ strokeColor: "#E44234",
254
396
  color: "#E44234",
397
+ // deprecated alias
255
398
  opacity: 1
256
399
  }
257
400
  },
@@ -269,7 +412,9 @@ const defaultTools = [
269
412
  },
270
413
  defaults: {
271
414
  type: PdfAnnotationSubtype.INK,
415
+ strokeColor: "#E44234",
272
416
  color: "#E44234",
417
+ // deprecated alias
273
418
  opacity: 1,
274
419
  strokeWidth: 6
275
420
  }
@@ -288,7 +433,9 @@ const defaultTools = [
288
433
  defaults: {
289
434
  type: PdfAnnotationSubtype.INK,
290
435
  intent: "InkHighlight",
436
+ strokeColor: "#FFCD45",
291
437
  color: "#FFCD45",
438
+ // deprecated alias
292
439
  opacity: 1,
293
440
  strokeWidth: 14,
294
441
  blendMode: PdfBlendMode.Multiply
@@ -352,7 +499,10 @@ const defaultTools = [
352
499
  cursor: "crosshair",
353
500
  isDraggable: true,
354
501
  isResizable: false,
355
- lockAspectRatio: false
502
+ // Uses vertex editing when selected individually
503
+ lockAspectRatio: false,
504
+ isGroupResizable: true
505
+ // Scales proportionally in a group
356
506
  },
357
507
  defaults: {
358
508
  type: PdfAnnotationSubtype.LINE,
@@ -376,7 +526,10 @@ const defaultTools = [
376
526
  cursor: "crosshair",
377
527
  isDraggable: true,
378
528
  isResizable: false,
379
- lockAspectRatio: false
529
+ // Uses vertex editing when selected individually
530
+ lockAspectRatio: false,
531
+ isGroupResizable: true
532
+ // Scales proportionally in a group
380
533
  },
381
534
  defaults: {
382
535
  type: PdfAnnotationSubtype.LINE,
@@ -405,7 +558,10 @@ const defaultTools = [
405
558
  cursor: "crosshair",
406
559
  isDraggable: true,
407
560
  isResizable: false,
408
- lockAspectRatio: false
561
+ // Uses vertex editing when selected individually
562
+ lockAspectRatio: false,
563
+ isGroupResizable: true
564
+ // Scales proportionally in a group
409
565
  },
410
566
  defaults: {
411
567
  type: PdfAnnotationSubtype.POLYLINE,
@@ -424,7 +580,10 @@ const defaultTools = [
424
580
  cursor: "crosshair",
425
581
  isDraggable: true,
426
582
  isResizable: false,
427
- lockAspectRatio: false
583
+ // Uses vertex editing when selected individually
584
+ lockAspectRatio: false,
585
+ isGroupResizable: true
586
+ // Scales proportionally in a group
428
587
  },
429
588
  defaults: {
430
589
  type: PdfAnnotationSubtype.POLYGON,
@@ -454,7 +613,10 @@ const defaultTools = [
454
613
  fontFamily: PdfStandardFont.Helvetica,
455
614
  textAlign: PdfTextAlignment.Left,
456
615
  verticalAlign: PdfVerticalAlignment.Top,
616
+ color: "transparent",
617
+ // fill color (matches shape convention)
457
618
  backgroundColor: "transparent",
619
+ // deprecated alias
458
620
  opacity: 1
459
621
  },
460
622
  clickBehavior: {
@@ -492,9 +654,11 @@ const DEFAULT_COLORS = [
492
654
  "#000000",
493
655
  "#FFFFFF"
494
656
  ];
657
+ const computeSelectedUid = (selectedUids) => selectedUids.length === 1 ? selectedUids[0] : null;
495
658
  const initialDocumentState = () => ({
496
659
  pages: {},
497
660
  byUid: {},
661
+ selectedUids: [],
498
662
  selectedUid: null,
499
663
  activeToolId: null,
500
664
  hasPendingChanges: false
@@ -595,7 +759,7 @@ const reducer = (state, action) => {
595
759
  ...state,
596
760
  documents: {
597
761
  ...state.documents,
598
- [documentId]: { ...docState, selectedUid: id }
762
+ [documentId]: { ...docState, selectedUids: [id], selectedUid: id }
599
763
  }
600
764
  };
601
765
  }
@@ -607,7 +771,58 @@ const reducer = (state, action) => {
607
771
  ...state,
608
772
  documents: {
609
773
  ...state.documents,
610
- [documentId]: { ...docState, selectedUid: null }
774
+ [documentId]: { ...docState, selectedUids: [], selectedUid: null }
775
+ }
776
+ };
777
+ }
778
+ case ADD_TO_SELECTION: {
779
+ const { documentId, id } = action.payload;
780
+ const docState = state.documents[documentId];
781
+ if (!docState) return state;
782
+ if (docState.selectedUids.includes(id)) return state;
783
+ const newSelectedUids = [...docState.selectedUids, id];
784
+ return {
785
+ ...state,
786
+ documents: {
787
+ ...state.documents,
788
+ [documentId]: {
789
+ ...docState,
790
+ selectedUids: newSelectedUids,
791
+ selectedUid: computeSelectedUid(newSelectedUids)
792
+ }
793
+ }
794
+ };
795
+ }
796
+ case REMOVE_FROM_SELECTION: {
797
+ const { documentId, id } = action.payload;
798
+ const docState = state.documents[documentId];
799
+ if (!docState) return state;
800
+ const newSelectedUids = docState.selectedUids.filter((uid) => uid !== id);
801
+ return {
802
+ ...state,
803
+ documents: {
804
+ ...state.documents,
805
+ [documentId]: {
806
+ ...docState,
807
+ selectedUids: newSelectedUids,
808
+ selectedUid: computeSelectedUid(newSelectedUids)
809
+ }
810
+ }
811
+ };
812
+ }
813
+ case SET_SELECTION: {
814
+ const { documentId, ids } = action.payload;
815
+ const docState = state.documents[documentId];
816
+ if (!docState) return state;
817
+ return {
818
+ ...state,
819
+ documents: {
820
+ ...state.documents,
821
+ [documentId]: {
822
+ ...docState,
823
+ selectedUids: ids,
824
+ selectedUid: computeSelectedUid(ids)
825
+ }
611
826
  }
612
827
  };
613
828
  }
@@ -683,21 +898,30 @@ const reducer = (state, action) => {
683
898
  };
684
899
  }
685
900
  case COMMIT_PENDING_CHANGES: {
686
- const { documentId } = action.payload;
901
+ const { documentId, committedUids } = action.payload;
687
902
  const docState = state.documents[documentId];
688
903
  if (!docState) return state;
904
+ const committedSet = new Set(committedUids);
689
905
  const cleaned = {};
906
+ let stillHasPending = false;
690
907
  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
- };
908
+ if (committedSet.has(uid)) {
909
+ cleaned[uid] = {
910
+ ...ta,
911
+ commitState: ta.commitState === "dirty" || ta.commitState === "new" ? "synced" : ta.commitState
912
+ };
913
+ } else {
914
+ cleaned[uid] = ta;
915
+ if (ta.commitState === "new" || ta.commitState === "dirty" || ta.commitState === "deleted") {
916
+ stillHasPending = true;
917
+ }
918
+ }
695
919
  }
696
920
  return {
697
921
  ...state,
698
922
  documents: {
699
923
  ...state.documents,
700
- [documentId]: { ...docState, byUid: cleaned, hasPendingChanges: false }
924
+ [documentId]: { ...docState, byUid: cleaned, hasPendingChanges: stillHasPending }
701
925
  }
702
926
  };
703
927
  }
@@ -763,7 +987,7 @@ const inkHandlerFactory = {
763
987
  return {
764
988
  ...tool.defaults,
765
989
  strokeWidth: tool.defaults.strokeWidth ?? 1,
766
- color: tool.defaults.color ?? "#000000",
990
+ strokeColor: tool.defaults.strokeColor ?? tool.defaults.color ?? "#000000",
767
991
  opacity: tool.defaults.opacity ?? 1,
768
992
  flags: tool.defaults.flags ?? ["print"]
769
993
  };
@@ -899,7 +1123,7 @@ const freeTextHandlerFactory = {
899
1123
  opacity: tool.defaults.opacity ?? 1,
900
1124
  fontSize: tool.defaults.fontSize ?? 12,
901
1125
  fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
902
- backgroundColor: tool.defaults.backgroundColor ?? "transparent",
1126
+ color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
903
1127
  textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
904
1128
  verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
905
1129
  contents: tool.defaults.contents ?? "Insert text here",
@@ -1435,7 +1659,10 @@ const polylineHandlerFactory = {
1435
1659
  };
1436
1660
  };
1437
1661
  return {
1438
- onClick: (pos) => {
1662
+ onClick: (pos, evt) => {
1663
+ if (evt.metaKey || evt.ctrlKey) {
1664
+ return;
1665
+ }
1439
1666
  const clampedPos = clampToPage(pos);
1440
1667
  const vertices = getVertices();
1441
1668
  const lastVertex = vertices[vertices.length - 1];
@@ -1537,7 +1764,10 @@ const polygonHandlerFactory = {
1537
1764
  };
1538
1765
  };
1539
1766
  return {
1540
- onClick: (pos) => {
1767
+ onClick: (pos, evt) => {
1768
+ if (evt.metaKey || evt.ctrlKey) {
1769
+ return;
1770
+ }
1541
1771
  const clampedPos = clampToPage(pos);
1542
1772
  if (isInsideStartHandle(clampedPos) && getVertices().length >= 3) {
1543
1773
  commitPolygon();
@@ -2234,11 +2464,16 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2234
2464
  this.pendingContexts = /* @__PURE__ */ new Map();
2235
2465
  this.isInitialLoadComplete = /* @__PURE__ */ new Map();
2236
2466
  this.importQueue = /* @__PURE__ */ new Map();
2467
+ this.commitInProgress = /* @__PURE__ */ new Map();
2237
2468
  this.handlerFactories = /* @__PURE__ */ new Map();
2238
2469
  this.activeTool$ = createBehaviorEmitter();
2239
2470
  this.events$ = createBehaviorEmitter();
2240
2471
  this.toolsChange$ = createBehaviorEmitter();
2241
2472
  this.patchRegistry = new PatchRegistry();
2473
+ this.unifiedDragStates = /* @__PURE__ */ new Map();
2474
+ this.unifiedDrag$ = createBehaviorEmitter();
2475
+ this.unifiedResizeStates = /* @__PURE__ */ new Map();
2476
+ this.unifiedResize$ = createBehaviorEmitter();
2242
2477
  this.config = config;
2243
2478
  this.selection = ((_a = registry.getPlugin("selection")) == null ? void 0 : _a.provides()) ?? null;
2244
2479
  this.history = ((_b = registry.getPlugin("history")) == null ? void 0 : _b.provides()) ?? null;
@@ -2268,7 +2503,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2268
2503
  if (this.selection) {
2269
2504
  for (const tool of this.state.tools) {
2270
2505
  if (tool.interaction.textSelection) {
2271
- this.selection.enableForMode(tool.interaction.mode ?? tool.id);
2506
+ this.selection.enableForMode(tool.interaction.mode ?? tool.id, { showRects: false });
2272
2507
  }
2273
2508
  }
2274
2509
  }
@@ -2301,7 +2536,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2301
2536
  this.patchRegistry.register(PdfAnnotationSubtype.POLYGON, patchPolygon);
2302
2537
  }
2303
2538
  async initialize() {
2304
- var _a, _b;
2539
+ var _a, _b, _c;
2305
2540
  this.state.tools.forEach((tool) => this.registerInteractionForTool(tool));
2306
2541
  if (this.history) {
2307
2542
  this.history.onHistoryChange((event) => {
@@ -2318,8 +2553,31 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2318
2553
  this.dispatch(setActiveToolId(s.documentId, newToolId));
2319
2554
  }
2320
2555
  });
2321
- (_b = this.selection) == null ? void 0 : _b.onEndSelection(({ documentId }) => {
2322
- var _a2, _b2, _c;
2556
+ (_b = this.selection) == null ? void 0 : _b.onMarqueeEnd(({ documentId, pageIndex, rect }) => {
2557
+ const docState = this.state.documents[documentId];
2558
+ if (!docState) return;
2559
+ const pageAnnotations = (docState.pages[pageIndex] ?? []).map((uid) => docState.byUid[uid]).filter((ta) => ta !== void 0).filter((ta) => !isLink(ta));
2560
+ const selectedIds = pageAnnotations.filter((ta) => rectsIntersect(rect, ta.object.rect)).map((ta) => ta.object.id);
2561
+ if (selectedIds.length > 0) {
2562
+ const expandedIds = /* @__PURE__ */ new Set();
2563
+ for (const id of selectedIds) {
2564
+ if (this.isInGroupMethod(id, documentId)) {
2565
+ const members = this.getGroupMembersMethod(id, documentId);
2566
+ for (const member of members) {
2567
+ expandedIds.add(member.object.id);
2568
+ }
2569
+ } else {
2570
+ expandedIds.add(id);
2571
+ }
2572
+ }
2573
+ this.setSelectionMethod([...expandedIds], documentId);
2574
+ }
2575
+ });
2576
+ (_c = this.selection) == null ? void 0 : _c.onEndSelection(({ documentId }) => {
2577
+ var _a2, _b2, _c2;
2578
+ if (!this.checkPermission(documentId, PdfPermissionFlag.ModifyAnnotations)) {
2579
+ return;
2580
+ }
2323
2581
  const activeTool = this.getActiveTool(documentId);
2324
2582
  if (!activeTool || !activeTool.interaction.textSelection) return;
2325
2583
  const formattedSelection = (_a2 = this.selection) == null ? void 0 : _a2.getFormattedSelection();
@@ -2352,7 +2610,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2352
2610
  }
2353
2611
  }, ignore);
2354
2612
  }
2355
- (_c = this.selection) == null ? void 0 : _c.clear();
2613
+ (_c2 = this.selection) == null ? void 0 : _c2.clear();
2356
2614
  });
2357
2615
  }
2358
2616
  registerInteractionForTool(tool) {
@@ -2372,15 +2630,32 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2372
2630
  getState: () => this.getDocumentState(),
2373
2631
  getPageAnnotations: (options) => this.getPageAnnotations(options),
2374
2632
  getSelectedAnnotation: () => this.getSelectedAnnotation(),
2633
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(),
2634
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(),
2375
2635
  getAnnotationById: (id) => this.getAnnotationById(id),
2376
2636
  selectAnnotation: (pageIndex, id) => this.selectAnnotation(pageIndex, id),
2637
+ toggleSelection: (pageIndex, id) => this.toggleSelectionMethod(pageIndex, id),
2638
+ addToSelection: (pageIndex, id) => this.addToSelectionMethod(pageIndex, id),
2639
+ removeFromSelection: (id) => this.removeFromSelectionMethod(id),
2640
+ setSelection: (ids) => this.setSelectionMethod(ids),
2377
2641
  deselectAnnotation: () => this.deselectAnnotation(),
2378
2642
  importAnnotations: (items) => this.importAnnotations(items),
2379
2643
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx),
2380
2644
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch),
2645
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches),
2381
2646
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id),
2647
+ deleteAnnotations: (annotations, documentId) => this.deleteAnnotationsMethod(annotations, documentId),
2382
2648
  renderAnnotation: (options) => this.renderAnnotation(options),
2383
2649
  commit: () => this.commit(),
2650
+ // Attached links (IRT link children)
2651
+ getAttachedLinks: (id, documentId) => this.getAttachedLinksMethod(id, documentId),
2652
+ hasAttachedLinks: (id, documentId) => this.hasAttachedLinksMethod(id, documentId),
2653
+ deleteAttachedLinks: (id, documentId) => this.deleteAttachedLinksMethod(id, documentId),
2654
+ // Annotation grouping (RT = Group)
2655
+ groupAnnotations: (documentId) => this.groupAnnotationsMethod(documentId),
2656
+ ungroupAnnotations: (id, documentId) => this.ungroupAnnotationsMethod(id, documentId),
2657
+ getGroupMembers: (id, documentId) => this.getGroupMembersMethod(id, documentId),
2658
+ isInGroup: (id, documentId) => this.isInGroupMethod(id, documentId),
2384
2659
  // Document-scoped operations
2385
2660
  forDocument: (documentId) => this.createAnnotationScope(documentId),
2386
2661
  // Global operations
@@ -2411,8 +2686,14 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2411
2686
  getState: () => this.getDocumentState(documentId),
2412
2687
  getPageAnnotations: (options) => this.getPageAnnotations(options, documentId),
2413
2688
  getSelectedAnnotation: () => this.getSelectedAnnotation(documentId),
2689
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(documentId),
2690
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(documentId),
2414
2691
  getAnnotationById: (id) => this.getAnnotationById(id, documentId),
2415
2692
  selectAnnotation: (pageIndex, id) => this.selectAnnotation(pageIndex, id, documentId),
2693
+ toggleSelection: (pageIndex, id) => this.toggleSelectionMethod(pageIndex, id, documentId),
2694
+ addToSelection: (pageIndex, id) => this.addToSelectionMethod(pageIndex, id, documentId),
2695
+ removeFromSelection: (id) => this.removeFromSelectionMethod(id, documentId),
2696
+ setSelection: (ids) => this.setSelectionMethod(ids, documentId),
2416
2697
  deselectAnnotation: () => this.deselectAnnotation(documentId),
2417
2698
  getActiveTool: () => this.getActiveTool(documentId),
2418
2699
  setActiveTool: (toolId) => this.setActiveTool(toolId, documentId),
@@ -2420,9 +2701,19 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2420
2701
  importAnnotations: (items) => this.importAnnotations(items, documentId),
2421
2702
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx, documentId),
2422
2703
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch, documentId),
2704
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches, documentId),
2423
2705
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id, documentId),
2706
+ deleteAnnotations: (annotations) => this.deleteAnnotationsMethod(annotations, documentId),
2424
2707
  renderAnnotation: (options) => this.renderAnnotation(options, documentId),
2425
2708
  commit: () => this.commit(documentId),
2709
+ getAttachedLinks: (id) => this.getAttachedLinksMethod(id, documentId),
2710
+ hasAttachedLinks: (id) => this.hasAttachedLinksMethod(id, documentId),
2711
+ deleteAttachedLinks: (id) => this.deleteAttachedLinksMethod(id, documentId),
2712
+ groupAnnotations: () => this.groupAnnotationsMethod(documentId),
2713
+ ungroupAnnotations: (id) => this.ungroupAnnotationsMethod(id, documentId),
2714
+ getGroupMembers: (id) => this.getGroupMembersMethod(id, documentId),
2715
+ isInGroup: (id) => this.isInGroupMethod(id, documentId),
2716
+ getGroupingAction: () => this.getGroupingActionMethod(documentId),
2426
2717
  onStateChange: (listener) => this.state$.on((event) => {
2427
2718
  if (event.documentId === documentId) listener(event.state);
2428
2719
  }),
@@ -2609,8 +2900,16 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2609
2900
  if (this.config.autoCommit !== false) this.commit(documentId);
2610
2901
  }
2611
2902
  createAnnotation(pageIndex, annotation, ctx, documentId) {
2612
- const id = annotation.id;
2613
2903
  const docId = documentId ?? this.getActiveDocumentId();
2904
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
2905
+ this.logger.debug(
2906
+ "AnnotationPlugin",
2907
+ "CreateAnnotation",
2908
+ `Cannot create annotation: document ${docId} lacks ModifyAnnotations permission`
2909
+ );
2910
+ return;
2911
+ }
2912
+ const id = annotation.id;
2614
2913
  const contexts = this.pendingContexts.get(docId);
2615
2914
  if (!contexts) return;
2616
2915
  const newAnnotation = {
@@ -2661,6 +2960,14 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2661
2960
  }
2662
2961
  updateAnnotation(pageIndex, id, patch, documentId) {
2663
2962
  const docId = documentId ?? this.getActiveDocumentId();
2963
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
2964
+ this.logger.debug(
2965
+ "AnnotationPlugin",
2966
+ "UpdateAnnotation",
2967
+ `Cannot update annotation: document ${docId} lacks ModifyAnnotations permission`
2968
+ );
2969
+ return;
2970
+ }
2664
2971
  const docState = this.getDocumentState(docId);
2665
2972
  const originalObject = docState.byUid[id].object;
2666
2973
  const finalPatch = this.buildPatch(originalObject, {
@@ -2708,10 +3015,37 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2708
3015
  deleteAnnotation(pageIndex, id, documentId) {
2709
3016
  var _a;
2710
3017
  const docId = documentId ?? this.getActiveDocumentId();
3018
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3019
+ this.logger.debug(
3020
+ "AnnotationPlugin",
3021
+ "DeleteAnnotation",
3022
+ `Cannot delete annotation: document ${docId} lacks ModifyAnnotations permission`
3023
+ );
3024
+ return;
3025
+ }
2711
3026
  const docState = this.getDocumentState(docId);
2712
3027
  const originalAnnotation = (_a = docState.byUid[id]) == null ? void 0 : _a.object;
2713
3028
  if (!originalAnnotation) return;
3029
+ const irtChildren = getIRTChildIds(docState, id);
3030
+ const childAnnotations = irtChildren.map((child) => {
3031
+ var _a2;
3032
+ return (_a2 = docState.byUid[child.id]) == null ? void 0 : _a2.object;
3033
+ }).filter((obj) => obj !== void 0);
2714
3034
  const execute = () => {
3035
+ var _a2;
3036
+ for (const child of irtChildren) {
3037
+ const childObj = (_a2 = docState.byUid[child.id]) == null ? void 0 : _a2.object;
3038
+ if (childObj) {
3039
+ this.dispatch(deleteAnnotation(docId, child.pageIndex, child.id));
3040
+ this.events$.emit({
3041
+ type: "delete",
3042
+ documentId: docId,
3043
+ annotation: childObj,
3044
+ pageIndex: child.pageIndex,
3045
+ committed: false
3046
+ });
3047
+ }
3048
+ }
2715
3049
  this.dispatch(deselectAnnotation(docId));
2716
3050
  this.dispatch(deleteAnnotation(docId, pageIndex, id));
2717
3051
  this.events$.emit({
@@ -2738,19 +3072,703 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2738
3072
  pageIndex,
2739
3073
  committed: false
2740
3074
  });
3075
+ for (const childObj of childAnnotations) {
3076
+ this.dispatch(createAnnotation(docId, childObj.pageIndex, childObj));
3077
+ this.events$.emit({
3078
+ type: "create",
3079
+ documentId: docId,
3080
+ annotation: childObj,
3081
+ pageIndex: childObj.pageIndex,
3082
+ committed: false
3083
+ });
3084
+ }
2741
3085
  }
2742
3086
  };
2743
3087
  const historyScope = this.history.forDocument(docId);
2744
3088
  historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
2745
3089
  }
3090
+ deleteAnnotationsMethod(annotations, documentId) {
3091
+ for (const { pageIndex, id } of annotations) {
3092
+ this.deleteAnnotation(pageIndex, id, documentId);
3093
+ }
3094
+ }
2746
3095
  selectAnnotation(pageIndex, id, documentId) {
2747
3096
  const docId = documentId ?? this.getActiveDocumentId();
2748
- this.dispatch(selectAnnotation(docId, pageIndex, id));
3097
+ if (this.isInGroupMethod(id, docId)) {
3098
+ const members = this.getGroupMembersMethod(id, docId);
3099
+ const memberIds = members.map((m) => m.object.id);
3100
+ this.dispatch(setSelection(docId, memberIds));
3101
+ } else {
3102
+ this.dispatch(selectAnnotation(docId, pageIndex, id));
3103
+ }
2749
3104
  }
2750
3105
  deselectAnnotation(documentId) {
2751
3106
  const docId = documentId ?? this.getActiveDocumentId();
2752
3107
  this.dispatch(deselectAnnotation(docId));
2753
3108
  }
3109
+ // ─────────────────────────────────────────────────────────
3110
+ // Multi-Select Methods
3111
+ // ─────────────────────────────────────────────────────────
3112
+ getSelectedAnnotationsMethod(documentId) {
3113
+ return getSelectedAnnotations(this.getDocumentState(documentId));
3114
+ }
3115
+ getSelectedAnnotationIdsMethod(documentId) {
3116
+ return getSelectedAnnotationIds(this.getDocumentState(documentId));
3117
+ }
3118
+ toggleSelectionMethod(pageIndex, id, documentId) {
3119
+ const docId = documentId ?? this.getActiveDocumentId();
3120
+ const docState = this.getDocumentState(docId);
3121
+ if (docState.selectedUids.includes(id)) {
3122
+ this.dispatch(removeFromSelection(docId, id));
3123
+ } else {
3124
+ if (this.isInGroupMethod(id, docId)) {
3125
+ const members = this.getGroupMembersMethod(id, docId);
3126
+ for (const member of members) {
3127
+ if (!docState.selectedUids.includes(member.object.id)) {
3128
+ this.dispatch(addToSelection(docId, member.object.pageIndex, member.object.id));
3129
+ }
3130
+ }
3131
+ } else {
3132
+ this.dispatch(addToSelection(docId, pageIndex, id));
3133
+ }
3134
+ }
3135
+ }
3136
+ addToSelectionMethod(pageIndex, id, documentId) {
3137
+ const docId = documentId ?? this.getActiveDocumentId();
3138
+ this.dispatch(addToSelection(docId, pageIndex, id));
3139
+ }
3140
+ removeFromSelectionMethod(id, documentId) {
3141
+ const docId = documentId ?? this.getActiveDocumentId();
3142
+ this.dispatch(removeFromSelection(docId, id));
3143
+ }
3144
+ setSelectionMethod(ids, documentId) {
3145
+ const docId = documentId ?? this.getActiveDocumentId();
3146
+ this.dispatch(setSelection(docId, ids));
3147
+ }
3148
+ // ─────────────────────────────────────────────────────────
3149
+ // Attached Links Methods
3150
+ // ─────────────────────────────────────────────────────────
3151
+ getAttachedLinksMethod(annotationId, documentId) {
3152
+ return getAttachedLinks(this.getDocumentState(documentId), annotationId);
3153
+ }
3154
+ hasAttachedLinksMethod(annotationId, documentId) {
3155
+ return this.getAttachedLinksMethod(annotationId, documentId).length > 0;
3156
+ }
3157
+ deleteAttachedLinksMethod(annotationId, documentId) {
3158
+ const links = this.getAttachedLinksMethod(annotationId, documentId);
3159
+ for (const link of links) {
3160
+ this.deleteAnnotation(link.object.pageIndex, link.object.id, documentId);
3161
+ }
3162
+ }
3163
+ // ─────────────────────────────────────────────────────────
3164
+ // Annotation Grouping Methods
3165
+ // ─────────────────────────────────────────────────────────
3166
+ /**
3167
+ * Group the currently selected annotations.
3168
+ * The first selected annotation becomes the group leader.
3169
+ * All other selected annotations get their IRT set to the leader's ID with RT = Group.
3170
+ */
3171
+ groupAnnotationsMethod(documentId) {
3172
+ const docId = documentId ?? this.getActiveDocumentId();
3173
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3174
+ this.logger.debug(
3175
+ "AnnotationPlugin",
3176
+ "GroupAnnotations",
3177
+ `Cannot group annotations: document ${docId} lacks ModifyAnnotations permission`
3178
+ );
3179
+ return;
3180
+ }
3181
+ const selected = this.getSelectedAnnotationsMethod(docId);
3182
+ if (selected.length < 2) {
3183
+ this.logger.debug(
3184
+ "AnnotationPlugin",
3185
+ "GroupAnnotations",
3186
+ "Need at least 2 annotations to group"
3187
+ );
3188
+ return;
3189
+ }
3190
+ const leader = selected[0];
3191
+ const members = selected.slice(1);
3192
+ const patches = members.map((ta) => ({
3193
+ pageIndex: ta.object.pageIndex,
3194
+ id: ta.object.id,
3195
+ patch: {
3196
+ inReplyToId: leader.object.id,
3197
+ replyType: PdfAnnotationReplyType.Group
3198
+ }
3199
+ }));
3200
+ this.updateAnnotationsMethod(patches, docId);
3201
+ }
3202
+ /**
3203
+ * Ungroup all annotations in the group containing the specified annotation.
3204
+ * Clears IRT and RT from all group members (the leader doesn't have them).
3205
+ */
3206
+ ungroupAnnotationsMethod(annotationId, documentId) {
3207
+ const docId = documentId ?? this.getActiveDocumentId();
3208
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3209
+ this.logger.debug(
3210
+ "AnnotationPlugin",
3211
+ "UngroupAnnotations",
3212
+ `Cannot ungroup annotations: document ${docId} lacks ModifyAnnotations permission`
3213
+ );
3214
+ return;
3215
+ }
3216
+ const members = this.getGroupMembersMethod(annotationId, docId);
3217
+ const patches = members.filter((ta) => ta.object.inReplyToId && ta.object.replyType === PdfAnnotationReplyType.Group).map((ta) => ({
3218
+ pageIndex: ta.object.pageIndex,
3219
+ id: ta.object.id,
3220
+ patch: {
3221
+ inReplyToId: void 0,
3222
+ replyType: void 0
3223
+ }
3224
+ }));
3225
+ if (patches.length > 0) {
3226
+ this.updateAnnotationsMethod(patches, docId);
3227
+ }
3228
+ }
3229
+ /**
3230
+ * Get all annotations in the same group as the specified annotation.
3231
+ */
3232
+ getGroupMembersMethod(annotationId, documentId) {
3233
+ return getGroupMembers(this.getDocumentState(documentId), annotationId);
3234
+ }
3235
+ /**
3236
+ * Check if an annotation is part of a group.
3237
+ */
3238
+ isInGroupMethod(annotationId, documentId) {
3239
+ return isInGroup(this.getDocumentState(documentId), annotationId);
3240
+ }
3241
+ /**
3242
+ * Get the available grouping action for the current selection.
3243
+ */
3244
+ getGroupingActionMethod(documentId) {
3245
+ return getSelectionGroupingAction(this.getDocumentState(documentId));
3246
+ }
3247
+ // ─────────────────────────────────────────────────────────
3248
+ // Multi-Drag Coordination (Internal API for framework components)
3249
+ // ─────────────────────────────────────────────────────────
3250
+ /**
3251
+ * Compute combined constraints from all selected annotations.
3252
+ * This finds the "weakest link" in each direction - the annotation with the least
3253
+ * room to move determines the group's limit.
3254
+ */
3255
+ computeCombinedConstraints(annotations) {
3256
+ let maxUp = Infinity;
3257
+ let maxDown = Infinity;
3258
+ let maxLeft = Infinity;
3259
+ let maxRight = Infinity;
3260
+ for (const anno of annotations) {
3261
+ const upLimit = anno.rect.origin.y;
3262
+ const downLimit = anno.pageSize.height - (anno.rect.origin.y + anno.rect.size.height);
3263
+ const leftLimit = anno.rect.origin.x;
3264
+ const rightLimit = anno.pageSize.width - (anno.rect.origin.x + anno.rect.size.width);
3265
+ maxUp = Math.min(maxUp, upLimit);
3266
+ maxDown = Math.min(maxDown, downLimit);
3267
+ maxLeft = Math.min(maxLeft, leftLimit);
3268
+ maxRight = Math.min(maxRight, rightLimit);
3269
+ }
3270
+ if (!isFinite(maxUp)) maxUp = 0;
3271
+ if (!isFinite(maxDown)) maxDown = 0;
3272
+ if (!isFinite(maxLeft)) maxLeft = 0;
3273
+ if (!isFinite(maxRight)) maxRight = 0;
3274
+ return { maxUp, maxDown, maxLeft, maxRight };
3275
+ }
3276
+ /**
3277
+ * Clamp a delta to the combined constraints.
3278
+ * Negative y = moving up, positive y = moving down
3279
+ * Negative x = moving left, positive x = moving right
3280
+ */
3281
+ clampDelta(rawDelta, constraints) {
3282
+ return {
3283
+ x: Math.max(-constraints.maxLeft, Math.min(constraints.maxRight, rawDelta.x)),
3284
+ y: Math.max(-constraints.maxUp, Math.min(constraints.maxDown, rawDelta.y))
3285
+ };
3286
+ }
3287
+ // ─────────────────────────────────────────────────────────
3288
+ // Unified Drag API (Plugin owns all logic - framework just calls these)
3289
+ // ─────────────────────────────────────────────────────────
3290
+ /**
3291
+ * Start a unified drag operation.
3292
+ * The plugin automatically expands the selection to include attached links.
3293
+ * Framework components should call this instead of building their own logic.
3294
+ *
3295
+ * @param documentId - The document ID
3296
+ * @param options - Drag options (annotationIds and pageSize)
3297
+ */
3298
+ startDrag(documentId, options) {
3299
+ const { annotationIds, pageSize } = options;
3300
+ const attachedLinkIds = [];
3301
+ for (const id of annotationIds) {
3302
+ const links = this.getAttachedLinksMethod(id, documentId);
3303
+ for (const link of links) {
3304
+ if (!attachedLinkIds.includes(link.object.id)) {
3305
+ attachedLinkIds.push(link.object.id);
3306
+ }
3307
+ }
3308
+ }
3309
+ const allParticipantIds = [...annotationIds, ...attachedLinkIds];
3310
+ const originalRects = /* @__PURE__ */ new Map();
3311
+ const constraints = [];
3312
+ for (const id of allParticipantIds) {
3313
+ const ta = this.getAnnotationById(id, documentId);
3314
+ if (ta) {
3315
+ originalRects.set(id, { ...ta.object.rect });
3316
+ constraints.push({
3317
+ id,
3318
+ rect: ta.object.rect,
3319
+ pageIndex: ta.object.pageIndex,
3320
+ pageSize
3321
+ });
3322
+ }
3323
+ }
3324
+ const combinedConstraints = this.computeCombinedConstraints(constraints);
3325
+ const state = {
3326
+ documentId,
3327
+ isDragging: true,
3328
+ primaryIds: annotationIds,
3329
+ attachedLinkIds,
3330
+ allParticipantIds,
3331
+ originalRects,
3332
+ delta: { x: 0, y: 0 },
3333
+ combinedConstraints
3334
+ };
3335
+ this.unifiedDragStates.set(documentId, state);
3336
+ this.unifiedDrag$.emit({ documentId, type: "start", state, previewPatches: {} });
3337
+ }
3338
+ /**
3339
+ * Compute preview patches for all drag participants.
3340
+ * Uses transformAnnotation to properly handle vertices, inkList, etc.
3341
+ */
3342
+ computeDragPreviewPatches(state, documentId) {
3343
+ const previewPatches = {};
3344
+ for (const id of state.allParticipantIds) {
3345
+ const ta = this.getAnnotationById(id, documentId);
3346
+ if (!ta) continue;
3347
+ const originalRect = state.originalRects.get(id);
3348
+ if (!originalRect) continue;
3349
+ const newRect = {
3350
+ ...originalRect,
3351
+ origin: {
3352
+ x: originalRect.origin.x + state.delta.x,
3353
+ y: originalRect.origin.y + state.delta.y
3354
+ }
3355
+ };
3356
+ previewPatches[id] = this.transformAnnotation(ta.object, {
3357
+ type: "move",
3358
+ changes: { rect: newRect }
3359
+ });
3360
+ }
3361
+ return previewPatches;
3362
+ }
3363
+ /**
3364
+ * Update the drag delta during a unified drag operation.
3365
+ * Returns the clamped delta synchronously for the caller's preview.
3366
+ *
3367
+ * @param documentId - The document ID
3368
+ * @param rawDelta - The unconstrained delta from the drag gesture
3369
+ * @returns The clamped delta
3370
+ */
3371
+ updateDrag(documentId, rawDelta) {
3372
+ const state = this.unifiedDragStates.get(documentId);
3373
+ if (!(state == null ? void 0 : state.isDragging)) {
3374
+ return { x: 0, y: 0 };
3375
+ }
3376
+ const clampedDelta = this.clampDelta(rawDelta, state.combinedConstraints);
3377
+ const newState = {
3378
+ ...state,
3379
+ delta: clampedDelta
3380
+ };
3381
+ this.unifiedDragStates.set(documentId, newState);
3382
+ const previewPatches = this.computeDragPreviewPatches(newState, documentId);
3383
+ this.unifiedDrag$.emit({ documentId, type: "update", state: newState, previewPatches });
3384
+ return clampedDelta;
3385
+ }
3386
+ /**
3387
+ * Commit the drag - plugin builds and applies ALL patches.
3388
+ * This is the key method that centralizes patch building in the plugin.
3389
+ *
3390
+ * @param documentId - The document ID
3391
+ */
3392
+ commitDrag(documentId) {
3393
+ const state = this.unifiedDragStates.get(documentId);
3394
+ if (!state) return;
3395
+ const finalDelta = state.delta;
3396
+ if (finalDelta.x !== 0 || finalDelta.y !== 0) {
3397
+ const patches = [];
3398
+ for (const id of state.allParticipantIds) {
3399
+ const ta = this.getAnnotationById(id, documentId);
3400
+ if (!ta) continue;
3401
+ const originalRect = state.originalRects.get(id) ?? ta.object.rect;
3402
+ const newRect = {
3403
+ ...originalRect,
3404
+ origin: {
3405
+ x: originalRect.origin.x + finalDelta.x,
3406
+ y: originalRect.origin.y + finalDelta.y
3407
+ }
3408
+ };
3409
+ const patch = this.transformAnnotation(ta.object, {
3410
+ type: "move",
3411
+ changes: { rect: newRect }
3412
+ });
3413
+ patches.push({ pageIndex: ta.object.pageIndex, id, patch });
3414
+ }
3415
+ if (patches.length > 0) {
3416
+ this.updateAnnotationsMethod(patches, documentId);
3417
+ }
3418
+ }
3419
+ const endPatches = this.computeDragPreviewPatches(state, documentId);
3420
+ this.unifiedDrag$.emit({
3421
+ documentId,
3422
+ type: "end",
3423
+ state: { ...state, isDragging: false },
3424
+ previewPatches: endPatches
3425
+ });
3426
+ this.unifiedDragStates.delete(documentId);
3427
+ }
3428
+ /**
3429
+ * Cancel the drag without committing.
3430
+ *
3431
+ * @param documentId - The document ID
3432
+ */
3433
+ cancelDrag(documentId) {
3434
+ const state = this.unifiedDragStates.get(documentId);
3435
+ if (!state) return;
3436
+ this.unifiedDrag$.emit({
3437
+ documentId,
3438
+ type: "cancel",
3439
+ state: { ...state, isDragging: false, delta: { x: 0, y: 0 } },
3440
+ previewPatches: {}
3441
+ });
3442
+ this.unifiedDragStates.delete(documentId);
3443
+ }
3444
+ /**
3445
+ * Get the current unified drag state for a document.
3446
+ */
3447
+ getDragState(documentId) {
3448
+ return this.unifiedDragStates.get(documentId) ?? null;
3449
+ }
3450
+ /**
3451
+ * Subscribe to unified drag state changes.
3452
+ * Framework components use this for preview updates.
3453
+ */
3454
+ get onDragChange() {
3455
+ return this.unifiedDrag$.on;
3456
+ }
3457
+ // ─────────────────────────────────────────────────────────
3458
+ // Unified Resize API (Plugin owns all logic - framework just calls these)
3459
+ // ─────────────────────────────────────────────────────────
3460
+ /**
3461
+ * Compute the union bounding box of multiple rects.
3462
+ */
3463
+ computeUnifiedGroupBoundingBox(rects) {
3464
+ if (rects.length === 0) {
3465
+ return { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } };
3466
+ }
3467
+ let minX = Infinity;
3468
+ let minY = Infinity;
3469
+ let maxX = -Infinity;
3470
+ let maxY = -Infinity;
3471
+ for (const rect of rects) {
3472
+ minX = Math.min(minX, rect.origin.x);
3473
+ minY = Math.min(minY, rect.origin.y);
3474
+ maxX = Math.max(maxX, rect.origin.x + rect.size.width);
3475
+ maxY = Math.max(maxY, rect.origin.y + rect.size.height);
3476
+ }
3477
+ return {
3478
+ origin: { x: minX, y: minY },
3479
+ size: { width: maxX - minX, height: maxY - minY }
3480
+ };
3481
+ }
3482
+ /**
3483
+ * Compute relative positions for annotations within a group bounding box.
3484
+ */
3485
+ computeUnifiedRelativePositions(annotations, groupBox) {
3486
+ return annotations.map((anno) => ({
3487
+ id: anno.id,
3488
+ originalRect: anno.rect,
3489
+ pageIndex: anno.pageIndex,
3490
+ isAttachedLink: anno.isAttachedLink,
3491
+ parentId: anno.parentId,
3492
+ relativeX: groupBox.size.width > 0 ? (anno.rect.origin.x - groupBox.origin.x) / groupBox.size.width : 0,
3493
+ relativeY: groupBox.size.height > 0 ? (anno.rect.origin.y - groupBox.origin.y) / groupBox.size.height : 0,
3494
+ relativeWidth: groupBox.size.width > 0 ? anno.rect.size.width / groupBox.size.width : 1,
3495
+ relativeHeight: groupBox.size.height > 0 ? anno.rect.size.height / groupBox.size.height : 1
3496
+ }));
3497
+ }
3498
+ /**
3499
+ * Compute new rects for all annotations based on the new group bounding box.
3500
+ */
3501
+ computeUnifiedResizedRects(participatingAnnotations, newGroupBox, minSize = 10) {
3502
+ const result = /* @__PURE__ */ new Map();
3503
+ for (const anno of participatingAnnotations) {
3504
+ const newWidth = Math.max(minSize, anno.relativeWidth * newGroupBox.size.width);
3505
+ const newHeight = Math.max(minSize, anno.relativeHeight * newGroupBox.size.height);
3506
+ result.set(anno.id, {
3507
+ origin: {
3508
+ x: newGroupBox.origin.x + anno.relativeX * newGroupBox.size.width,
3509
+ y: newGroupBox.origin.y + anno.relativeY * newGroupBox.size.height
3510
+ },
3511
+ size: {
3512
+ width: newWidth,
3513
+ height: newHeight
3514
+ }
3515
+ });
3516
+ }
3517
+ return result;
3518
+ }
3519
+ /**
3520
+ * Compute preview patches for all resize participants.
3521
+ * Uses transformAnnotation to properly handle vertices, inkList, etc.
3522
+ */
3523
+ computeResizePreviewPatches(computedRects, documentId) {
3524
+ const previewPatches = {};
3525
+ for (const [id, newRect] of computedRects) {
3526
+ const ta = this.getAnnotationById(id, documentId);
3527
+ if (!ta) continue;
3528
+ previewPatches[id] = this.transformAnnotation(ta.object, {
3529
+ type: "resize",
3530
+ changes: { rect: newRect }
3531
+ });
3532
+ }
3533
+ return previewPatches;
3534
+ }
3535
+ /**
3536
+ * Start a unified resize operation.
3537
+ * The plugin automatically expands the selection to include attached links.
3538
+ *
3539
+ * @param documentId - The document ID
3540
+ * @param options - Resize options
3541
+ */
3542
+ startResize(documentId, options) {
3543
+ const { annotationIds, pageSize, resizeHandle } = options;
3544
+ const attachedLinkIds = [];
3545
+ const annotationsWithLinks = [];
3546
+ for (const id of annotationIds) {
3547
+ const ta = this.getAnnotationById(id, documentId);
3548
+ if (ta) {
3549
+ annotationsWithLinks.push({
3550
+ id,
3551
+ rect: ta.object.rect,
3552
+ pageIndex: ta.object.pageIndex,
3553
+ isAttachedLink: false
3554
+ });
3555
+ const links = this.getAttachedLinksMethod(id, documentId);
3556
+ for (const link of links) {
3557
+ if (!attachedLinkIds.includes(link.object.id)) {
3558
+ attachedLinkIds.push(link.object.id);
3559
+ annotationsWithLinks.push({
3560
+ id: link.object.id,
3561
+ rect: link.object.rect,
3562
+ pageIndex: link.object.pageIndex,
3563
+ isAttachedLink: true,
3564
+ parentId: id
3565
+ });
3566
+ }
3567
+ }
3568
+ }
3569
+ }
3570
+ const allParticipantIds = [...annotationIds, ...attachedLinkIds];
3571
+ const rects = annotationsWithLinks.map((a) => a.rect);
3572
+ const groupBox = this.computeUnifiedGroupBoundingBox(rects);
3573
+ const participatingAnnotations = this.computeUnifiedRelativePositions(
3574
+ annotationsWithLinks,
3575
+ groupBox
3576
+ );
3577
+ const computedRects = this.computeUnifiedResizedRects(participatingAnnotations, groupBox);
3578
+ const state = {
3579
+ documentId,
3580
+ isResizing: true,
3581
+ primaryIds: annotationIds,
3582
+ attachedLinkIds,
3583
+ allParticipantIds,
3584
+ originalGroupBox: groupBox,
3585
+ currentGroupBox: groupBox,
3586
+ participatingAnnotations,
3587
+ resizeHandle,
3588
+ computedRects
3589
+ };
3590
+ this.unifiedResizeStates.set(documentId, state);
3591
+ const startPatches = this.computeResizePreviewPatches(computedRects, documentId);
3592
+ this.unifiedResize$.emit({
3593
+ documentId,
3594
+ type: "start",
3595
+ state,
3596
+ computedRects: Object.fromEntries(computedRects),
3597
+ previewPatches: startPatches
3598
+ });
3599
+ }
3600
+ /**
3601
+ * Update the resize with a new group bounding box.
3602
+ * Returns the computed rects synchronously for immediate preview use.
3603
+ *
3604
+ * @param documentId - The document ID
3605
+ * @param newGroupBox - The new group bounding box
3606
+ * @returns Record of annotation ID to new rect
3607
+ */
3608
+ updateResize(documentId, newGroupBox) {
3609
+ const state = this.unifiedResizeStates.get(documentId);
3610
+ if (!(state == null ? void 0 : state.isResizing)) {
3611
+ return {};
3612
+ }
3613
+ const computedRects = this.computeUnifiedResizedRects(
3614
+ state.participatingAnnotations,
3615
+ newGroupBox
3616
+ );
3617
+ const newState = {
3618
+ ...state,
3619
+ currentGroupBox: newGroupBox,
3620
+ computedRects
3621
+ };
3622
+ this.unifiedResizeStates.set(documentId, newState);
3623
+ const computedRectsObj = Object.fromEntries(computedRects);
3624
+ const previewPatches = this.computeResizePreviewPatches(computedRects, documentId);
3625
+ this.unifiedResize$.emit({
3626
+ documentId,
3627
+ type: "update",
3628
+ state: newState,
3629
+ computedRects: computedRectsObj,
3630
+ previewPatches
3631
+ });
3632
+ return computedRectsObj;
3633
+ }
3634
+ /**
3635
+ * Commit the resize - plugin builds and applies ALL patches.
3636
+ *
3637
+ * @param documentId - The document ID
3638
+ */
3639
+ commitResize(documentId) {
3640
+ const state = this.unifiedResizeStates.get(documentId);
3641
+ if (!state) return;
3642
+ const computedRects = this.computeUnifiedResizedRects(
3643
+ state.participatingAnnotations,
3644
+ state.currentGroupBox
3645
+ );
3646
+ const patches = [];
3647
+ for (const [id, newRect] of computedRects) {
3648
+ const ta = this.getAnnotationById(id, documentId);
3649
+ if (!ta) continue;
3650
+ const patch = this.transformAnnotation(ta.object, {
3651
+ type: "resize",
3652
+ changes: { rect: newRect }
3653
+ });
3654
+ patches.push({ pageIndex: ta.object.pageIndex, id, patch });
3655
+ }
3656
+ if (patches.length > 0) {
3657
+ this.updateAnnotationsMethod(patches, documentId);
3658
+ }
3659
+ const endPatches = this.computeResizePreviewPatches(computedRects, documentId);
3660
+ this.unifiedResize$.emit({
3661
+ documentId,
3662
+ type: "end",
3663
+ state: { ...state, isResizing: false },
3664
+ computedRects: Object.fromEntries(computedRects),
3665
+ previewPatches: endPatches
3666
+ });
3667
+ this.unifiedResizeStates.delete(documentId);
3668
+ }
3669
+ /**
3670
+ * Cancel the resize without committing.
3671
+ *
3672
+ * @param documentId - The document ID
3673
+ */
3674
+ cancelResize(documentId) {
3675
+ const state = this.unifiedResizeStates.get(documentId);
3676
+ if (!state) return;
3677
+ const originalRects = this.computeUnifiedResizedRects(
3678
+ state.participatingAnnotations,
3679
+ state.originalGroupBox
3680
+ );
3681
+ this.unifiedResize$.emit({
3682
+ documentId,
3683
+ type: "cancel",
3684
+ state: { ...state, isResizing: false, currentGroupBox: state.originalGroupBox },
3685
+ computedRects: Object.fromEntries(originalRects),
3686
+ previewPatches: {}
3687
+ });
3688
+ this.unifiedResizeStates.delete(documentId);
3689
+ }
3690
+ /**
3691
+ * Get the current unified resize state for a document.
3692
+ */
3693
+ getResizeState(documentId) {
3694
+ return this.unifiedResizeStates.get(documentId) ?? null;
3695
+ }
3696
+ /**
3697
+ * Subscribe to unified resize state changes.
3698
+ * Framework components use this for preview updates.
3699
+ */
3700
+ get onResizeChange() {
3701
+ return this.unifiedResize$.on;
3702
+ }
3703
+ updateAnnotationsMethod(patches, documentId) {
3704
+ const docId = documentId ?? this.getActiveDocumentId();
3705
+ if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3706
+ this.logger.debug(
3707
+ "AnnotationPlugin",
3708
+ "UpdateAnnotations",
3709
+ `Cannot update annotations: document ${docId} lacks ModifyAnnotations permission`
3710
+ );
3711
+ return;
3712
+ }
3713
+ const docState = this.getDocumentState(docId);
3714
+ const patchData = patches.map(({ pageIndex, id, patch }) => {
3715
+ var _a;
3716
+ const originalObject = (_a = docState.byUid[id]) == null ? void 0 : _a.object;
3717
+ if (!originalObject) return null;
3718
+ const finalPatch = this.buildPatch(originalObject, {
3719
+ ...patch,
3720
+ author: patch.author ?? this.config.annotationAuthor
3721
+ });
3722
+ return { pageIndex, id, patch: finalPatch, originalObject };
3723
+ }).filter((p) => p !== null);
3724
+ if (patchData.length === 0) return;
3725
+ const execute = () => {
3726
+ for (const { pageIndex, id, patch, originalObject } of patchData) {
3727
+ this.dispatch(patchAnnotation(docId, pageIndex, id, patch));
3728
+ this.events$.emit({
3729
+ type: "update",
3730
+ documentId: docId,
3731
+ annotation: originalObject,
3732
+ pageIndex,
3733
+ patch,
3734
+ committed: false
3735
+ });
3736
+ }
3737
+ };
3738
+ if (!this.history) {
3739
+ execute();
3740
+ if (this.config.autoCommit !== false) {
3741
+ this.commit(docId);
3742
+ }
3743
+ return;
3744
+ }
3745
+ const undoData = patchData.map(({ pageIndex, id, patch, originalObject }) => ({
3746
+ pageIndex,
3747
+ id,
3748
+ originalPatch: Object.fromEntries(
3749
+ Object.keys(patch).map((key) => [key, originalObject[key]])
3750
+ ),
3751
+ originalObject
3752
+ }));
3753
+ const command = {
3754
+ execute,
3755
+ undo: () => {
3756
+ for (const { pageIndex, id, originalPatch, originalObject } of undoData) {
3757
+ this.dispatch(patchAnnotation(docId, pageIndex, id, originalPatch));
3758
+ this.events$.emit({
3759
+ type: "update",
3760
+ documentId: docId,
3761
+ annotation: originalObject,
3762
+ pageIndex,
3763
+ patch: originalPatch,
3764
+ committed: false
3765
+ });
3766
+ }
3767
+ }
3768
+ };
3769
+ const historyScope = this.history.forDocument(docId);
3770
+ historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
3771
+ }
2754
3772
  getActiveTool(documentId) {
2755
3773
  const docState = this.getDocumentState(documentId);
2756
3774
  if (!docState.activeToolId) return null;
@@ -2759,6 +3777,14 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2759
3777
  setActiveTool(toolId, documentId) {
2760
3778
  var _a, _b;
2761
3779
  const docId = documentId ?? this.getActiveDocumentId();
3780
+ if (toolId !== null && !this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
3781
+ this.logger.debug(
3782
+ "AnnotationPlugin",
3783
+ "SetActiveTool",
3784
+ `Cannot activate tool: document ${docId} lacks ModifyAnnotations permission`
3785
+ );
3786
+ return;
3787
+ }
2762
3788
  const docState = this.getDocumentState(docId);
2763
3789
  if (toolId === docState.activeToolId) return;
2764
3790
  this.dispatch(setActiveToolId(docId, toolId));
@@ -2784,87 +3810,185 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2784
3810
  }
2785
3811
  return bestTool;
2786
3812
  }
2787
- commit(documentId) {
2788
- const task = new Task();
2789
- const docId = documentId ?? this.getActiveDocumentId();
3813
+ /**
3814
+ * Collects all pending annotation changes for a document into a batch.
3815
+ * This separates the "what to commit" from "how to commit" for cleaner code.
3816
+ */
3817
+ collectPendingChanges(docId, doc) {
2790
3818
  const docState = this.getDocumentState(docId);
2791
- if (!docState.hasPendingChanges) return PdfTaskHelper.resolve(true);
2792
- const coreDocState = this.getCoreDocument(docId);
2793
- const doc = coreDocState == null ? void 0 : coreDocState.document;
2794
- if (!doc)
2795
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
2796
3819
  const contexts = this.pendingContexts.get(docId);
2797
- if (!contexts) return PdfTaskHelper.resolve(true);
2798
- const creations = [];
2799
- const updates = [];
2800
- const deletions = [];
3820
+ const batch = {
3821
+ creations: [],
3822
+ updates: [],
3823
+ deletions: [],
3824
+ committedUids: [],
3825
+ isEmpty: true
3826
+ };
2801
3827
  for (const [uid, ta] of Object.entries(docState.byUid)) {
2802
3828
  if (ta.commitState === "synced") continue;
2803
3829
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2804
3830
  if (!page) continue;
3831
+ batch.committedUids.push(uid);
3832
+ batch.isEmpty = false;
2805
3833
  switch (ta.commitState) {
2806
3834
  case "new":
2807
- const ctx = contexts.get(ta.object.id);
2808
- const task2 = this.engine.createPageAnnotation(doc, page, ta.object, ctx);
2809
- task2.wait(() => {
2810
- this.events$.emit({
2811
- type: "create",
2812
- documentId: docId,
2813
- annotation: ta.object,
2814
- pageIndex: ta.object.pageIndex,
2815
- ctx,
2816
- committed: true
2817
- });
2818
- contexts.delete(ta.object.id);
2819
- }, ignore);
2820
- creations.push(task2);
3835
+ batch.creations.push({
3836
+ uid,
3837
+ ta,
3838
+ ctx: contexts == null ? void 0 : contexts.get(ta.object.id)
3839
+ });
2821
3840
  break;
2822
3841
  case "dirty":
2823
- const updateTask = this.engine.updatePageAnnotation(doc, page, ta.object);
2824
- updateTask.wait(() => {
2825
- this.events$.emit({
2826
- type: "update",
2827
- documentId: docId,
2828
- annotation: ta.object,
2829
- pageIndex: ta.object.pageIndex,
2830
- patch: ta.object,
2831
- committed: true
2832
- });
2833
- }, ignore);
2834
- updates.push(updateTask);
3842
+ batch.updates.push({ uid, ta });
2835
3843
  break;
2836
3844
  case "deleted":
2837
- deletions.push({ ta, uid });
3845
+ batch.deletions.push({ uid, ta });
2838
3846
  break;
2839
3847
  }
2840
3848
  }
2841
- const deletionTasks = [];
2842
- for (const { ta, uid } of deletions) {
3849
+ return batch;
3850
+ }
3851
+ /**
3852
+ * Executes a batch of pending changes by creating engine tasks.
3853
+ * Returns a task that resolves when all operations complete.
3854
+ */
3855
+ executeCommitBatch(docId, doc, batch) {
3856
+ const task = new Task();
3857
+ const contexts = this.pendingContexts.get(docId);
3858
+ const pendingOps = [];
3859
+ for (const { uid, ta, ctx } of batch.creations) {
3860
+ const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
3861
+ if (!page) continue;
3862
+ const createTask = this.engine.createPageAnnotation(doc, page, ta.object, ctx);
3863
+ pendingOps.push({ type: "create", task: createTask, ta, uid, ctx });
3864
+ }
3865
+ for (const { uid, ta } of batch.updates) {
3866
+ const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
3867
+ if (!page) continue;
3868
+ const updateTask = this.engine.updatePageAnnotation(doc, page, ta.object);
3869
+ pendingOps.push({ type: "update", task: updateTask, ta, uid });
3870
+ }
3871
+ for (const { uid, ta } of batch.deletions) {
2843
3872
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2844
- if (ta.commitState === "deleted" && ta.object.id) {
2845
- const task2 = new Task();
3873
+ if (!page) continue;
3874
+ if (ta.object.id) {
3875
+ const deleteTask = new Task();
2846
3876
  const removeTask = this.engine.removePageAnnotation(doc, page, ta.object);
2847
- removeTask.wait(() => {
2848
- this.dispatch(purgeAnnotation(docId, uid));
3877
+ removeTask.wait(() => deleteTask.resolve(true), deleteTask.fail);
3878
+ pendingOps.push({ type: "delete", task: deleteTask, ta, uid });
3879
+ } else {
3880
+ this.dispatch(purgeAnnotation(docId, uid));
3881
+ }
3882
+ }
3883
+ const allTasks = pendingOps.map((op) => op.task);
3884
+ Task.allSettled(allTasks).wait(
3885
+ () => {
3886
+ this.emitCommitEvents(docId, pendingOps, contexts);
3887
+ this.dispatch(commitPendingChanges(docId, batch.committedUids));
3888
+ task.resolve(true);
3889
+ },
3890
+ (error) => task.fail(error)
3891
+ );
3892
+ return task;
3893
+ }
3894
+ /**
3895
+ * Emits commit events for all completed operations.
3896
+ * Centralizes event emission for cleaner separation of concerns.
3897
+ */
3898
+ emitCommitEvents(docId, operations, contexts) {
3899
+ for (const op of operations) {
3900
+ if (op.task.state.stage !== TaskStage.Resolved) continue;
3901
+ switch (op.type) {
3902
+ case "create":
3903
+ this.events$.emit({
3904
+ type: "create",
3905
+ documentId: docId,
3906
+ annotation: op.ta.object,
3907
+ pageIndex: op.ta.object.pageIndex,
3908
+ ctx: op.ctx,
3909
+ committed: true
3910
+ });
3911
+ contexts == null ? void 0 : contexts.delete(op.ta.object.id);
3912
+ break;
3913
+ case "update":
3914
+ this.events$.emit({
3915
+ type: "update",
3916
+ documentId: docId,
3917
+ annotation: op.ta.object,
3918
+ pageIndex: op.ta.object.pageIndex,
3919
+ patch: op.ta.object,
3920
+ committed: true
3921
+ });
3922
+ break;
3923
+ case "delete":
3924
+ this.dispatch(purgeAnnotation(docId, op.uid));
2849
3925
  this.events$.emit({
2850
3926
  type: "delete",
2851
3927
  documentId: docId,
2852
- annotation: ta.object,
2853
- pageIndex: ta.object.pageIndex,
3928
+ annotation: op.ta.object,
3929
+ pageIndex: op.ta.object.pageIndex,
2854
3930
  committed: true
2855
3931
  });
2856
- task2.resolve(true);
2857
- }, task2.fail);
2858
- deletionTasks.push(task2);
2859
- } else {
2860
- this.dispatch(purgeAnnotation(docId, uid));
3932
+ break;
2861
3933
  }
2862
3934
  }
2863
- const allWriteTasks = [...creations, ...updates, ...deletionTasks];
2864
- Task.allSettled(allWriteTasks).wait(() => {
2865
- this.dispatch(commitPendingChanges(docId));
2866
- task.resolve(true);
2867
- }, task.fail);
3935
+ }
3936
+ /**
3937
+ * Attempts to acquire the commit lock for a document.
3938
+ * Returns true if acquired, false if a commit is already in progress.
3939
+ */
3940
+ acquireCommitLock(docId) {
3941
+ if (this.commitInProgress.get(docId)) {
3942
+ return false;
3943
+ }
3944
+ this.commitInProgress.set(docId, true);
3945
+ return true;
3946
+ }
3947
+ /**
3948
+ * Releases the commit lock for a document.
3949
+ */
3950
+ releaseCommitLock(docId) {
3951
+ this.commitInProgress.set(docId, false);
3952
+ }
3953
+ commit(documentId) {
3954
+ const docId = documentId ?? this.getActiveDocumentId();
3955
+ const docState = this.getDocumentState(docId);
3956
+ if (!docState.hasPendingChanges) {
3957
+ return PdfTaskHelper.resolve(true);
3958
+ }
3959
+ if (!this.acquireCommitLock(docId)) {
3960
+ return PdfTaskHelper.resolve(true);
3961
+ }
3962
+ const coreDocState = this.getCoreDocument(docId);
3963
+ const doc = coreDocState == null ? void 0 : coreDocState.document;
3964
+ if (!doc) {
3965
+ this.releaseCommitLock(docId);
3966
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
3967
+ }
3968
+ const batch = this.collectPendingChanges(docId, doc);
3969
+ if (batch.isEmpty) {
3970
+ this.releaseCommitLock(docId);
3971
+ return PdfTaskHelper.resolve(true);
3972
+ }
3973
+ const task = new Task();
3974
+ this.executeCommitBatch(docId, doc, batch).wait(
3975
+ () => {
3976
+ this.releaseCommitLock(docId);
3977
+ const updatedDocState = this.getDocumentState(docId);
3978
+ if (updatedDocState.hasPendingChanges) {
3979
+ this.commit(docId).wait(
3980
+ (result) => task.resolve(result),
3981
+ (error) => task.fail(error)
3982
+ );
3983
+ } else {
3984
+ task.resolve(true);
3985
+ }
3986
+ },
3987
+ (error) => {
3988
+ this.releaseCommitLock(docId);
3989
+ task.fail(error);
3990
+ }
3991
+ );
2868
3992
  return task;
2869
3993
  }
2870
3994
  /**
@@ -2913,11 +4037,23 @@ export {
2913
4037
  getAnnotationByUid,
2914
4038
  getAnnotations,
2915
4039
  getAnnotationsByPageIndex,
4040
+ getAttachedLinks,
4041
+ getGroupLeaderId,
4042
+ getGroupMembers,
4043
+ getIRTChildIds,
4044
+ getIRTChildrenByType,
2916
4045
  getSelectedAnnotation,
2917
4046
  getSelectedAnnotationByPageIndex,
4047
+ getSelectedAnnotationIds,
4048
+ getSelectedAnnotations,
4049
+ getSelectedAnnotationsByPageIndex,
4050
+ getSelectionGroupingAction,
2918
4051
  getSidebarAnnotationsWithReplies,
2919
4052
  getSidebarAnnotationsWithRepliesGroupedByPage,
2920
4053
  getToolDefaultsById,
4054
+ hasAttachedLinks,
4055
+ hasIRTChildren,
4056
+ hasMultipleSelected,
2921
4057
  initialDocumentState,
2922
4058
  initialState,
2923
4059
  isAnnotationSelected,
@@ -2927,11 +4063,13 @@ export {
2927
4063
  isFreeTextTool,
2928
4064
  isHighlight,
2929
4065
  isHighlightTool,
4066
+ isInGroup,
2930
4067
  isInk,
2931
4068
  isInkHighlighterTool,
2932
4069
  isInkTool,
2933
4070
  isLine,
2934
4071
  isLineTool,
4072
+ isLink,
2935
4073
  isPolygon,
2936
4074
  isPolygonTool,
2937
4075
  isPolyline,
@@ -2950,6 +4088,7 @@ export {
2950
4088
  isUnderline,
2951
4089
  isUnderlineTool,
2952
4090
  manifest,
2953
- index as patching
4091
+ index as patching,
4092
+ rectsIntersect
2954
4093
  };
2955
4094
  //# sourceMappingURL=index.js.map