@embedpdf/plugin-annotation 2.2.0 → 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 +1196 -92
  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 +857 -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 +857 -403
  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 +905 -255
  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 +946 -406
  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, 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,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,28 @@ 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;
2323
2578
  if (!this.checkPermission(documentId, PdfPermissionFlag.ModifyAnnotations)) {
2324
2579
  return;
2325
2580
  }
@@ -2355,7 +2610,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2355
2610
  }
2356
2611
  }, ignore);
2357
2612
  }
2358
- (_c = this.selection) == null ? void 0 : _c.clear();
2613
+ (_c2 = this.selection) == null ? void 0 : _c2.clear();
2359
2614
  });
2360
2615
  }
2361
2616
  registerInteractionForTool(tool) {
@@ -2375,15 +2630,32 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2375
2630
  getState: () => this.getDocumentState(),
2376
2631
  getPageAnnotations: (options) => this.getPageAnnotations(options),
2377
2632
  getSelectedAnnotation: () => this.getSelectedAnnotation(),
2633
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(),
2634
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(),
2378
2635
  getAnnotationById: (id) => this.getAnnotationById(id),
2379
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),
2380
2641
  deselectAnnotation: () => this.deselectAnnotation(),
2381
2642
  importAnnotations: (items) => this.importAnnotations(items),
2382
2643
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx),
2383
2644
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch),
2645
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches),
2384
2646
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id),
2647
+ deleteAnnotations: (annotations, documentId) => this.deleteAnnotationsMethod(annotations, documentId),
2385
2648
  renderAnnotation: (options) => this.renderAnnotation(options),
2386
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),
2387
2659
  // Document-scoped operations
2388
2660
  forDocument: (documentId) => this.createAnnotationScope(documentId),
2389
2661
  // Global operations
@@ -2414,8 +2686,14 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2414
2686
  getState: () => this.getDocumentState(documentId),
2415
2687
  getPageAnnotations: (options) => this.getPageAnnotations(options, documentId),
2416
2688
  getSelectedAnnotation: () => this.getSelectedAnnotation(documentId),
2689
+ getSelectedAnnotations: () => this.getSelectedAnnotationsMethod(documentId),
2690
+ getSelectedAnnotationIds: () => this.getSelectedAnnotationIdsMethod(documentId),
2417
2691
  getAnnotationById: (id) => this.getAnnotationById(id, documentId),
2418
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),
2419
2697
  deselectAnnotation: () => this.deselectAnnotation(documentId),
2420
2698
  getActiveTool: () => this.getActiveTool(documentId),
2421
2699
  setActiveTool: (toolId) => this.setActiveTool(toolId, documentId),
@@ -2423,9 +2701,19 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2423
2701
  importAnnotations: (items) => this.importAnnotations(items, documentId),
2424
2702
  createAnnotation: (pageIndex, anno, ctx) => this.createAnnotation(pageIndex, anno, ctx, documentId),
2425
2703
  updateAnnotation: (pageIndex, id, patch) => this.updateAnnotation(pageIndex, id, patch, documentId),
2704
+ updateAnnotations: (patches) => this.updateAnnotationsMethod(patches, documentId),
2426
2705
  deleteAnnotation: (pageIndex, id) => this.deleteAnnotation(pageIndex, id, documentId),
2706
+ deleteAnnotations: (annotations) => this.deleteAnnotationsMethod(annotations, documentId),
2427
2707
  renderAnnotation: (options) => this.renderAnnotation(options, documentId),
2428
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),
2429
2717
  onStateChange: (listener) => this.state$.on((event) => {
2430
2718
  if (event.documentId === documentId) listener(event.state);
2431
2719
  }),
@@ -2738,7 +3026,26 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2738
3026
  const docState = this.getDocumentState(docId);
2739
3027
  const originalAnnotation = (_a = docState.byUid[id]) == null ? void 0 : _a.object;
2740
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);
2741
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
+ }
2742
3049
  this.dispatch(deselectAnnotation(docId));
2743
3050
  this.dispatch(deleteAnnotation(docId, pageIndex, id));
2744
3051
  this.events$.emit({
@@ -2765,19 +3072,703 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2765
3072
  pageIndex,
2766
3073
  committed: false
2767
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
+ }
2768
3085
  }
2769
3086
  };
2770
3087
  const historyScope = this.history.forDocument(docId);
2771
3088
  historyScope.register(command, this.ANNOTATION_HISTORY_TOPIC);
2772
3089
  }
3090
+ deleteAnnotationsMethod(annotations, documentId) {
3091
+ for (const { pageIndex, id } of annotations) {
3092
+ this.deleteAnnotation(pageIndex, id, documentId);
3093
+ }
3094
+ }
2773
3095
  selectAnnotation(pageIndex, id, documentId) {
2774
3096
  const docId = documentId ?? this.getActiveDocumentId();
2775
- 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
+ }
2776
3104
  }
2777
3105
  deselectAnnotation(documentId) {
2778
3106
  const docId = documentId ?? this.getActiveDocumentId();
2779
3107
  this.dispatch(deselectAnnotation(docId));
2780
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
+ }
2781
3772
  getActiveTool(documentId) {
2782
3773
  const docState = this.getDocumentState(documentId);
2783
3774
  if (!docState.activeToolId) return null;
@@ -2819,87 +3810,185 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
2819
3810
  }
