@embedpdf/plugin-annotation 2.5.0 → 2.6.1

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 (71) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1061 -430
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/annotation-plugin.d.ts +24 -6
  6. package/dist/lib/geometry/index.d.ts +1 -0
  7. package/dist/lib/geometry/rotation.d.ts +32 -0
  8. package/dist/lib/handlers/types.d.ts +3 -1
  9. package/dist/lib/index.d.ts +1 -0
  10. package/dist/lib/patching/base-patch.d.ts +87 -0
  11. package/dist/lib/patching/index.d.ts +1 -0
  12. package/dist/lib/patching/insert-upright.d.ts +20 -0
  13. package/dist/lib/patching/patch-registry.d.ts +14 -1
  14. package/dist/lib/patching/patch-utils.d.ts +54 -1
  15. package/dist/lib/patching/patches/circle.patch.d.ts +3 -0
  16. package/dist/lib/patching/patches/freetext.patch.d.ts +3 -0
  17. package/dist/lib/patching/patches/index.d.ts +4 -0
  18. package/dist/lib/patching/patches/square.patch.d.ts +3 -0
  19. package/dist/lib/patching/patches/stamp.patch.d.ts +3 -0
  20. package/dist/lib/tools/default-tools.d.ts +32 -0
  21. package/dist/lib/tools/types.d.ts +20 -1
  22. package/dist/lib/types.d.ts +67 -3
  23. package/dist/preact/adapter.d.ts +3 -0
  24. package/dist/preact/index.cjs +1 -1
  25. package/dist/preact/index.cjs.map +1 -1
  26. package/dist/preact/index.js +793 -126
  27. package/dist/preact/index.js.map +1 -1
  28. package/dist/react/adapter.d.ts +2 -1
  29. package/dist/react/index.cjs +1 -1
  30. package/dist/react/index.cjs.map +1 -1
  31. package/dist/react/index.js +793 -126
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/shared/components/annotation-container.d.ts +10 -2
  34. package/dist/shared/components/annotation-layer.d.ts +9 -3
  35. package/dist/shared/components/annotations.d.ts +4 -1
  36. package/dist/shared/components/group-selection-box.d.ts +12 -4
  37. package/dist/shared/components/render-annotation.d.ts +2 -1
  38. package/dist/shared/components/types.d.ts +51 -1
  39. package/dist/shared-preact/components/annotation-container.d.ts +10 -2
  40. package/dist/shared-preact/components/annotation-layer.d.ts +9 -3
  41. package/dist/shared-preact/components/annotations.d.ts +4 -1
  42. package/dist/shared-preact/components/group-selection-box.d.ts +12 -4
  43. package/dist/shared-preact/components/render-annotation.d.ts +2 -1
  44. package/dist/shared-preact/components/types.d.ts +51 -1
  45. package/dist/shared-react/components/annotation-container.d.ts +10 -2
  46. package/dist/shared-react/components/annotation-layer.d.ts +9 -3
  47. package/dist/shared-react/components/annotations.d.ts +4 -1
  48. package/dist/shared-react/components/group-selection-box.d.ts +12 -4
  49. package/dist/shared-react/components/render-annotation.d.ts +2 -1
  50. package/dist/shared-react/components/types.d.ts +51 -1
  51. package/dist/svelte/components/AnnotationLayer.svelte.d.ts +8 -2
  52. package/dist/svelte/components/Annotations.svelte.d.ts +7 -1
  53. package/dist/svelte/components/GroupSelectionBox.svelte.d.ts +11 -3
  54. package/dist/svelte/components/RenderAnnotation.svelte.d.ts +1 -0
  55. package/dist/svelte/components/types.d.ts +14 -1
  56. package/dist/svelte/index.cjs +1 -1
  57. package/dist/svelte/index.cjs.map +1 -1
  58. package/dist/svelte/index.js +1166 -330
  59. package/dist/svelte/index.js.map +1 -1
  60. package/dist/svelte/types.d.ts +53 -0
  61. package/dist/vue/components/annotation-container.vue.d.ts +35 -9
  62. package/dist/vue/components/annotation-layer.vue.d.ts +29 -5
  63. package/dist/vue/components/annotations.vue.d.ts +278 -134
  64. package/dist/vue/components/group-selection-box.vue.d.ts +35 -10
  65. package/dist/vue/components/render-annotation.vue.d.ts +2 -0
  66. package/dist/vue/index.cjs +1 -1
  67. package/dist/vue/index.cjs.map +1 -1
  68. package/dist/vue/index.js +945 -161
  69. package/dist/vue/index.js.map +1 -1
  70. package/dist/vue/types.d.ts +52 -0
  71. package/package.json +11 -10
@@ -1,10 +1,11 @@
1
1
  import { createPluginPackage } from "@embedpdf/core";
2
- import { AnnotationPlugin, initialDocumentState, patching, getAnnotationsByPageIndex, getSelectedAnnotationIds, resolveInteractionProp, isInk, isSquare, isCircle, isUnderline, isStrikeout, isSquiggly, isHighlight, isLine, isPolyline, isPolygon, isFreeText, isStamp, isLink, AnnotationPluginPackage as AnnotationPluginPackage$1 } from "@embedpdf/plugin-annotation";
2
+ import { initialDocumentState, AnnotationPlugin, patching, getAnnotationsByPageIndex, getSelectedAnnotationIds, resolveInteractionProp, isInk, isSquare, isCircle, isUnderline, isStrikeout, isSquiggly, isHighlight, isLine, isPolyline, isPolygon, isFreeText, isStamp, isLink, AnnotationPluginPackage as AnnotationPluginPackage$1 } from "@embedpdf/plugin-annotation";
3
3
  export * from "@embedpdf/plugin-annotation";
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
5
  import { createContext, useState, useCallback, useContext, useRef, useEffect, useMemo, useLayoutEffect, Fragment as Fragment$1 } from "react";
6
- import { usePlugin, useCapability, useDocumentPermissions, useDocumentState } from "@embedpdf/core/react";
7
- import { boundingRectOrEmpty, PdfAnnotationBorderStyle, textAlignmentToCss, standardFontCss, PdfVerticalAlignment, ignore, PdfErrorCode, blendModeToCss, PdfBlendMode, PdfAnnotationSubtype } from "@embedpdf/models";
6
+ import { createPortal } from "react-dom";
7
+ import { useCapability, usePlugin, useDocumentPermissions, useDocumentState } from "@embedpdf/core/react";
8
+ import { inferRotationCenterFromRects, boundingRectOrEmpty, PdfAnnotationBorderStyle, PdfVerticalAlignment, textAlignmentToCss, standardFontCssProperties, ignore, PdfErrorCode, blendModeToCss, PdfBlendMode, PdfAnnotationSubtype } from "@embedpdf/models";
8
9
  import { usePointerHandlers } from "@embedpdf/plugin-interaction-manager/react";
9
10
  import { useSelectionCapability } from "@embedpdf/plugin-selection/react";
10
11
  import { useInteractionHandles, useDoublePressProps, CounterRotate } from "@embedpdf/utils/react";