2820
3811
  return bestTool;
2821
3812
  }
2822
- commit(documentId) {
2823
- const task = new Task();
2824
- 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) {
2825
3818
  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
3819
  const contexts = this.pendingContexts.get(docId);
2832
- if (!contexts) return PdfTaskHelper.resolve(true);
2833
- const creations = [];
2834
- const updates = [];
2835
- const deletions = [];
3820
+ const batch = {
3821
+ creations: [],
3822
+ updates: [],
3823
+ deletions: [],
3824
+ committedUids: [],
3825
+ isEmpty: true
3826
+ };
2836
3827
  for (const [uid, ta] of Object.entries(docState.byUid)) {
2837
3828
  if (ta.commitState === "synced") continue;
2838
3829
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2839
3830
  if (!page) continue;
3831
+ batch.committedUids.push(uid);
3832
+ batch.isEmpty = false;
2840
3833
  switch (ta.commitState) {
2841
3834
  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);
3835
+ batch.creations.push({
3836
+ uid,
3837
+ ta,
3838
+ ctx: contexts == null ? void 0 : contexts.get(ta.object.id)
3839
+ });
2856
3840
  break;
2857
3841
  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);
3842
+ batch.updates.push({ uid, ta });
2870
3843
  break;
2871
3844
  case "deleted":
2872
- deletions.push({ ta, uid });
3845
+ batch.deletions.push({ uid, ta });
2873
3846
  break;
2874
3847
  }
2875
3848
  }
2876
- const deletionTasks = [];
2877
- 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) {
2878
3866
  const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
2879
- if (ta.commitState === "deleted" && ta.object.id) {
2880
- const task2 = new Task();
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) {
3872
+ const page = doc.pages.find((p) => p.index === ta.object.pageIndex);
3873
+ if (!page) continue;
3874
+ if (ta.object.id) {
3875
+ const deleteTask = new Task();
2881
3876
  const removeTask = this.engine.removePageAnnotation(doc, page, ta.object);
2882
- removeTask.wait(() => {
2883
- 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));
2884
3925
  this.events$.emit({
2885
3926
  type: "delete",
2886
3927
  documentId: docId,
2887
- annotation: ta.object,
2888
- pageIndex: ta.object.pageIndex,
3928
+ annotation: op.ta.object,
3929
+ pageIndex: op.ta.object.pageIndex,
2889
3930
  committed: true
2890
3931
  });
2891
- task2.resolve(true);
2892
- }, task2.fail);
2893
- deletionTasks.push(task2);
2894
- } else {
2895
- this.dispatch(purgeAnnotation(docId, uid));
3932
+ break;
2896
3933
  }
2897
3934
  }
2898
- const allWriteTasks = [...creations, ...updates, ...deletionTasks];
2899
- Task.allSettled(allWriteTasks).wait(() => {
2900
- this.dispatch(commitPendingChanges(docId));
2901
- task.resolve(true);
2902
- }, 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
+ );
2903
3992
  return task;
2904
3993
  }
2905
3994
  /**
@@ -2948,11 +4037,23 @@ export {
2948
4037
  getAnnotationByUid,
2949
4038
  getAnnotations,
2950
4039
  getAnnotationsByPageIndex,
4040
+ getAttachedLinks,
4041
+ getGroupLeaderId,
4042
+ getGroupMembers,
4043
+ getIRTChildIds,
4044
+ getIRTChildrenByType,
2951
4045
  getSelectedAnnotation,
2952
4046
  getSelectedAnnotationByPageIndex,
4047
+ getSelectedAnnotationIds,
4048
+ getSelectedAnnotations,
4049
+ getSelectedAnnotationsByPageIndex,
4050
+ getSelectionGroupingAction,
2953
4051
  getSidebarAnnotationsWithReplies,
2954
4052
  getSidebarAnnotationsWithRepliesGroupedByPage,
2955
4053
  getToolDefaultsById,
4054
+ hasAttachedLinks,
4055
+ hasIRTChildren,
4056
+ hasMultipleSelected,
2956
4057
  initialDocumentState,
2957
4058
  initialState,
2958
4059
  isAnnotationSelected,
@@ -2962,11 +4063,13 @@ export {
2962
4063
  isFreeTextTool,
2963
4064
  isHighlight,
2964
4065
  isHighlightTool,
4066
+ isInGroup,
2965
4067
  isInk,
2966
4068
  isInkHighlighterTool,
2967
4069
  isInkTool,
2968
4070
  isLine,
2969
4071
  isLineTool,
4072
+ isLink,
2970
4073
  isPolygon,
2971
4074
  isPolygonTool,
2972
4075
  isPolyline,
@@ -2985,6 +4088,7 @@ export {
2985
4088
  isUnderline,
2986
4089
  isUnderlineTool,
2987
4090
  manifest,
2988
- index as patching
4091
+ index as patching,
4092
+ rectsIntersect
2989
4093
  };
2990
4094
  //# sourceMappingURL=index.js.map