@@ -72,6 +73,7 @@ function AnnotationContainer({
72
73
  isMultiSelected = false,
73
74
  isDraggable,
74
75
  isResizable,
76
+ isRotatable = true,
75
77
  lockAspectRatio = false,
76
78
  style = {},
77
79
  vertexConfig,
@@ -82,20 +84,28 @@ function AnnotationContainer({
82
84
  zIndex = 1,
83
85
  resizeUI,
84
86
  vertexUI,
85
- selectionOutlineColor = "#007ACC",
87
+ rotationUI,
88
+ selectionOutlineColor,
89
+ selectionOutline,
86
90
  customAnnotationRenderer,
87
91
  // Destructure props that shouldn't be passed to DOM elements
88
92
  groupSelectionMenu: _groupSelectionMenu,
93
+ groupSelectionOutline: _groupSelectionOutline,
89
94
  annotationRenderers: _annotationRenderers,
90
95
  ...props
91
96
  }) {
97
+ var _a, _b, _c;
92
98
  const [preview, setPreview] = useState(trackedAnnotation.object);
99
+ const [liveRotation, setLiveRotation] = useState(null);
100
+ const [cursorScreen, setCursorScreen] = useState(null);
101
+ const [isHandleHovered, setIsHandleHovered] = useState(false);
93
102
  const { provides: annotationCapability } = useAnnotationCapability();
94
103
  const { plugin } = useAnnotationPlugin();
95
104
  const { canModifyAnnotations } = useDocumentPermissions(documentId);
96
105
  const gestureBaseRef = useRef(null);
97
106
  const effectiveIsDraggable = canModifyAnnotations && isDraggable && !isMultiSelected;
98
107
  const effectiveIsResizable = canModifyAnnotations && isResizable && !isMultiSelected;
108
+ const effectiveIsRotatable = canModifyAnnotations && isRotatable && !isMultiSelected;
99
109
  const annotationProvides = useMemo(
100
110
  () => annotationCapability ? annotationCapability.forDocument(documentId) : null,
101
111
  [annotationCapability, documentId]
@@ -103,18 +113,35 @@ function AnnotationContainer({
103
113
  const currentObject = preview ? { ...trackedAnnotation.object, ...preview } : trackedAnnotation.object;
104
114
  const HANDLE_COLOR = (resizeUI == null ? void 0 : resizeUI.color) ?? "#007ACC";
105
115
  const VERTEX_COLOR = (vertexUI == null ? void 0 : vertexUI.color) ?? "#007ACC";
116
+ const ROTATION_COLOR = (rotationUI == null ? void 0 : rotationUI.color) ?? "white";
117
+ const ROTATION_CONNECTOR_COLOR = (rotationUI == null ? void 0 : rotationUI.connectorColor) ?? "#007ACC";
106
118
  const HANDLE_SIZE = (resizeUI == null ? void 0 : resizeUI.size) ?? 12;
107
119
  const VERTEX_SIZE = (vertexUI == null ? void 0 : vertexUI.size) ?? 12;
120
+ const ROTATION_SIZE = (rotationUI == null ? void 0 : rotationUI.size) ?? 32;
121
+ const ROTATION_MARGIN = rotationUI == null ? void 0 : rotationUI.margin;
122
+ const ROTATION_ICON_COLOR = (rotationUI == null ? void 0 : rotationUI.iconColor) ?? "#007ACC";
123
+ const SHOW_CONNECTOR = (rotationUI == null ? void 0 : rotationUI.showConnector) ?? false;
124
+ const ROTATION_BORDER_COLOR = ((_a = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _a.color) ?? "#007ACC";
125
+ const ROTATION_BORDER_WIDTH = ((_b = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _b.width) ?? 1;
126
+ const ROTATION_BORDER_STYLE = ((_c = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _c.style) ?? "solid";
127
+ const outlineColor = (selectionOutline == null ? void 0 : selectionOutline.color) ?? selectionOutlineColor ?? "#007ACC";
128
+ const outlineStyle = (selectionOutline == null ? void 0 : selectionOutline.style) ?? "solid";
129
+ const outlineWidth = (selectionOutline == null ? void 0 : selectionOutline.width) ?? 1;
130
+ const outlineOff = (selectionOutline == null ? void 0 : selectionOutline.offset) ?? outlineOffset ?? 1;
131
+ const annotationRotation = liveRotation ?? currentObject.rotation ?? 0;
132
+ const rotationDisplay = liveRotation ?? currentObject.rotation ?? 0;
133
+ const normalizedRotationDisplay = Number.isFinite(rotationDisplay) ? Math.round(rotationDisplay * 10) / 10 : 0;
134
+ const rotationActive = liveRotation !== null;
108
135
  const gestureBaseRectRef = useRef(null);
109
136
  const handleUpdate = useCallback(
110
137
  (event) => {
111
- var _a;
112
- if (!((_a = event.transformData) == null ? void 0 : _a.type) || isMultiSelected || !plugin) return;
138
+ var _a2;
139
+ if (!((_a2 = event.transformData) == null ? void 0 : _a2.type) || isMultiSelected || !plugin) return;
113
140
  const { type, changes, metadata } = event.transformData;
114
141
  const id = trackedAnnotation.object.id;
115
142
  const pageSize = { width: pageWidth, height: pageHeight };
116
143
  if (event.state === "start") {
117
- gestureBaseRectRef.current = trackedAnnotation.object.rect;
144
+ gestureBaseRectRef.current = trackedAnnotation.object.unrotatedRect ?? trackedAnnotation.object.rect;
118
145
  gestureBaseRef.current = trackedAnnotation.object;
119
146
  if (type === "move") {
120
147
  plugin.startDrag(documentId, { annotationIds: [id], pageSize });
@@ -152,6 +179,27 @@ function AnnotationContainer({
152
179
  }
153
180
  }
154
181
  }
182
+ if (type === "rotate") {
183
+ const cursorAngle = (metadata == null ? void 0 : metadata.rotationAngle) ?? annotationRotation;
184
+ const cursorPos = metadata == null ? void 0 : metadata.cursorPosition;
185
+ if (cursorPos) setCursorScreen({ x: cursorPos.clientX, y: cursorPos.clientY });
186
+ if (event.state === "start") {
187
+ setLiveRotation(cursorAngle);
188
+ plugin.startRotation(documentId, {
189
+ annotationIds: [id],
190
+ cursorAngle,
191
+ rotationCenter: metadata == null ? void 0 : metadata.rotationCenter
192
+ });
193
+ } else if (event.state === "move") {
194
+ setLiveRotation(cursorAngle);
195
+ plugin.updateRotation(documentId, cursorAngle, metadata == null ? void 0 : metadata.rotationDelta);
196
+ } else if (event.state === "end") {
197
+ setLiveRotation(null);
198
+ setCursorScreen(null);
199
+ plugin.commitRotation(documentId);
200
+ }
201
+ return;
202
+ }
155
203
  if (event.state === "end") {
156
204
  gestureBaseRectRef.current = null;
157
205
  gestureBaseRef.current = null;
@@ -169,12 +217,22 @@ function AnnotationContainer({
169
217
  isMultiSelected,
170
218
  vertexConfig,
171
219
  annotationCapability,
172
- annotationProvides
220
+ annotationProvides,
221
+ annotationRotation
173
222
  ]
174
223
  );
175
- const { dragProps, vertices, resize } = useInteractionHandles({
224
+ const explicitUnrotatedRect = currentObject.unrotatedRect;
225
+ const effectiveUnrotatedRect = explicitUnrotatedRect ?? currentObject.rect;
226
+ const rotationPivot = explicitUnrotatedRect && annotationRotation !== 0 ? inferRotationCenterFromRects(effectiveUnrotatedRect, currentObject.rect, annotationRotation) : void 0;
227
+ const controllerElement = effectiveUnrotatedRect;
228
+ const {
229
+ dragProps,
230
+ vertices,
231
+ resize,
232
+ rotation: rotationHandle
233
+ } = useInteractionHandles({
176
234
  controller: {
177
- element: currentObject.rect,
235
+ element: controllerElement,
178
236
  vertices: vertexConfig == null ? void 0 : vertexConfig.extractVertices(currentObject),
179
237
  constraints: {
180
238
  minWidth: 10,
@@ -183,6 +241,9 @@ function AnnotationContainer({
183
241
  },
184
242
  maintainAspectRatio: lockAspectRatio,
185
243
  pageRotation: rotation,
244
+ annotationRotation,
245
+ rotationCenter: rotationPivot,
246
+ rotationElement: currentObject.rect,
186
247
  scale,
187
248
  // Disable interaction handles when multi-selected
188
249
  enabled: isSelected && !isMultiSelected,
@@ -190,7 +251,7 @@ function AnnotationContainer({
190
251
  },
191
252
  resizeUI: {
192
253
  handleSize: HANDLE_SIZE,
193
- spacing: outlineOffset,
254
+ spacing: outlineOff,
194
255
  offsetMode: "outside",
195
256
  includeSides: lockAspectRatio ? false : true,
196
257
  zIndex: zIndex + 1
@@ -199,7 +260,15 @@ function AnnotationContainer({
199
260
  vertexSize: VERTEX_SIZE,
200
261
  zIndex: zIndex + 2
201
262
  },
202
- includeVertices: vertexConfig ? true : false
263
+ rotationUI: {
264
+ handleSize: ROTATION_SIZE,
265
+ margin: ROTATION_MARGIN,
266
+ zIndex: zIndex + 3,
267
+ showConnector: SHOW_CONNECTOR
268
+ },
269
+ includeVertices: vertexConfig ? true : false,
270
+ includeRotation: effectiveIsRotatable,
271
+ currentRotation: annotationRotation
203
272
  });
204
273
  const guardedOnDoubleClick = useMemo(() => {
205
274
  if (!canModifyAnnotations || !onDoubleClick) return void 0;
@@ -213,88 +282,268 @@ function AnnotationContainer({
213
282
  if (!plugin) return;
214
283
  const id = trackedAnnotation.object.id;
215
284
  const handleEvent = (event) => {
216
- var _a;
285
+ var _a2;
217
286
  if (event.documentId !== documentId) return;
218
- const patch = (_a = event.previewPatches) == null ? void 0 : _a[id];
287
+ if (event.type === "end" || event.type === "cancel") {
288
+ setLiveRotation(null);
289
+ }
290
+ const patch = (_a2 = event.previewPatches) == null ? void 0 : _a2[id];
219
291
  if (event.type === "update" && patch) setPreview((prev) => ({ ...prev, ...patch }));
220
292
  else if (event.type === "cancel") setPreview(trackedAnnotation.object);
221
293
  };
222
- const unsubs = [plugin.onDragChange(handleEvent), plugin.onResizeChange(handleEvent)];
294
+ const unsubs = [
295
+ plugin.onDragChange(handleEvent),
296
+ plugin.onResizeChange(handleEvent),
297
+ plugin.onRotateChange(handleEvent)
298
+ ];
223
299
  return () => unsubs.forEach((u) => u());
224
300
  }, [plugin, documentId, trackedAnnotation.object]);
225
301
  const showOutline = isSelected && !isMultiSelected;
302
+ const aabbWidth = currentObject.rect.size.width * scale;
303
+ const aabbHeight = currentObject.rect.size.height * scale;
304
+ const innerWidth = effectiveUnrotatedRect.size.width * scale;
305
+ const innerHeight = effectiveUnrotatedRect.size.height * scale;
306
+ const usesCustomPivot = Boolean(explicitUnrotatedRect) && annotationRotation !== 0;
307
+ const innerLeft = usesCustomPivot ? (effectiveUnrotatedRect.origin.x - currentObject.rect.origin.x) * scale : (aabbWidth - innerWidth) / 2;
308
+ const innerTop = usesCustomPivot ? (effectiveUnrotatedRect.origin.y - currentObject.rect.origin.y) * scale : (aabbHeight - innerHeight) / 2;
309
+ const innerTransformOrigin = usesCustomPivot && rotationPivot ? `${(rotationPivot.x - effectiveUnrotatedRect.origin.x) * scale}px ${(rotationPivot.y - effectiveUnrotatedRect.origin.y) * scale}px` : "center center";
310
+ const centerX = rotationPivot ? (rotationPivot.x - currentObject.rect.origin.x) * scale : aabbWidth / 2;
311
+ const centerY = rotationPivot ? (rotationPivot.y - currentObject.rect.origin.y) * scale : aabbHeight / 2;
312
+ const guideLength = Math.max(300, Math.max(aabbWidth, aabbHeight) + 80);
313
+ const childObject = useMemo(() => {
314
+ if (explicitUnrotatedRect) {
315
+ return { ...currentObject, rect: explicitUnrotatedRect };
316
+ }
317
+ return currentObject;
318
+ }, [currentObject, explicitUnrotatedRect]);
226
319
  return /* @__PURE__ */ jsxs("div", { "data-no-interaction": true, children: [
227
320
  /* @__PURE__ */ jsxs(
228
321
  "div",
229
322
  {
230
- ...effectiveIsDraggable && isSelected ? dragProps : {},
231
- ...doubleProps,
232
323
  style: {
233
324
  position: "absolute",
234
325
  left: currentObject.rect.origin.x * scale,
235
326
  top: currentObject.rect.origin.y * scale,
236
- width: currentObject.rect.size.width * scale,
237
- height: currentObject.rect.size.height * scale,
238
- outline: showOutline ? `1px solid ${selectionOutlineColor}` : "none",
239
- outlineOffset: showOutline ? `${outlineOffset}px` : "0px",
240
- pointerEvents: isSelected && !isMultiSelected ? "auto" : "none",
241
- touchAction: "none",
242
- cursor: isSelected && effectiveIsDraggable ? "move" : "default",
327
+ width: aabbWidth,
328
+ height: aabbHeight,
329
+ pointerEvents: "none",
243
330
  zIndex,
244
331
  ...style
245
332
  },
246
333
  ...props,
247
334
  children: [
248
- (() => {
249
- const childrenRender = typeof children === "function" ? children(currentObject) : children;
250
- const customRender = customAnnotationRenderer == null ? void 0 : customAnnotationRenderer({
251
- annotation: currentObject,
252
- children: childrenRender,
253
- isSelected,
254
- scale,
255
- rotation,
256
- pageWidth,
257
- pageHeight,
258
- pageIndex,
259
- onSelect
260
- });
261
- if (customRender !== null && customRender !== void 0) {
262
- return customRender;
263
- }
264
- return childrenRender;
265
- })(),
266
- isSelected && effectiveIsResizable && resize.map(
267
- ({ key, ...hProps }) => (resizeUI == null ? void 0 : resizeUI.component) ? resizeUI.component({
268
- key,
269
- ...hProps,
270
- backgroundColor: HANDLE_COLOR
271
- }) : /* @__PURE__ */ jsx(
335
+ rotationActive && /* @__PURE__ */ jsxs(Fragment, { children: [
336
+ /* @__PURE__ */ jsx(
272
337
  "div",
273
338
  {
274
- ...hProps,
275
- style: { ...hProps.style, backgroundColor: HANDLE_COLOR }
276
- },
277
- key
278
- )
279
- ),
280
- isSelected && canModifyAnnotations && !isMultiSelected && vertices.map(
281
- ({ key, ...vProps }) => (vertexUI == null ? void 0 : vertexUI.component) ? vertexUI.component({
282
- key,
283
- ...vProps,
284
- backgroundColor: VERTEX_COLOR
285
- }) : /* @__PURE__ */ jsx(
339
+ style: {
340
+ position: "absolute",
341
+ left: centerX - guideLength / 2,
342
+ top: centerY,
343
+ width: guideLength,
344
+ height: 1,
345
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
346
+ opacity: 0.35,
347
+ pointerEvents: "none"
348
+ }
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsx(
286
352
  "div",
287
353
  {
288
- ...vProps,
289
- style: { ...vProps.style, backgroundColor: VERTEX_COLOR }
290
- },
291
- key
354
+ style: {
355
+ position: "absolute",
356
+ left: centerX,
357
+ top: centerY - guideLength / 2,
358
+ width: 1,
359
+ height: guideLength,
360
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
361
+ opacity: 0.35,
362
+ pointerEvents: "none"
363
+ }
364
+ }
365
+ ),
366
+ /* @__PURE__ */ jsx(
367
+ "div",
368
+ {
369
+ style: {
370
+ position: "absolute",
371
+ left: centerX - guideLength / 2,
372
+ top: centerY,
373
+ width: guideLength,
374
+ height: 1,
375
+ transformOrigin: "center center",
376
+ transform: `rotate(${annotationRotation}deg)`,
377
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
378
+ opacity: 0.8,
379
+ pointerEvents: "none"
380
+ }
381
+ }
292
382
  )
383
+ ] }),
384
+ isSelected && effectiveIsRotatable && rotationHandle && ((rotationUI == null ? void 0 : rotationUI.component) ? /* @__PURE__ */ jsx(
385
+ "div",
386
+ {
387
+ onPointerEnter: () => setIsHandleHovered(true),
388
+ onPointerLeave: () => {
389
+ setIsHandleHovered(false);
390
+ setCursorScreen(null);
391
+ },
392
+ onPointerMove: (e) => {
393
+ if (!rotationActive) setCursorScreen({ x: e.clientX, y: e.clientY });
394
+ },
395
+ style: { display: "contents" },
396
+ children: rotationUI.component({
397
+ ...rotationHandle.handle,
398
+ backgroundColor: ROTATION_COLOR,
399
+ iconColor: ROTATION_ICON_COLOR,
400
+ connectorStyle: {
401
+ ...rotationHandle.connector.style,
402
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
403
+ opacity: rotationActive ? 0 : 1
404
+ },
405
+ showConnector: SHOW_CONNECTOR,
406
+ opacity: rotationActive ? 0 : 1,
407
+ border: {
408
+ color: ROTATION_BORDER_COLOR,
409
+ width: ROTATION_BORDER_WIDTH,
410
+ style: ROTATION_BORDER_STYLE
411
+ }
412
+ })
413
+ }
414
+ ) : /* @__PURE__ */ jsxs(
415
+ "div",
416
+ {
417
+ onPointerEnter: () => setIsHandleHovered(true),
418
+ onPointerLeave: () => {
419
+ setIsHandleHovered(false);
420
+ setCursorScreen(null);
421
+ },
422
+ onPointerMove: (e) => {
423
+ if (!rotationActive) setCursorScreen({ x: e.clientX, y: e.clientY });
424
+ },
425
+ style: { display: "contents" },
426
+ children: [
427
+ SHOW_CONNECTOR && /* @__PURE__ */ jsx(
428
+ "div",
429
+ {
430
+ style: {
431
+ ...rotationHandle.connector.style,
432
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
433
+ opacity: rotationActive ? 0 : 1
434
+ }
435
+ }
436
+ ),
437
+ /* @__PURE__ */ jsx(
438
+ "div",
439
+ {
440
+ ...rotationHandle.handle,
441
+ style: {
442
+ ...rotationHandle.handle.style,
443
+ backgroundColor: ROTATION_COLOR,
444
+ border: `${ROTATION_BORDER_WIDTH}px ${ROTATION_BORDER_STYLE} ${ROTATION_BORDER_COLOR}`,
445
+ boxSizing: "border-box",
446
+ display: "flex",
447
+ alignItems: "center",
448
+ justifyContent: "center",
449
+ pointerEvents: "auto",
450
+ opacity: rotationActive ? 0 : 1
451
+ },
452
+ children: /* @__PURE__ */ jsxs(
453
+ "svg",
454
+ {
455
+ width: Math.round(ROTATION_SIZE * 0.6),
456
+ height: Math.round(ROTATION_SIZE * 0.6),
457
+ viewBox: "0 0 24 24",
458
+ fill: "none",
459
+ stroke: ROTATION_ICON_COLOR,
460
+ strokeWidth: "2",
461
+ strokeLinecap: "round",
462
+ strokeLinejoin: "round",
463
+ children: [
464
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
465
+ /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" })
466
+ ]
467
+ }
468
+ )
469
+ }
470
+ )
471
+ ]
472
+ }
473
+ )),
474
+ /* @__PURE__ */ jsxs(
475
+ "div",
476
+ {
477
+ ...effectiveIsDraggable && isSelected ? dragProps : {},
478
+ ...doubleProps,
479
+ style: {
480
+ position: "absolute",
481
+ left: innerLeft,
482
+ top: innerTop,
483
+ width: innerWidth,
484
+ height: innerHeight,
485
+ transform: annotationRotation !== 0 ? `rotate(${annotationRotation}deg)` : void 0,
486
+ transformOrigin: innerTransformOrigin,
487
+ outline: showOutline ? `${outlineWidth}px ${outlineStyle} ${outlineColor}` : "none",
488
+ outlineOffset: showOutline ? `${outlineOff}px` : "0px",
489
+ pointerEvents: isSelected && !isMultiSelected ? "auto" : "none",
490
+ touchAction: "none",
491
+ cursor: isSelected && effectiveIsDraggable ? "move" : "default"
492
+ },
493
+ children: [
494
+ (() => {
495
+ const childrenRender = typeof children === "function" ? children(childObject) : children;
496
+ const customRender = customAnnotationRenderer == null ? void 0 : customAnnotationRenderer({
497
+ annotation: childObject,
498
+ children: childrenRender,
499
+ isSelected,
500
+ scale,
501
+ rotation,
502
+ pageWidth,
503
+ pageHeight,
504
+ pageIndex,
505
+ onSelect
506
+ });
507
+ if (customRender !== null && customRender !== void 0) {
508
+ return customRender;
509
+ }
510
+ return childrenRender;
511
+ })(),
512
+ isSelected && effectiveIsResizable && !rotationActive && resize.map(
513
+ ({ key, ...hProps }) => (resizeUI == null ? void 0 : resizeUI.component) ? resizeUI.component({
514
+ key,
515
+ ...hProps,
516
+ backgroundColor: HANDLE_COLOR
517
+ }) : /* @__PURE__ */ jsx(
518
+ "div",
519
+ {
520
+ ...hProps,
521
+ style: { ...hProps.style, backgroundColor: HANDLE_COLOR }
522
+ },
523
+ key
524
+ )
525
+ ),
526
+ isSelected && canModifyAnnotations && !isMultiSelected && !rotationActive && vertices.map(
527
+ ({ key, ...vProps }) => (vertexUI == null ? void 0 : vertexUI.component) ? vertexUI.component({
528
+ key,
529
+ ...vProps,
530
+ backgroundColor: VERTEX_COLOR
531
+ }) : /* @__PURE__ */ jsx(
532
+ "div",
533
+ {
534
+ ...vProps,
535
+ style: { ...vProps.style, backgroundColor: VERTEX_COLOR }
536
+ },
537
+ key
538
+ )
539
+ )
540
+ ]
541
+ }
293
542
  )
294
543
  ]
295
544
  }
296
545
  ),
297
- selectionMenu && !isMultiSelected && /* @__PURE__ */ jsx(
546
+ selectionMenu && !isMultiSelected && !rotationActive && /* @__PURE__ */ jsx(
298
547
  CounterRotate,
299
548
  {
300
549
  rect: {
@@ -308,19 +557,49 @@ function AnnotationContainer({
308
557
  }
309
558
  },
310
559
  rotation,
311
- children: (counterRotateProps) => selectionMenu({
312
- ...counterRotateProps,
313
- context: {
314
- type: "annotation",
315
- annotation: trackedAnnotation,
316
- pageIndex
317
- },
318
- selected: isSelected,
319
- placement: {
320
- suggestTop: false
321
- }
322
- })
560
+ children: (counterRotateProps) => {
561
+ const effectiveAngle = ((annotationRotation + rotation * 90) % 360 + 360) % 360;
562
+ const handleNearMenuSide = effectiveIsRotatable && effectiveAngle > 90 && effectiveAngle < 270;
563
+ return selectionMenu({
564
+ ...counterRotateProps,
565
+ context: {
566
+ type: "annotation",
567
+ annotation: trackedAnnotation,
568
+ pageIndex
569
+ },
570
+ selected: isSelected,
571
+ placement: {
572
+ suggestTop: handleNearMenuSide
573
+ }
574
+ });
575
+ }
323
576
  }
577
+ ),
578
+ (rotationActive || isHandleHovered) && cursorScreen && createPortal(
579
+ /* @__PURE__ */ jsxs(
580
+ "div",
581
+ {
582
+ style: {
583
+ position: "fixed",
584
+ left: cursorScreen.x + 16,
585
+ top: cursorScreen.y - 16,
586
+ background: "rgba(0,0,0,0.8)",
587
+ color: "#fff",
588
+ padding: "4px 8px",
589
+ borderRadius: 4,
590
+ fontSize: 12,
591
+ fontFamily: "monospace",
592
+ pointerEvents: "none",
593
+ zIndex: 1e4,
594
+ whiteSpace: "nowrap"
595
+ },
596
+ children: [
597
+ normalizedRotationDisplay.toFixed(0),
598
+ "°"
599
+ ]
600
+ }
601
+ ),
602
+ document.body
324
603
  )
325
604
  ] });
326
605
  }
@@ -334,19 +613,28 @@ function GroupSelectionBox({
334
613
  selectedAnnotations,
335
614
  isDraggable,
336
615
  isResizable,
616
+ isRotatable = true,
617
+ lockAspectRatio = false,
337
618
  resizeUI,
338
- selectionOutlineColor = "#007ACC",
339
- outlineOffset = 2,
340
- zIndex = 100,
619
+ rotationUI,
620
+ selectionOutlineColor,
621
+ outlineOffset,
622
+ selectionOutline,
623
+ zIndex = 2,
341
624
  groupSelectionMenu
342
625
  }) {
626
+ var _a, _b, _c;
343
627
  const { plugin } = useAnnotationPlugin();
344
628
  const { canModifyAnnotations } = useDocumentPermissions(documentId);
345
629
  const gestureBaseRef = useRef(null);
346
630
  const isDraggingRef = useRef(false);
347
631
  const isResizingRef = useRef(false);
632
+ const [liveRotation, setLiveRotation] = useState(null);
633
+ const [cursorScreen, setCursorScreen] = useState(null);
634
+ const [isHandleHovered, setIsHandleHovered] = useState(false);
348
635
  const effectiveIsDraggable = canModifyAnnotations && isDraggable;
349
636
  const effectiveIsResizable = canModifyAnnotations && isResizable;
637
+ const effectiveIsRotatable = canModifyAnnotations && isRotatable;
350
638
  const groupBox = useMemo(() => {
351
639
  const rects = selectedAnnotations.map((ta) => ta.object.rect);
352
640
  return boundingRectOrEmpty(rects);
@@ -357,10 +645,20 @@ function GroupSelectionBox({
357
645
  setPreviewGroupBox(groupBox);
358
646
  }
359
647
  }, [groupBox]);
648
+ useEffect(() => {
649
+ if (!plugin) return;
650
+ const unsubscribe = plugin.onRotateChange((event) => {
651
+ if (event.documentId !== documentId) return;
652
+ if (event.type === "end" || event.type === "cancel") {
653
+ setLiveRotation(null);
654
+ }
655
+ });
656
+ return unsubscribe;
657
+ }, [plugin, documentId]);
360
658
  const handleUpdate = useCallback(
361
659
  (event) => {
362
- var _a, _b;
363
- if (!((_a = event.transformData) == null ? void 0 : _a.type)) return;
660
+ var _a2, _b2, _c2, _d, _e, _f;
661
+ if (!((_a2 = event.transformData) == null ? void 0 : _a2.type)) return;
364
662
  if (!plugin) return;
365
663
  const transformType = event.transformData.type;
366
664
  const isMove = transformType === "move";
@@ -379,10 +677,37 @@ function GroupSelectionBox({
379
677
  plugin.startResize(documentId, {
380
678
  annotationIds: selectedAnnotations.map((ta) => ta.object.id),
381
679
  pageSize: { width: pageWidth, height: pageHeight },
382
- resizeHandle: ((_b = event.transformData.metadata) == null ? void 0 : _b.handle) ?? "se"
680
+ resizeHandle: ((_b2 = event.transformData.metadata) == null ? void 0 : _b2.handle) ?? "se"
383
681
  });
384
682
  }
385
683
  }
684
+ if (transformType === "rotate") {
685
+ if (!isRotatable) return;
686
+ const ids = selectedAnnotations.map((ta) => ta.object.id);
687
+ const cursorAngle = ((_c2 = event.transformData.metadata) == null ? void 0 : _c2.rotationAngle) ?? 0;
688
+ const cursorPos = (_d = event.transformData.metadata) == null ? void 0 : _d.cursorPosition;
689
+ if (cursorPos) setCursorScreen({ x: cursorPos.clientX, y: cursorPos.clientY });
690
+ if (event.state === "start") {
691
+ setLiveRotation(cursorAngle);
692
+ plugin.startRotation(documentId, {
693
+ annotationIds: ids,
694
+ cursorAngle,
695
+ rotationCenter: (_e = event.transformData.metadata) == null ? void 0 : _e.rotationCenter
696
+ });
697
+ } else if (event.state === "move") {
698
+ setLiveRotation(cursorAngle);
699
+ plugin.updateRotation(
700
+ documentId,
701
+ cursorAngle,
702
+ (_f = event.transformData.metadata) == null ? void 0 : _f.rotationDelta
703
+ );
704
+ } else if (event.state === "end") {
705
+ setLiveRotation(null);
706
+ setCursorScreen(null);
707
+ plugin.commitRotation(documentId);
708
+ }
709
+ return;
710
+ }
386
711
  const base = gestureBaseRef.current ?? groupBox;
387
712
  if (isMove && event.transformData.changes.rect) {
388
713
  const newRect = event.transformData.changes.rect;
@@ -421,12 +746,33 @@ function GroupSelectionBox({
421
746
  pageHeight,
422
747
  groupBox,
423
748
  effectiveIsDraggable,
424
- selectedAnnotations
749
+ selectedAnnotations,
750
+ isRotatable
425
751
  ]
426
752
  );
753
+ const groupRotationDisplay = liveRotation ?? 0;
754
+ const rotationActive = liveRotation !== null;
755
+ const normalizedRotationDisplay = Number.isFinite(groupRotationDisplay) ? Math.round(groupRotationDisplay * 10) / 10 : 0;
427
756
  const HANDLE_COLOR = (resizeUI == null ? void 0 : resizeUI.color) ?? "#007ACC";
428
757
  const HANDLE_SIZE = (resizeUI == null ? void 0 : resizeUI.size) ?? 12;
429
- const { dragProps, resize } = useInteractionHandles({
758
+ const ROTATION_COLOR = (rotationUI == null ? void 0 : rotationUI.color) ?? "white";
759
+ const ROTATION_CONNECTOR_COLOR = (rotationUI == null ? void 0 : rotationUI.connectorColor) ?? "#007ACC";
760
+ const ROTATION_SIZE = (rotationUI == null ? void 0 : rotationUI.size) ?? 32;
761
+ const ROTATION_MARGIN = rotationUI == null ? void 0 : rotationUI.margin;
762
+ const ROTATION_ICON_COLOR = (rotationUI == null ? void 0 : rotationUI.iconColor) ?? "#007ACC";
763
+ const SHOW_CONNECTOR = (rotationUI == null ? void 0 : rotationUI.showConnector) ?? false;
764
+ const ROTATION_BORDER_COLOR = ((_a = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _a.color) ?? "#007ACC";
765
+ const ROTATION_BORDER_WIDTH = ((_b = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _b.width) ?? 1;
766
+ const ROTATION_BORDER_STYLE = ((_c = rotationUI == null ? void 0 : rotationUI.border) == null ? void 0 : _c.style) ?? "solid";
767
+ const outlineColor = (selectionOutline == null ? void 0 : selectionOutline.color) ?? selectionOutlineColor ?? "#007ACC";
768
+ const outlineStyleVal = (selectionOutline == null ? void 0 : selectionOutline.style) ?? "dashed";
769
+ const outlineWidth = (selectionOutline == null ? void 0 : selectionOutline.width) ?? 2;
770
+ const outlineOff = (selectionOutline == null ? void 0 : selectionOutline.offset) ?? outlineOffset ?? 2;
771
+ const {
772
+ dragProps,
773
+ resize,
774
+ rotation: rotationHandle
775
+ } = useInteractionHandles({
430
776
  controller: {
431
777
  element: previewGroupBox,
432
778
  constraints: {
@@ -434,7 +780,7 @@ function GroupSelectionBox({
434
780
  minHeight: 20,
435
781
  boundingBox: { width: pageWidth, height: pageHeight }
436
782
  },
437
- maintainAspectRatio: false,
783
+ maintainAspectRatio: lockAspectRatio,
438
784
  pageRotation: rotation,
439
785
  scale,
440
786
  enabled: true,
@@ -442,55 +788,249 @@ function GroupSelectionBox({
442
788
  },
443
789
  resizeUI: {
444
790
  handleSize: HANDLE_SIZE,
445
- spacing: outlineOffset,
791
+ spacing: outlineOff,
446
792
  offsetMode: "outside",
447
- includeSides: true,
793
+ includeSides: !lockAspectRatio,
448
794
  zIndex: zIndex + 1
449
795
  },
450
796
  vertexUI: {
451
797
  vertexSize: 0,
452
798
  zIndex
453
799
  },
454
- includeVertices: false
800
+ rotationUI: {
801
+ handleSize: ROTATION_SIZE,
802
+ margin: ROTATION_MARGIN,
803
+ zIndex: zIndex + 2,
804
+ showConnector: SHOW_CONNECTOR
805
+ },
806
+ includeVertices: false,
807
+ includeRotation: effectiveIsRotatable,
808
+ currentRotation: liveRotation ?? 0
455
809
  });
456
810
  if (selectedAnnotations.length < 2) {
457
811
  return null;
458
812
  }
813
+ const groupBoxWidth = previewGroupBox.size.width * scale;
814
+ const groupBoxHeight = previewGroupBox.size.height * scale;
815
+ const groupCenterX = groupBoxWidth / 2;
816
+ const groupCenterY = groupBoxHeight / 2;
817
+ const groupGuideLength = Math.max(300, Math.max(groupBoxWidth, groupBoxHeight) + 80);
459
818
  return /* @__PURE__ */ jsxs("div", { "data-group-selection-box": true, "data-no-interaction": true, children: [
460
- /* @__PURE__ */ jsx(
819
+ /* @__PURE__ */ jsxs(
461
820
  "div",
462
821
  {
463
- ...effectiveIsDraggable ? dragProps : {
464
- onPointerDown: (e) => e.stopPropagation()
465
- },
466
822
  style: {
467
823
  position: "absolute",
468
824
  left: previewGroupBox.origin.x * scale,
469
825
  top: previewGroupBox.origin.y * scale,
470
- width: previewGroupBox.size.width * scale,
471
- height: previewGroupBox.size.height * scale,
472
- outline: `2px dashed ${selectionOutlineColor}`,
473
- outlineOffset: outlineOffset - 1,
474
- cursor: effectiveIsDraggable ? "move" : "default",
475
- touchAction: "none",
826
+ width: groupBoxWidth,
827
+ height: groupBoxHeight,
828
+ pointerEvents: "none",
476
829
  zIndex
477
830
  },
478
- children: effectiveIsResizable && resize.map(
479
- ({ key, ...hProps }) => (resizeUI == null ? void 0 : resizeUI.component) ? resizeUI.component({
480
- key,
481
- ...hProps,
482
- backgroundColor: HANDLE_COLOR
483
- }) : /* @__PURE__ */ jsx(
831
+ children: [
832
+ rotationActive && /* @__PURE__ */ jsxs(Fragment, { children: [
833
+ /* @__PURE__ */ jsx(
834
+ "div",
835
+ {
836
+ style: {
837
+ position: "absolute",
838
+ left: groupCenterX - groupGuideLength / 2,
839
+ top: groupCenterY,
840
+ width: groupGuideLength,
841
+ height: 1,
842
+ backgroundColor: HANDLE_COLOR,
843
+ opacity: 0.35,
844
+ pointerEvents: "none"
845
+ }
846
+ }
847
+ ),
848
+ /* @__PURE__ */ jsx(
849
+ "div",
850
+ {
851
+ style: {
852
+ position: "absolute",
853
+ left: groupCenterX,
854
+ top: groupCenterY - groupGuideLength / 2,
855
+ width: 1,
856
+ height: groupGuideLength,
857
+ backgroundColor: HANDLE_COLOR,
858
+ opacity: 0.35,
859
+ pointerEvents: "none"
860
+ }
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsx(
864
+ "div",
865
+ {
866
+ style: {
867
+ position: "absolute",
868
+ left: groupCenterX - groupGuideLength / 2,
869
+ top: groupCenterY,
870
+ width: groupGuideLength,
871
+ height: 1,
872
+ transformOrigin: "center center",
873
+ transform: `rotate(${groupRotationDisplay}deg)`,
874
+ backgroundColor: HANDLE_COLOR,
875
+ opacity: 0.8,
876
+ pointerEvents: "none"
877
+ }
878
+ }
879
+ )
880
+ ] }),
881
+ effectiveIsRotatable && rotationHandle && ((rotationUI == null ? void 0 : rotationUI.component) ? /* @__PURE__ */ jsx(
484
882
  "div",
485
883
  {
486
- ...hProps,
487
- style: { ...hProps.style, backgroundColor: HANDLE_COLOR }
488
- },
489
- key
884
+ onPointerEnter: () => setIsHandleHovered(true),
885
+ onPointerLeave: () => {
886
+ setIsHandleHovered(false);
887
+ setCursorScreen(null);
888
+ },
889
+ onPointerMove: (e) => {
890
+ if (!rotationActive) setCursorScreen({ x: e.clientX, y: e.clientY });
891
+ },
892
+ style: { display: "contents" },
893
+ children: rotationUI.component({
894
+ ...rotationHandle.handle,
895
+ backgroundColor: ROTATION_COLOR,
896
+ iconColor: ROTATION_ICON_COLOR,
897
+ connectorStyle: {
898
+ ...rotationHandle.connector.style,
899
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
900
+ opacity: rotationActive ? 0 : 1
901
+ },
902
+ showConnector: SHOW_CONNECTOR,
903
+ opacity: rotationActive ? 0 : 1,
904
+ border: {
905
+ color: ROTATION_BORDER_COLOR,
906
+ width: ROTATION_BORDER_WIDTH,
907
+ style: ROTATION_BORDER_STYLE
908
+ }
909
+ })
910
+ }
911
+ ) : /* @__PURE__ */ jsxs(
912
+ "div",
913
+ {
914
+ onPointerEnter: () => setIsHandleHovered(true),
915
+ onPointerLeave: () => {
916
+ setIsHandleHovered(false);
917
+ setCursorScreen(null);
918
+ },
919
+ onPointerMove: (e) => {
920
+ if (!rotationActive) setCursorScreen({ x: e.clientX, y: e.clientY });
921
+ },
922
+ style: { display: "contents" },
923
+ children: [
924
+ SHOW_CONNECTOR && /* @__PURE__ */ jsx(
925
+ "div",
926
+ {
927
+ style: {
928
+ ...rotationHandle.connector.style,
929
+ backgroundColor: ROTATION_CONNECTOR_COLOR,
930
+ opacity: rotationActive ? 0 : 1
931
+ }
932
+ }
933
+ ),
934
+ /* @__PURE__ */ jsx(
935
+ "div",
936
+ {
937
+ ...rotationHandle.handle,
938
+ style: {
939
+ ...rotationHandle.handle.style,
940
+ backgroundColor: ROTATION_COLOR,
941
+ border: `${ROTATION_BORDER_WIDTH}px ${ROTATION_BORDER_STYLE} ${ROTATION_BORDER_COLOR}`,
942
+ boxSizing: "border-box",
943
+ display: "flex",
944
+ alignItems: "center",
945
+ justifyContent: "center",
946
+ pointerEvents: "auto",
947
+ opacity: rotationActive ? 0 : 1
948
+ },
949
+ children: /* @__PURE__ */ jsxs(
950
+ "svg",
951
+ {
952
+ width: Math.round(ROTATION_SIZE * 0.6),
953
+ height: Math.round(ROTATION_SIZE * 0.6),
954
+ viewBox: "0 0 24 24",
955
+ fill: "none",
956
+ stroke: ROTATION_ICON_COLOR,
957
+ strokeWidth: "2",
958
+ strokeLinecap: "round",
959
+ strokeLinejoin: "round",
960
+ children: [
961
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
962
+ /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" })
963
+ ]
964
+ }
965
+ )
966
+ }
967
+ )
968
+ ]
969
+ }
970
+ )),
971
+ /* @__PURE__ */ jsx(
972
+ "div",
973
+ {
974
+ ...effectiveIsDraggable ? dragProps : {
975
+ onPointerDown: (e) => e.stopPropagation()
976
+ },
977
+ style: {
978
+ position: "absolute",
979
+ left: 0,
980
+ top: 0,
981
+ width: groupBoxWidth,
982
+ height: groupBoxHeight,
983
+ outline: rotationActive ? "none" : `${outlineWidth}px ${outlineStyleVal} ${outlineColor}`,
984
+ outlineOffset: outlineOff - 1,
985
+ cursor: effectiveIsDraggable ? "move" : "default",
986
+ touchAction: "none",
987
+ pointerEvents: "auto"
988
+ },
989
+ children: effectiveIsResizable && !rotationActive && resize.map(
990
+ ({ key, ...hProps }) => (resizeUI == null ? void 0 : resizeUI.component) ? resizeUI.component({
991
+ key,
992
+ ...hProps,
993
+ backgroundColor: HANDLE_COLOR
994
+ }) : /* @__PURE__ */ jsx(
995
+ "div",
996
+ {
997
+ ...hProps,
998
+ style: { ...hProps.style, backgroundColor: HANDLE_COLOR }
999
+ },
1000
+ key
1001
+ )
1002
+ )
1003
+ }
490
1004
  )
491
- )
1005
+ ]
492
1006
  }
493
1007
  ),
1008
+ (rotationActive || isHandleHovered) && cursorScreen && createPortal(
1009
+ /* @__PURE__ */ jsxs(
1010
+ "div",
1011
+ {
1012
+ style: {
1013
+ position: "fixed",
1014
+ left: cursorScreen.x + 16,
1015
+ top: cursorScreen.y - 16,
1016
+ background: "rgba(0,0,0,0.8)",
1017
+ color: "#fff",
1018
+ padding: "4px 8px",
1019
+ borderRadius: 4,
1020
+ fontSize: 12,
1021
+ fontFamily: "monospace",
1022
+ pointerEvents: "none",
1023
+ zIndex: 1e4,
1024
+ whiteSpace: "nowrap"
1025
+ },
1026
+ children: [
1027
+ normalizedRotationDisplay.toFixed(0),
1028
+ "°"
1029
+ ]
1030
+ }
1031
+ ),
1032
+ document.body
1033
+ ),
494
1034
  groupSelectionMenu && /* @__PURE__ */ jsx(
495
1035
  CounterRotate,
496
1036
  {
@@ -505,18 +1045,22 @@ function GroupSelectionBox({
505
1045
  }
506
1046
  },
507
1047
  rotation,
508
- children: (counterRotateProps) => groupSelectionMenu({
509
- ...counterRotateProps,
510
- context: {
511
- type: "group",
512
- annotations: selectedAnnotations,
513
- pageIndex
514
- },
515
- selected: true,
516
- placement: {
517
- suggestTop: false
518
- }
519
- })
1048
+ children: (counterRotateProps) => {
1049
+ const effectiveAngle = ((groupRotationDisplay + rotation * 90) % 360 + 360) % 360;
1050
+ const handleNearMenuSide = effectiveIsRotatable && effectiveAngle > 90 && effectiveAngle < 270;
1051
+ return groupSelectionMenu({
1052
+ ...counterRotateProps,
1053
+ context: {
1054
+ type: "group",
1055
+ annotations: selectedAnnotations,
1056
+ pageIndex
1057
+ },
1058
+ selected: true,
1059
+ placement: {
1060
+ suggestTop: handleNearMenuSide
1061
+ }
1062
+ });
1063
+ }
520
1064
  }
521
1065
  )
522
1066
  ] });
@@ -1285,7 +1829,7 @@ function FreeText({
1285
1829
  style: {
1286
1830
  color: annotation.object.fontColor,
1287
1831
  fontSize: adjustedFontPx,
1288
- fontFamily: standardFontCss(annotation.object.fontFamily),
1832
+ ...standardFontCssProperties(annotation.object.fontFamily),
1289
1833
  textAlign: textAlignmentToCss(annotation.object.textAlign),
1290
1834
  flexDirection: "column",
1291
1835
  justifyContent: annotation.object.verticalAlign === PdfVerticalAlignment.Top ? "flex-start" : annotation.object.verticalAlign === PdfVerticalAlignment.Middle ? "center" : "flex-end",
@@ -1314,6 +1858,7 @@ function RenderAnnotation({
1314
1858
  pageIndex,
1315
1859
  annotation,
1316
1860
  scaleFactor = 1,
1861
+ unrotated,
1317
1862
  style,
1318
1863
  ...props
1319
1864
  }) {
@@ -1328,7 +1873,8 @@ function RenderAnnotation({
1328
1873
  annotation,
1329
1874
  options: {
1330
1875
  scaleFactor,
1331
- dpr: window.devicePixelRatio
1876
+ dpr: window.devicePixelRatio,
1877
+ unrotated
1332
1878
  }
1333
1879
  });
1334
1880
  task.wait((blob) => {
@@ -1348,7 +1894,16 @@ function RenderAnnotation({
1348
1894
  }
1349
1895
  };
1350
1896
  }
1351
- }, [pageIndex, scaleFactor, annotationProvides, documentId, annotation.id, width, height]);
1897
+ }, [
1898
+ pageIndex,
1899
+ scaleFactor,
1900
+ unrotated,
1901
+ annotationProvides,
1902
+ documentId,
1903
+ annotation.id,
1904
+ width,
1905
+ height
1906
+ ]);
1352
1907
  const handleImageLoad = () => {
1353
1908
  if (urlRef.current) {
1354
1909
  URL.revokeObjectURL(urlRef.current);
@@ -1378,6 +1933,7 @@ function Stamp({
1378
1933
  scale,
1379
1934
  onClick
1380
1935
  }) {
1936
+ const unrotated = !!annotation.object.rotation && !!annotation.object.unrotatedRect;
1381
1937
  return /* @__PURE__ */ jsx(
1382
1938
  "div",
1383
1939
  {
@@ -1397,7 +1953,8 @@ function Stamp({
1397
1953
  documentId,
1398
1954
  pageIndex,
1399
1955
  annotation: { ...annotation.object, id: annotation.object.id },
1400
- scaleFactor: scale
1956
+ scaleFactor: scale,
1957
+ unrotated
1401
1958
  }
1402
1959
  )
1403
1960
  }
@@ -1611,6 +2168,40 @@ function Annotations(annotationsProps) {
1611
2168
  return (tool == null ? void 0 : tool.interaction.isGroupResizable) !== void 0 ? groupResizable : singleResizable;
1612
2169
  });
1613
2170
  }, [selectedAnnotationsOnPage, annotationProvides]);
2171
+ const areAllSelectedRotatable = useMemo(() => {
2172
+ if (selectedAnnotationsOnPage.length < 2) return false;
2173
+ return selectedAnnotationsOnPage.every((ta) => {
2174
+ const tool = annotationProvides == null ? void 0 : annotationProvides.findToolForAnnotation(ta.object);
2175
+ const groupRotatable = resolveInteractionProp(
2176
+ tool == null ? void 0 : tool.interaction.isGroupRotatable,
2177
+ ta.object,
2178
+ true
2179
+ );
2180
+ const singleRotatable = resolveInteractionProp(
2181
+ tool == null ? void 0 : tool.interaction.isRotatable,
2182
+ ta.object,
2183
+ true
2184
+ );
2185
+ return (tool == null ? void 0 : tool.interaction.isGroupRotatable) !== void 0 ? groupRotatable : singleRotatable;
2186
+ });
2187
+ }, [selectedAnnotationsOnPage, annotationProvides]);
2188
+ const shouldLockGroupAspectRatio = useMemo(() => {
2189
+ if (selectedAnnotationsOnPage.length < 2) return false;
2190
+ return selectedAnnotationsOnPage.some((ta) => {
2191
+ const tool = annotationProvides == null ? void 0 : annotationProvides.findToolForAnnotation(ta.object);
2192
+ const groupLock = resolveInteractionProp(
2193
+ tool == null ? void 0 : tool.interaction.lockGroupAspectRatio,
2194
+ ta.object,
2195
+ false
2196
+ );
2197
+ const singleLock = resolveInteractionProp(
2198
+ tool == null ? void 0 : tool.interaction.lockAspectRatio,
2199
+ ta.object,
2200
+ false
2201
+ );
2202
+ return (tool == null ? void 0 : tool.interaction.lockGroupAspectRatio) !== void 0 ? groupLock : singleLock;
2203
+ });
2204
+ }, [selectedAnnotationsOnPage, annotationProvides]);
1614
2205
  const allSelectedOnSamePage = useMemo(() => {
1615
2206
  if (!annotationProvides) return false;
1616
2207
  const allSelected = annotationProvides.getSelectedAnnotations();
@@ -1650,6 +2241,11 @@ function Annotations(annotationsProps) {
1650
2241
  annotation.object,
1651
2242
  false
1652
2243
  ),
2244
+ isRotatable: resolveInteractionProp(
2245
+ tool == null ? void 0 : tool.interaction.isRotatable,
2246
+ annotation.object,
2247
+ false
2248
+ ),
1653
2249
  selectionMenu,
1654
2250
  onSelect: (e) => handleClick(e, annotation),
1655
2251
  style: {
@@ -1684,6 +2280,11 @@ function Annotations(annotationsProps) {
1684
2280
  annotation.object,
1685
2281
  false
1686
2282
  ),
2283
+ isRotatable: resolveInteractionProp(
2284
+ tool == null ? void 0 : tool.interaction.isRotatable,
2285
+ annotation.object,
2286
+ true
2287
+ ),
1687
2288
  selectionMenu,
1688
2289
  onSelect: (e) => handleClick(e, annotation),
1689
2290
  style: {
@@ -1725,6 +2326,11 @@ function Annotations(annotationsProps) {
1725
2326
  annotation.object,
1726
2327
  false
1727
2328
  ),
2329
+ isRotatable: resolveInteractionProp(
2330
+ tool == null ? void 0 : tool.interaction.isRotatable,
2331
+ annotation.object,
2332
+ true
2333
+ ),
1728
2334
  selectionMenu,
1729
2335
  onSelect: (e) => handleClick(e, annotation),
1730
2336
  style: {
@@ -1766,6 +2372,11 @@ function Annotations(annotationsProps) {
1766
2372
  annotation.object,
1767
2373
  false
1768
2374
  ),
2375
+ isRotatable: resolveInteractionProp(
2376
+ tool == null ? void 0 : tool.interaction.isRotatable,
2377
+ annotation.object,
2378
+ true
2379
+ ),
1769
2380
  selectionMenu,
1770
2381
  onSelect: (e) => handleClick(e, annotation),
1771
2382
  style: {
@@ -1807,6 +2418,11 @@ function Annotations(annotationsProps) {
1807
2418
  annotation.object,
1808
2419
  false
1809
2420
  ),
2421
+ isRotatable: resolveInteractionProp(
2422
+ tool == null ? void 0 : tool.interaction.isRotatable,
2423
+ annotation.object,
2424
+ false
2425
+ ),
1810
2426
  selectionMenu,
1811
2427
  onSelect: (e) => handleClick(e, annotation),
1812
2428
  zIndex: 0,
@@ -1841,6 +2457,11 @@ function Annotations(annotationsProps) {
1841
2457
  annotation.object,
1842
2458
  false
1843
2459
  ),
2460
+ isRotatable: resolveInteractionProp(
2461
+ tool == null ? void 0 : tool.interaction.isRotatable,
2462
+ annotation.object,
2463
+ false
2464
+ ),
1844
2465
  selectionMenu,
1845
2466
  onSelect: (e) => handleClick(e, annotation),
1846
2467
  zIndex: 0,
@@ -1875,6 +2496,11 @@ function Annotations(annotationsProps) {
1875
2496
  annotation.object,
1876
2497
  false
1877
2498
  ),
2499
+ isRotatable: resolveInteractionProp(
2500
+ tool == null ? void 0 : tool.interaction.isRotatable,
2501
+ annotation.object,
2502
+ false
2503
+ ),
1878
2504
  selectionMenu,
1879
2505
  onSelect: (e) => handleClick(e, annotation),
1880
2506
  zIndex: 0,
@@ -1909,6 +2535,11 @@ function Annotations(annotationsProps) {
1909
2535
  annotation.object,
1910
2536
  false
1911
2537
  ),
2538
+ isRotatable: resolveInteractionProp(
2539
+ tool == null ? void 0 : tool.interaction.isRotatable,
2540
+ annotation.object,
2541
+ false
2542
+ ),
1912
2543
  selectionMenu,
1913
2544
  onSelect: (e) => handleClick(e, annotation),
1914
2545
  zIndex: 0,
@@ -1943,6 +2574,11 @@ function Annotations(annotationsProps) {
1943
2574
  annotation.object,
1944
2575
  false
1945
2576
  ),
2577
+ isRotatable: resolveInteractionProp(
2578
+ tool == null ? void 0 : tool.interaction.isRotatable,
2579
+ annotation.object,
2580
+ true
2581
+ ),
1946
2582
  selectionMenu,
1947
2583
  onSelect: (e) => handleClick(e, annotation),
1948
2584
  vertexConfig: {
@@ -1999,6 +2635,11 @@ function Annotations(annotationsProps) {
1999
2635
  annotation.object,
2000
2636
  false
2001
2637
  ),
2638
+ isRotatable: resolveInteractionProp(
2639
+ tool == null ? void 0 : tool.interaction.isRotatable,
2640
+ annotation.object,
2641
+ true
2642
+ ),
2002
2643
  selectionMenu,
2003
2644
  onSelect: (e) => handleClick(e, annotation),
2004
2645
  vertexConfig: {
@@ -2049,6 +2690,11 @@ function Annotations(annotationsProps) {
2049
2690
  annotation.object,
2050
2691
  false
2051
2692
  ),
2693
+ isRotatable: resolveInteractionProp(
2694
+ tool == null ? void 0 : tool.interaction.isRotatable,
2695
+ annotation.object,
2696
+ true
2697
+ ),
2052
2698
  selectionMenu,
2053
2699
  onSelect: (e) => handleClick(e, annotation),
2054
2700
  vertexConfig: {
@@ -2095,6 +2741,11 @@ function Annotations(annotationsProps) {
2095
2741
  annotation.object,
2096
2742
  false
2097
2743
  ),
2744
+ isRotatable: resolveInteractionProp(
2745
+ tool == null ? void 0 : tool.interaction.isRotatable,
2746
+ annotation.object,
2747
+ true
2748
+ ),
2098
2749
  selectionMenu,
2099
2750
  onSelect: (e) => handleClick(e, annotation),
2100
2751
  style: {
@@ -2145,6 +2796,11 @@ function Annotations(annotationsProps) {
2145
2796
  annotation.object,
2146
2797
  false
2147
2798
  ),
2799
+ isRotatable: resolveInteractionProp(
2800
+ tool == null ? void 0 : tool.interaction.isRotatable,
2801
+ annotation.object,
2802
+ true
2803
+ ),
2148
2804
  selectionMenu,
2149
2805
  onSelect: (e) => handleClick(e, annotation),
2150
2806
  style: {
@@ -2177,6 +2833,7 @@ function Annotations(annotationsProps) {
2177
2833
  isDraggable: false,
2178
2834
  isResizable: false,
2179
2835
  lockAspectRatio: false,
2836
+ isRotatable: false,
2180
2837
  selectionMenu: hasIRT ? void 0 : selectionMenu,
2181
2838
  onSelect: (e) => handleLinkClick(e, annotation),
2182
2839
  ...annotationsProps,
@@ -2208,8 +2865,12 @@ function Annotations(annotationsProps) {
2208
2865
  selectedAnnotations: selectedAnnotationsOnPage,
2209
2866
  isDraggable: areAllSelectedDraggable,
2210
2867
  isResizable: areAllSelectedResizable,
2868
+ isRotatable: areAllSelectedRotatable,
2869
+ lockAspectRatio: shouldLockGroupAspectRatio,
2211
2870
  resizeUI: annotationsProps.resizeUI,
2871
+ rotationUI: annotationsProps.rotationUI,
2212
2872
  selectionOutlineColor: annotationsProps.selectionOutlineColor,
2873
+ selectionOutline: annotationsProps.groupSelectionOutline ?? annotationsProps.selectionOutline,
2213
2874
  groupSelectionMenu: annotationsProps.groupSelectionMenu
2214
2875
  }
2215
2876
  )
@@ -2449,7 +3110,10 @@ function AnnotationLayer({
2449
3110
  groupSelectionMenu,
2450
3111
  resizeUI,
2451
3112
  vertexUI,
3113
+ rotationUI,
2452
3114
  selectionOutlineColor,
3115
+ selectionOutline,
3116
+ groupSelectionOutline,
2453
3117
  customAnnotationRenderer,
2454
3118
  annotationRenderers,
2455
3119
  ...props
@@ -2500,7 +3164,10 @@ function AnnotationLayer({
2500
3164
  pageHeight: height,
2501
3165
  resizeUI,
2502
3166
  vertexUI,
3167
+ rotationUI,
2503
3168
  selectionOutlineColor,
3169
+ selectionOutline,
3170
+ groupSelectionOutline,
2504
3171
  customAnnotationRenderer,
2505
3172
  annotationRenderers: allRenderers
2506
3173
  }