@embedpdf/utils 2.4.1 → 2.6.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.
- package/dist/preact/index.cjs +1 -1
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.js +834 -317
- package/dist/preact/index.js.map +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +834 -317
- package/dist/react/index.js.map +1 -1
- package/dist/shared/hooks/use-drag-resize.d.ts +4 -0
- package/dist/shared/hooks/use-interaction-handles.d.ts +18 -2
- package/dist/shared/plugin-interaction-primitives/drag-resize-controller.d.ts +49 -23
- package/dist/shared/plugin-interaction-primitives/index.d.ts +1 -0
- package/dist/shared/plugin-interaction-primitives/resize-geometry.d.ts +72 -0
- package/dist/shared/plugin-interaction-primitives/utils.d.ts +33 -0
- package/dist/shared-preact/hooks/use-drag-resize.d.ts +4 -0
- package/dist/shared-preact/hooks/use-interaction-handles.d.ts +18 -2
- package/dist/shared-preact/plugin-interaction-primitives/drag-resize-controller.d.ts +49 -23
- package/dist/shared-preact/plugin-interaction-primitives/index.d.ts +1 -0
- package/dist/shared-preact/plugin-interaction-primitives/resize-geometry.d.ts +72 -0
- package/dist/shared-preact/plugin-interaction-primitives/utils.d.ts +33 -0
- package/dist/shared-react/hooks/use-drag-resize.d.ts +4 -0
- package/dist/shared-react/hooks/use-interaction-handles.d.ts +18 -2
- package/dist/shared-react/plugin-interaction-primitives/drag-resize-controller.d.ts +49 -23
- package/dist/shared-react/plugin-interaction-primitives/index.d.ts +1 -0
- package/dist/shared-react/plugin-interaction-primitives/resize-geometry.d.ts +72 -0
- package/dist/shared-react/plugin-interaction-primitives/utils.d.ts +33 -0
- package/dist/shared-svelte/plugin-interaction-primitives/drag-resize-controller.d.ts +49 -23
- package/dist/shared-svelte/plugin-interaction-primitives/index.d.ts +1 -0
- package/dist/shared-svelte/plugin-interaction-primitives/resize-geometry.d.ts +72 -0
- package/dist/shared-svelte/plugin-interaction-primitives/utils.d.ts +33 -0
- package/dist/shared-vue/plugin-interaction-primitives/drag-resize-controller.d.ts +49 -23
- package/dist/shared-vue/plugin-interaction-primitives/index.d.ts +1 -0
- package/dist/shared-vue/plugin-interaction-primitives/resize-geometry.d.ts +72 -0
- package/dist/shared-vue/plugin-interaction-primitives/utils.d.ts +33 -0
- package/dist/svelte/hooks/use-drag-resize.svelte.d.ts +1 -0
- package/dist/svelte/hooks/use-interaction-handles.svelte.d.ts +16 -2
- package/dist/svelte/index.cjs +1 -1
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.js +680 -288
- package/dist/svelte/index.js.map +1 -1
- package/dist/vue/hooks/use-drag-resize.d.ts +9 -0
- package/dist/vue/hooks/use-interaction-handles.d.ts +17 -2
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.js +716 -292
- package/dist/vue/index.js.map +1 -1
- package/package.json +2 -2
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { getCounterRotation } from "@embedpdf/utils";
|
|
3
3
|
import { useRef, useEffect, Fragment, useCallback, useMemo } from "react";
|
|
4
|
+
import { rotatePointAround, calculateRotatedRectAABB, normalizeAngle } from "@embedpdf/models";
|
|
4
5
|
const dblClickProp = "onDoubleClick";
|
|
5
6
|
function CounterRotate({ children, ...props }) {
|
|
6
7
|
const { rect, rotation } = props;
|
|
@@ -48,12 +49,405 @@ function CounterRotate({ children, ...props }) {
|
|
|
48
49
|
}
|
|
49
50
|
}) });
|
|
50
51
|
}
|
|
52
|
+
const ROTATION_HANDLE_MARGIN = 35;
|
|
53
|
+
const HANDLE_BASE_ANGLE = {
|
|
54
|
+
n: 0,
|
|
55
|
+
ne: 45,
|
|
56
|
+
e: 90,
|
|
57
|
+
se: 135,
|
|
58
|
+
s: 180,
|
|
59
|
+
sw: 225,
|
|
60
|
+
w: 270,
|
|
61
|
+
nw: 315
|
|
62
|
+
};
|
|
63
|
+
const SECTOR_CURSORS = [
|
|
64
|
+
"ns-resize",
|
|
65
|
+
// 0: north
|
|
66
|
+
"nesw-resize",
|
|
67
|
+
// 1: NE
|
|
68
|
+
"ew-resize",
|
|
69
|
+
// 2: east
|
|
70
|
+
"nwse-resize",
|
|
71
|
+
// 3: SE
|
|
72
|
+
"ns-resize",
|
|
73
|
+
// 4: south
|
|
74
|
+
"nesw-resize",
|
|
75
|
+
// 5: SW
|
|
76
|
+
"ew-resize",
|
|
77
|
+
// 6: west
|
|
78
|
+
"nwse-resize"
|
|
79
|
+
// 7: NW
|
|
80
|
+
];
|
|
81
|
+
function diagonalCursor(handle, pageQuarterTurns, annotationRotation = 0) {
|
|
82
|
+
const pageAngle = pageQuarterTurns * 90;
|
|
83
|
+
const totalAngle = HANDLE_BASE_ANGLE[handle] + pageAngle + annotationRotation;
|
|
84
|
+
const normalized = (totalAngle % 360 + 360) % 360;
|
|
85
|
+
const sector = Math.round(normalized / 45) % 8;
|
|
86
|
+
return SECTOR_CURSORS[sector];
|
|
87
|
+
}
|
|
88
|
+
function edgeOffset(k, spacing, mode) {
|
|
89
|
+
const base = -k / 2;
|
|
90
|
+
if (mode === "center") return base;
|
|
91
|
+
return mode === "outside" ? base - spacing : base + spacing;
|
|
92
|
+
}
|
|
93
|
+
function describeResizeFromConfig(cfg, ui = {}) {
|
|
94
|
+
const {
|
|
95
|
+
handleSize = 8,
|
|
96
|
+
spacing = 1,
|
|
97
|
+
offsetMode = "outside",
|
|
98
|
+
includeSides = false,
|
|
99
|
+
zIndex = 3,
|
|
100
|
+
rotationAwareCursor = true
|
|
101
|
+
} = ui;
|
|
102
|
+
const pageQuarterTurns = (cfg.pageRotation ?? 0) % 4;
|
|
103
|
+
const annotationRot = cfg.annotationRotation ?? 0;
|
|
104
|
+
const off = (edge) => ({
|
|
105
|
+
[edge]: edgeOffset(handleSize, spacing, offsetMode) + "px"
|
|
106
|
+
});
|
|
107
|
+
const corners = [
|
|
108
|
+
["nw", { ...off("top"), ...off("left") }],
|
|
109
|
+
["ne", { ...off("top"), ...off("right") }],
|
|
110
|
+
["sw", { ...off("bottom"), ...off("left") }],
|
|
111
|
+
["se", { ...off("bottom"), ...off("right") }]
|
|
112
|
+
];
|
|
113
|
+
const sides = includeSides ? [
|
|
114
|
+
["n", { ...off("top"), left: `calc(50% - ${handleSize / 2}px)` }],
|
|
115
|
+
["s", { ...off("bottom"), left: `calc(50% - ${handleSize / 2}px)` }],
|
|
116
|
+
["w", { ...off("left"), top: `calc(50% - ${handleSize / 2}px)` }],
|
|
117
|
+
["e", { ...off("right"), top: `calc(50% - ${handleSize / 2}px)` }]
|
|
118
|
+
] : [];
|
|
119
|
+
const all = [...corners, ...sides];
|
|
120
|
+
return all.map(([handle, pos]) => ({
|
|
121
|
+
handle,
|
|
122
|
+
style: {
|
|
123
|
+
position: "absolute",
|
|
124
|
+
width: handleSize + "px",
|
|
125
|
+
height: handleSize + "px",
|
|
126
|
+
borderRadius: "50%",
|
|
127
|
+
zIndex,
|
|
128
|
+
cursor: rotationAwareCursor ? diagonalCursor(handle, pageQuarterTurns, annotationRot) : "default",
|
|
129
|
+
pointerEvents: "auto",
|
|
130
|
+
touchAction: "none",
|
|
131
|
+
...pos
|
|
132
|
+
},
|
|
133
|
+
attrs: { "data-epdf-handle": handle }
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function describeVerticesFromConfig(cfg, ui = {}, liveVertices) {
|
|
137
|
+
const { vertexSize = 12, zIndex = 4 } = ui;
|
|
138
|
+
const rect = cfg.element;
|
|
139
|
+
const scale = cfg.scale ?? 1;
|
|
140
|
+
const verts = liveVertices ?? cfg.vertices ?? [];
|
|
141
|
+
return verts.map((v, i) => {
|
|
142
|
+
const left = (v.x - rect.origin.x) * scale - vertexSize / 2;
|
|
143
|
+
const top = (v.y - rect.origin.y) * scale - vertexSize / 2;
|
|
144
|
+
return {
|
|
145
|
+
handle: "nw",
|
|
146
|
+
// not used; kept for type
|
|
147
|
+
style: {
|
|
148
|
+
position: "absolute",
|
|
149
|
+
left: left + "px",
|
|
150
|
+
top: top + "px",
|
|
151
|
+
width: vertexSize + "px",
|
|
152
|
+
height: vertexSize + "px",
|
|
153
|
+
borderRadius: "50%",
|
|
154
|
+
cursor: "pointer",
|
|
155
|
+
zIndex,
|
|
156
|
+
pointerEvents: "auto",
|
|
157
|
+
touchAction: "none"
|
|
158
|
+
},
|
|
159
|
+
attrs: { "data-epdf-vertex": i }
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function describeRotationFromConfig(cfg, ui = {}, currentAngle = 0) {
|
|
164
|
+
const { handleSize = 16, zIndex = 5, showConnector = true, connectorWidth = 1 } = ui;
|
|
165
|
+
const scale = cfg.scale ?? 1;
|
|
166
|
+
const rect = cfg.element;
|
|
167
|
+
const orbitRect = cfg.rotationElement ?? rect;
|
|
168
|
+
const orbitCenter = cfg.rotationCenter ?? {
|
|
169
|
+
x: rect.origin.x + rect.size.width / 2,
|
|
170
|
+
y: rect.origin.y + rect.size.height / 2
|
|
171
|
+
};
|
|
172
|
+
orbitRect.size.width * scale;
|
|
173
|
+
orbitRect.size.height * scale;
|
|
174
|
+
const centerX = (orbitCenter.x - orbitRect.origin.x) * scale;
|
|
175
|
+
const centerY = (orbitCenter.y - orbitRect.origin.y) * scale;
|
|
176
|
+
const angleRad = currentAngle * Math.PI / 180;
|
|
177
|
+
const margin = ui.margin ?? ROTATION_HANDLE_MARGIN;
|
|
178
|
+
const radius = rect.size.height * scale / 2 + margin;
|
|
179
|
+
const handleCenterX = centerX + radius * Math.sin(angleRad);
|
|
180
|
+
const handleCenterY = centerY - radius * Math.cos(angleRad);
|
|
181
|
+
const handleLeft = handleCenterX - handleSize / 2;
|
|
182
|
+
const handleTop = handleCenterY - handleSize / 2;
|
|
183
|
+
return {
|
|
184
|
+
handleStyle: {
|
|
185
|
+
position: "absolute",
|
|
186
|
+
left: handleLeft + "px",
|
|
187
|
+
top: handleTop + "px",
|
|
188
|
+
width: handleSize + "px",
|
|
189
|
+
height: handleSize + "px",
|
|
190
|
+
borderRadius: "50%",
|
|
191
|
+
cursor: "grab",
|
|
192
|
+
zIndex,
|
|
193
|
+
pointerEvents: "auto",
|
|
194
|
+
touchAction: "none"
|
|
195
|
+
},
|
|
196
|
+
connectorStyle: showConnector ? {
|
|
197
|
+
position: "absolute",
|
|
198
|
+
left: centerX - connectorWidth / 2 + "px",
|
|
199
|
+
top: centerY - radius + "px",
|
|
200
|
+
width: connectorWidth + "px",
|
|
201
|
+
height: radius + "px",
|
|
202
|
+
transformOrigin: "center bottom",
|
|
203
|
+
transform: `rotate(${currentAngle}deg)`,
|
|
204
|
+
zIndex: zIndex - 1,
|
|
205
|
+
pointerEvents: "none"
|
|
206
|
+
} : {},
|
|
207
|
+
radius,
|
|
208
|
+
attrs: { "data-epdf-rotation-handle": true }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
51
211
|
function getAnchor(handle) {
|
|
52
212
|
return {
|
|
53
213
|
x: handle.includes("e") ? "left" : handle.includes("w") ? "right" : "center",
|
|
54
214
|
y: handle.includes("s") ? "top" : handle.includes("n") ? "bottom" : "center"
|
|
55
215
|
};
|
|
56
216
|
}
|
|
217
|
+
function getAnchorPoint(rect, anchor) {
|
|
218
|
+
const x = anchor.x === "left" ? rect.origin.x : anchor.x === "right" ? rect.origin.x + rect.size.width : rect.origin.x + rect.size.width / 2;
|
|
219
|
+
const y = anchor.y === "top" ? rect.origin.y : anchor.y === "bottom" ? rect.origin.y + rect.size.height : rect.origin.y + rect.size.height / 2;
|
|
220
|
+
return { x, y };
|
|
221
|
+
}
|
|
222
|
+
function applyResizeDelta(startRect, delta, anchor) {
|
|
223
|
+
let x = startRect.origin.x;
|
|
224
|
+
let y = startRect.origin.y;
|
|
225
|
+
let width = startRect.size.width;
|
|
226
|
+
let height = startRect.size.height;
|
|
227
|
+
if (anchor.x === "left") {
|
|
228
|
+
width += delta.x;
|
|
229
|
+
} else if (anchor.x === "right") {
|
|
230
|
+
x += delta.x;
|
|
231
|
+
width -= delta.x;
|
|
232
|
+
}
|
|
233
|
+
if (anchor.y === "top") {
|
|
234
|
+
height += delta.y;
|
|
235
|
+
} else if (anchor.y === "bottom") {
|
|
236
|
+
y += delta.y;
|
|
237
|
+
height -= delta.y;
|
|
238
|
+
}
|
|
239
|
+
return { origin: { x, y }, size: { width, height } };
|
|
240
|
+
}
|
|
241
|
+
function enforceAspectRatio(rect, startRect, anchor, aspectRatio) {
|
|
242
|
+
let { x, y } = rect.origin;
|
|
243
|
+
let { width, height } = rect.size;
|
|
244
|
+
const isEdgeHandle = anchor.x === "center" || anchor.y === "center";
|
|
245
|
+
if (isEdgeHandle) {
|
|
246
|
+
if (anchor.y === "center") {
|
|
247
|
+
height = width / aspectRatio;
|
|
248
|
+
y = startRect.origin.y + (startRect.size.height - height) / 2;
|
|
249
|
+
} else {
|
|
250
|
+
width = height * aspectRatio;
|
|
251
|
+
x = startRect.origin.x + (startRect.size.width - width) / 2;
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const dw = Math.abs(width - startRect.size.width);
|
|
255
|
+
const dh = Math.abs(height - startRect.size.height);
|
|
256
|
+
const total = dw + dh;
|
|
257
|
+
if (total === 0) {
|
|
258
|
+
width = startRect.size.width;
|
|
259
|
+
height = startRect.size.height;
|
|
260
|
+
} else {
|
|
261
|
+
const wWeight = dw / total;
|
|
262
|
+
const hWeight = dh / total;
|
|
263
|
+
const wFromW = width;
|
|
264
|
+
const hFromW = width / aspectRatio;
|
|
265
|
+
const wFromH = height * aspectRatio;
|
|
266
|
+
const hFromH = height;
|
|
267
|
+
width = wWeight * wFromW + hWeight * wFromH;
|
|
268
|
+
height = wWeight * hFromW + hWeight * hFromH;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (anchor.x === "right") {
|
|
272
|
+
x = startRect.origin.x + startRect.size.width - width;
|
|
273
|
+
}
|
|
274
|
+
if (anchor.y === "bottom") {
|
|
275
|
+
y = startRect.origin.y + startRect.size.height - height;
|
|
276
|
+
}
|
|
277
|
+
return { origin: { x, y }, size: { width, height } };
|
|
278
|
+
}
|
|
279
|
+
function clampToBounds(rect, startRect, anchor, bbox, maintainAspectRatio) {
|
|
280
|
+
if (!bbox) return rect;
|
|
281
|
+
let { x, y } = rect.origin;
|
|
282
|
+
let { width, height } = rect.size;
|
|
283
|
+
width = Math.max(1, width);
|
|
284
|
+
height = Math.max(1, height);
|
|
285
|
+
const anchorX = anchor.x === "left" ? startRect.origin.x : startRect.origin.x + startRect.size.width;
|
|
286
|
+
const anchorY = anchor.y === "top" ? startRect.origin.y : startRect.origin.y + startRect.size.height;
|
|
287
|
+
const maxW = anchor.x === "left" ? bbox.width - anchorX : anchor.x === "right" ? anchorX : Math.min(startRect.origin.x, bbox.width - startRect.origin.x - startRect.size.width) * 2 + startRect.size.width;
|
|
288
|
+
const maxH = anchor.y === "top" ? bbox.height - anchorY : anchor.y === "bottom" ? anchorY : Math.min(startRect.origin.y, bbox.height - startRect.origin.y - startRect.size.height) * 2 + startRect.size.height;
|
|
289
|
+
if (maintainAspectRatio) {
|
|
290
|
+
const scaleW = width > maxW ? maxW / width : 1;
|
|
291
|
+
const scaleH = height > maxH ? maxH / height : 1;
|
|
292
|
+
const scale = Math.min(scaleW, scaleH);
|
|
293
|
+
if (scale < 1) {
|
|
294
|
+
width *= scale;
|
|
295
|
+
height *= scale;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
width = Math.min(width, maxW);
|
|
299
|
+
height = Math.min(height, maxH);
|
|
300
|
+
}
|
|
301
|
+
if (anchor.x === "left") {
|
|
302
|
+
x = anchorX;
|
|
303
|
+
} else if (anchor.x === "right") {
|
|
304
|
+
x = anchorX - width;
|
|
305
|
+
} else {
|
|
306
|
+
x = startRect.origin.x + (startRect.size.width - width) / 2;
|
|
307
|
+
}
|
|
308
|
+
if (anchor.y === "top") {
|
|
309
|
+
y = anchorY;
|
|
310
|
+
} else if (anchor.y === "bottom") {
|
|
311
|
+
y = anchorY - height;
|
|
312
|
+
} else {
|
|
313
|
+
y = startRect.origin.y + (startRect.size.height - height) / 2;
|
|
314
|
+
}
|
|
315
|
+
x = Math.max(0, Math.min(x, bbox.width - width));
|
|
316
|
+
y = Math.max(0, Math.min(y, bbox.height - height));
|
|
317
|
+
return { origin: { x, y }, size: { width, height } };
|
|
318
|
+
}
|
|
319
|
+
function reanchorRect(rect, startRect, anchor) {
|
|
320
|
+
let x;
|
|
321
|
+
let y;
|
|
322
|
+
if (anchor.x === "left") {
|
|
323
|
+
x = startRect.origin.x;
|
|
324
|
+
} else if (anchor.x === "right") {
|
|
325
|
+
x = startRect.origin.x + startRect.size.width - rect.size.width;
|
|
326
|
+
} else {
|
|
327
|
+
x = startRect.origin.x + (startRect.size.width - rect.size.width) / 2;
|
|
328
|
+
}
|
|
329
|
+
if (anchor.y === "top") {
|
|
330
|
+
y = startRect.origin.y;
|
|
331
|
+
} else if (anchor.y === "bottom") {
|
|
332
|
+
y = startRect.origin.y + startRect.size.height - rect.size.height;
|
|
333
|
+
} else {
|
|
334
|
+
y = startRect.origin.y + (startRect.size.height - rect.size.height) / 2;
|
|
335
|
+
}
|
|
336
|
+
return { origin: { x, y }, size: rect.size };
|
|
337
|
+
}
|
|
338
|
+
function applyConstraints(position, constraints, maintainAspectRatio, skipBoundingClamp = false) {
|
|
339
|
+
if (!constraints) return position;
|
|
340
|
+
let {
|
|
341
|
+
origin: { x, y },
|
|
342
|
+
size: { width, height }
|
|
343
|
+
} = position;
|
|
344
|
+
const minW = constraints.minWidth ?? 1;
|
|
345
|
+
const minH = constraints.minHeight ?? 1;
|
|
346
|
+
const maxW = constraints.maxWidth;
|
|
347
|
+
const maxH = constraints.maxHeight;
|
|
348
|
+
if (maintainAspectRatio && width > 0 && height > 0) {
|
|
349
|
+
const ratio = width / height;
|
|
350
|
+
if (width < minW) {
|
|
351
|
+
width = minW;
|
|
352
|
+
height = width / ratio;
|
|
353
|
+
}
|
|
354
|
+
if (height < minH) {
|
|
355
|
+
height = minH;
|
|
356
|
+
width = height * ratio;
|
|
357
|
+
}
|
|
358
|
+
if (maxW !== void 0 && width > maxW) {
|
|
359
|
+
width = maxW;
|
|
360
|
+
height = width / ratio;
|
|
361
|
+
}
|
|
362
|
+
if (maxH !== void 0 && height > maxH) {
|
|
363
|
+
height = maxH;
|
|
364
|
+
width = height * ratio;
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
width = Math.max(minW, width);
|
|
368
|
+
height = Math.max(minH, height);
|
|
369
|
+
if (maxW !== void 0) width = Math.min(maxW, width);
|
|
370
|
+
if (maxH !== void 0) height = Math.min(maxH, height);
|
|
371
|
+
}
|
|
372
|
+
if (constraints.boundingBox && !skipBoundingClamp) {
|
|
373
|
+
x = Math.max(0, Math.min(x, constraints.boundingBox.width - width));
|
|
374
|
+
y = Math.max(0, Math.min(y, constraints.boundingBox.height - height));
|
|
375
|
+
}
|
|
376
|
+
return { origin: { x, y }, size: { width, height } };
|
|
377
|
+
}
|
|
378
|
+
function isRectWithinRotatedBounds(rect, angleDegrees, bbox) {
|
|
379
|
+
const eps = 1e-6;
|
|
380
|
+
const aabb = calculateRotatedRectAABB(rect, angleDegrees);
|
|
381
|
+
return aabb.origin.x >= -eps && aabb.origin.y >= -eps && aabb.origin.x + aabb.size.width <= bbox.width + eps && aabb.origin.y + aabb.size.height <= bbox.height + eps;
|
|
382
|
+
}
|
|
383
|
+
function computeResizeStep(delta, handle, config, clampLocalBounds, skipConstraintBoundingClamp) {
|
|
384
|
+
const { startRect, maintainAspectRatio = false, annotationRotation = 0, constraints } = config;
|
|
385
|
+
const anchor = getAnchor(handle);
|
|
386
|
+
const aspectRatio = startRect.size.width / startRect.size.height || 1;
|
|
387
|
+
let rect = applyResizeDelta(startRect, delta, anchor);
|
|
388
|
+
if (maintainAspectRatio) {
|
|
389
|
+
rect = enforceAspectRatio(rect, startRect, anchor, aspectRatio);
|
|
390
|
+
}
|
|
391
|
+
if (clampLocalBounds) {
|
|
392
|
+
rect = clampToBounds(rect, startRect, anchor, constraints == null ? void 0 : constraints.boundingBox, maintainAspectRatio);
|
|
393
|
+
}
|
|
394
|
+
rect = applyConstraints(rect, constraints, maintainAspectRatio, skipConstraintBoundingClamp);
|
|
395
|
+
if (skipConstraintBoundingClamp) {
|
|
396
|
+
rect = reanchorRect(rect, startRect, anchor);
|
|
397
|
+
}
|
|
398
|
+
if (annotationRotation !== 0) {
|
|
399
|
+
const anchorPt = getAnchorPoint(startRect, anchor);
|
|
400
|
+
const oldCenter = {
|
|
401
|
+
x: startRect.origin.x + startRect.size.width / 2,
|
|
402
|
+
y: startRect.origin.y + startRect.size.height / 2
|
|
403
|
+
};
|
|
404
|
+
const newCenter = {
|
|
405
|
+
x: rect.origin.x + rect.size.width / 2,
|
|
406
|
+
y: rect.origin.y + rect.size.height / 2
|
|
407
|
+
};
|
|
408
|
+
const oldVisual = rotatePointAround(anchorPt, oldCenter, annotationRotation);
|
|
409
|
+
const newVisual = rotatePointAround(anchorPt, newCenter, annotationRotation);
|
|
410
|
+
rect = {
|
|
411
|
+
origin: {
|
|
412
|
+
x: rect.origin.x + (oldVisual.x - newVisual.x),
|
|
413
|
+
y: rect.origin.y + (oldVisual.y - newVisual.y)
|
|
414
|
+
},
|
|
415
|
+
size: rect.size
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return rect;
|
|
419
|
+
}
|
|
420
|
+
function computeResizedRect(delta, handle, config) {
|
|
421
|
+
const { annotationRotation = 0, constraints } = config;
|
|
422
|
+
const bbox = constraints == null ? void 0 : constraints.boundingBox;
|
|
423
|
+
if (annotationRotation !== 0 && bbox) {
|
|
424
|
+
const target = computeResizeStep(delta, handle, config, false, true);
|
|
425
|
+
if (isRectWithinRotatedBounds(target, annotationRotation, bbox)) {
|
|
426
|
+
return target;
|
|
427
|
+
}
|
|
428
|
+
let best = computeResizeStep({ x: 0, y: 0 }, handle, config, false, true);
|
|
429
|
+
let low = 0;
|
|
430
|
+
let high = 1;
|
|
431
|
+
for (let i = 0; i < 20; i += 1) {
|
|
432
|
+
const mid = (low + high) / 2;
|
|
433
|
+
const trial = computeResizeStep(
|
|
434
|
+
{ x: delta.x * mid, y: delta.y * mid },
|
|
435
|
+
handle,
|
|
436
|
+
config,
|
|
437
|
+
false,
|
|
438
|
+
true
|
|
439
|
+
);
|
|
440
|
+
if (isRectWithinRotatedBounds(trial, annotationRotation, bbox)) {
|
|
441
|
+
best = trial;
|
|
442
|
+
low = mid;
|
|
443
|
+
} else {
|
|
444
|
+
high = mid;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return best;
|
|
448
|
+
}
|
|
449
|
+
return computeResizeStep(delta, handle, config, true, false);
|
|
450
|
+
}
|
|
57
451
|
class DragResizeController {
|
|
58
452
|
constructor(config, onUpdate) {
|
|
59
453
|
this.config = config;
|
|
@@ -61,29 +455,41 @@ class DragResizeController {
|
|
|
61
455
|
this.state = "idle";
|
|
62
456
|
this.startPoint = null;
|
|
63
457
|
this.startElement = null;
|
|
458
|
+
this.startRotationElement = null;
|
|
459
|
+
this.gestureRotationCenter = null;
|
|
64
460
|
this.activeHandle = null;
|
|
65
461
|
this.currentPosition = null;
|
|
66
462
|
this.activeVertexIndex = null;
|
|
67
463
|
this.startVertices = [];
|
|
68
464
|
this.currentVertices = [];
|
|
465
|
+
this.rotationCenter = null;
|
|
466
|
+
this.centerScreen = null;
|
|
467
|
+
this.initialRotation = 0;
|
|
468
|
+
this.lastComputedRotation = 0;
|
|
469
|
+
this.rotationDelta = 0;
|
|
470
|
+
this.rotationSnappedAngle = null;
|
|
69
471
|
this.currentVertices = config.vertices || [];
|
|
70
472
|
}
|
|
71
473
|
updateConfig(config) {
|
|
72
474
|
this.config = { ...this.config, ...config };
|
|
73
|
-
this.
|
|
475
|
+
if (this.state !== "vertex-editing") {
|
|
476
|
+
this.currentVertices = config.vertices || [];
|
|
477
|
+
}
|
|
74
478
|
}
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// Gesture start
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
75
482
|
startDrag(clientX, clientY) {
|
|
76
483
|
this.state = "dragging";
|
|
77
484
|
this.startPoint = { x: clientX, y: clientY };
|
|
78
485
|
this.startElement = { ...this.config.element };
|
|
486
|
+
this.startRotationElement = this.config.rotationElement ? { ...this.config.rotationElement } : null;
|
|
79
487
|
this.currentPosition = { ...this.config.element };
|
|
80
488
|
this.onUpdate({
|
|
81
489
|
state: "start",
|
|
82
490
|
transformData: {
|
|
83
491
|
type: "move",
|
|
84
|
-
changes: {
|
|
85
|
-
rect: this.startElement
|
|
86
|
-
}
|
|
492
|
+
changes: { rect: this.startElement }
|
|
87
493
|
}
|
|
88
494
|
});
|
|
89
495
|
}
|
|
@@ -97,9 +503,7 @@ class DragResizeController {
|
|
|
97
503
|
state: "start",
|
|
98
504
|
transformData: {
|
|
99
505
|
type: "resize",
|
|
100
|
-
changes: {
|
|
101
|
-
rect: this.startElement
|
|
102
|
-
},
|
|
506
|
+
changes: { rect: this.startElement },
|
|
103
507
|
metadata: {
|
|
104
508
|
handle: this.activeHandle,
|
|
105
509
|
maintainAspectRatio: this.config.maintainAspectRatio
|
|
@@ -114,45 +518,87 @@ class DragResizeController {
|
|
|
114
518
|
this.activeVertexIndex = vertexIndex;
|
|
115
519
|
this.startPoint = { x: clientX, y: clientY };
|
|
116
520
|
this.startVertices = [...this.currentVertices];
|
|
521
|
+
this.gestureRotationCenter = this.config.rotationCenter ?? {
|
|
522
|
+
x: this.config.element.origin.x + this.config.element.size.width / 2,
|
|
523
|
+
y: this.config.element.origin.y + this.config.element.size.height / 2
|
|
524
|
+
};
|
|
117
525
|
this.onUpdate({
|
|
118
526
|
state: "start",
|
|
119
527
|
transformData: {
|
|
120
528
|
type: "vertex-edit",
|
|
121
|
-
changes: {
|
|
122
|
-
|
|
123
|
-
|
|
529
|
+
changes: { vertices: this.startVertices },
|
|
530
|
+
metadata: { vertexIndex }
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
startRotation(clientX, clientY, initialRotation = 0, orbitRadiusPx) {
|
|
535
|
+
this.state = "rotating";
|
|
536
|
+
this.startPoint = { x: clientX, y: clientY };
|
|
537
|
+
this.startElement = { ...this.config.element };
|
|
538
|
+
this.rotationCenter = this.config.rotationCenter ?? {
|
|
539
|
+
x: this.config.element.origin.x + this.config.element.size.width / 2,
|
|
540
|
+
y: this.config.element.origin.y + this.config.element.size.height / 2
|
|
541
|
+
};
|
|
542
|
+
const { scale = 1 } = this.config;
|
|
543
|
+
const orbitRect = this.config.rotationElement ?? this.config.element;
|
|
544
|
+
const sw = orbitRect.size.width * scale;
|
|
545
|
+
const sh = orbitRect.size.height * scale;
|
|
546
|
+
const radius = orbitRadiusPx ?? Math.max(sw, sh) / 2 + ROTATION_HANDLE_MARGIN;
|
|
547
|
+
const pageRotOffset = (this.config.pageRotation ?? 0) * 90;
|
|
548
|
+
const screenAngleRad = (initialRotation + pageRotOffset) * Math.PI / 180;
|
|
549
|
+
this.centerScreen = {
|
|
550
|
+
x: clientX - radius * Math.sin(screenAngleRad),
|
|
551
|
+
y: clientY + radius * Math.cos(screenAngleRad)
|
|
552
|
+
};
|
|
553
|
+
this.initialRotation = initialRotation;
|
|
554
|
+
this.lastComputedRotation = initialRotation;
|
|
555
|
+
this.rotationDelta = 0;
|
|
556
|
+
this.rotationSnappedAngle = null;
|
|
557
|
+
this.onUpdate({
|
|
558
|
+
state: "start",
|
|
559
|
+
transformData: {
|
|
560
|
+
type: "rotate",
|
|
561
|
+
changes: { rotation: initialRotation },
|
|
124
562
|
metadata: {
|
|
125
|
-
|
|
563
|
+
rotationAngle: initialRotation,
|
|
564
|
+
rotationDelta: 0,
|
|
565
|
+
rotationCenter: this.rotationCenter,
|
|
566
|
+
isSnapped: false
|
|
126
567
|
}
|
|
127
568
|
}
|
|
128
569
|
});
|
|
129
570
|
}
|
|
130
|
-
|
|
571
|
+
// ---------------------------------------------------------------------------
|
|
572
|
+
// Gesture move
|
|
573
|
+
// ---------------------------------------------------------------------------
|
|
574
|
+
move(clientX, clientY, buttons) {
|
|
131
575
|
if (this.state === "idle" || !this.startPoint) return;
|
|
576
|
+
if (buttons !== void 0 && buttons === 0) {
|
|
577
|
+
this.end();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
132
580
|
if (this.state === "dragging" && this.startElement) {
|
|
133
581
|
const delta = this.calculateDelta(clientX, clientY);
|
|
134
582
|
const position = this.calculateDragPosition(delta);
|
|
135
583
|
this.currentPosition = position;
|
|
136
584
|
this.onUpdate({
|
|
137
585
|
state: "move",
|
|
138
|
-
transformData: {
|
|
139
|
-
type: "move",
|
|
140
|
-
changes: {
|
|
141
|
-
rect: position
|
|
142
|
-
}
|
|
143
|
-
}
|
|
586
|
+
transformData: { type: "move", changes: { rect: position } }
|
|
144
587
|
});
|
|
145
588
|
} else if (this.state === "resizing" && this.activeHandle && this.startElement) {
|
|
146
|
-
const delta = this.
|
|
147
|
-
const position =
|
|
589
|
+
const delta = this.calculateLocalDelta(clientX, clientY);
|
|
590
|
+
const position = computeResizedRect(delta, this.activeHandle, {
|
|
591
|
+
startRect: this.startElement,
|
|
592
|
+
maintainAspectRatio: this.config.maintainAspectRatio,
|
|
593
|
+
annotationRotation: this.config.annotationRotation,
|
|
594
|
+
constraints: this.config.constraints
|
|
595
|
+
});
|
|
148
596
|
this.currentPosition = position;
|
|
149
597
|
this.onUpdate({
|
|
150
598
|
state: "move",
|
|
151
599
|
transformData: {
|
|
152
600
|
type: "resize",
|
|
153
|
-
changes: {
|
|
154
|
-
rect: position
|
|
155
|
-
},
|
|
601
|
+
changes: { rect: position },
|
|
156
602
|
metadata: {
|
|
157
603
|
handle: this.activeHandle,
|
|
158
604
|
maintainAspectRatio: this.config.maintainAspectRatio
|
|
@@ -166,16 +612,40 @@ class DragResizeController {
|
|
|
166
612
|
state: "move",
|
|
167
613
|
transformData: {
|
|
168
614
|
type: "vertex-edit",
|
|
169
|
-
changes: {
|
|
170
|
-
|
|
171
|
-
|
|
615
|
+
changes: { vertices },
|
|
616
|
+
metadata: { vertexIndex: this.activeVertexIndex }
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
} else if (this.state === "rotating" && this.rotationCenter) {
|
|
620
|
+
const absoluteAngle = this.calculateAngleFromMouse(clientX, clientY);
|
|
621
|
+
const snapResult = this.applyRotationSnapping(absoluteAngle);
|
|
622
|
+
const snappedAngle = normalizeAngle(snapResult.angle);
|
|
623
|
+
const previousAngle = this.lastComputedRotation;
|
|
624
|
+
const rawDelta = snappedAngle - previousAngle;
|
|
625
|
+
const adjustedDelta = rawDelta > 180 ? rawDelta - 360 : rawDelta < -180 ? rawDelta + 360 : rawDelta;
|
|
626
|
+
this.rotationDelta += adjustedDelta;
|
|
627
|
+
this.lastComputedRotation = snappedAngle;
|
|
628
|
+
this.rotationSnappedAngle = snapResult.isSnapped ? snappedAngle : null;
|
|
629
|
+
this.onUpdate({
|
|
630
|
+
state: "move",
|
|
631
|
+
transformData: {
|
|
632
|
+
type: "rotate",
|
|
633
|
+
changes: { rotation: snappedAngle },
|
|
172
634
|
metadata: {
|
|
173
|
-
|
|
635
|
+
rotationAngle: snappedAngle,
|
|
636
|
+
rotationDelta: this.rotationDelta,
|
|
637
|
+
rotationCenter: this.rotationCenter,
|
|
638
|
+
isSnapped: snapResult.isSnapped,
|
|
639
|
+
snappedAngle: this.rotationSnappedAngle ?? void 0,
|
|
640
|
+
cursorPosition: { clientX, clientY }
|
|
174
641
|
}
|
|
175
642
|
}
|
|
176
643
|
});
|
|
177
644
|
}
|
|
178
645
|
}
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
// Gesture end / cancel
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
179
649
|
end() {
|
|
180
650
|
if (this.state === "idle") return;
|
|
181
651
|
const wasState = this.state;
|
|
@@ -186,23 +656,32 @@ class DragResizeController {
|
|
|
186
656
|
state: "end",
|
|
187
657
|
transformData: {
|
|
188
658
|
type: "vertex-edit",
|
|
189
|
-
changes: {
|
|
190
|
-
|
|
191
|
-
|
|
659
|
+
changes: { vertices: this.currentVertices },
|
|
660
|
+
metadata: { vertexIndex: vertexIndex || void 0 }
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
} else if (wasState === "rotating") {
|
|
664
|
+
this.onUpdate({
|
|
665
|
+
state: "end",
|
|
666
|
+
transformData: {
|
|
667
|
+
type: "rotate",
|
|
668
|
+
changes: { rotation: this.lastComputedRotation },
|
|
192
669
|
metadata: {
|
|
193
|
-
|
|
670
|
+
rotationAngle: this.lastComputedRotation,
|
|
671
|
+
rotationDelta: this.rotationDelta,
|
|
672
|
+
rotationCenter: this.rotationCenter || void 0,
|
|
673
|
+
isSnapped: this.rotationSnappedAngle !== null,
|
|
674
|
+
snappedAngle: this.rotationSnappedAngle ?? void 0
|
|
194
675
|
}
|
|
195
676
|
}
|
|
196
677
|
});
|
|
197
678
|
} else {
|
|
198
|
-
const finalPosition = this.
|
|
679
|
+
const finalPosition = this.currentPosition || this.config.element;
|
|
199
680
|
this.onUpdate({
|
|
200
681
|
state: "end",
|
|
201
682
|
transformData: {
|
|
202
683
|
type: wasState === "dragging" ? "move" : "resize",
|
|
203
|
-
changes: {
|
|
204
|
-
rect: finalPosition
|
|
205
|
-
},
|
|
684
|
+
changes: { rect: finalPosition },
|
|
206
685
|
metadata: wasState === "dragging" ? void 0 : {
|
|
207
686
|
handle: handle || void 0,
|
|
208
687
|
maintainAspectRatio: this.config.maintainAspectRatio
|
|
@@ -219,11 +698,21 @@ class DragResizeController {
|
|
|
219
698
|
state: "end",
|
|
220
699
|
transformData: {
|
|
221
700
|
type: "vertex-edit",
|
|
222
|
-
changes: {
|
|
223
|
-
|
|
224
|
-
|
|
701
|
+
changes: { vertices: this.startVertices },
|
|
702
|
+
metadata: { vertexIndex: this.activeVertexIndex || void 0 }
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
} else if (this.state === "rotating") {
|
|
706
|
+
this.onUpdate({
|
|
707
|
+
state: "end",
|
|
708
|
+
transformData: {
|
|
709
|
+
type: "rotate",
|
|
710
|
+
changes: { rotation: this.initialRotation },
|
|
225
711
|
metadata: {
|
|
226
|
-
|
|
712
|
+
rotationAngle: this.initialRotation,
|
|
713
|
+
rotationDelta: 0,
|
|
714
|
+
rotationCenter: this.rotationCenter || void 0,
|
|
715
|
+
isSnapped: false
|
|
227
716
|
}
|
|
228
717
|
}
|
|
229
718
|
});
|
|
@@ -232,9 +721,7 @@ class DragResizeController {
|
|
|
232
721
|
state: "end",
|
|
233
722
|
transformData: {
|
|
234
723
|
type: this.state === "dragging" ? "move" : "resize",
|
|
235
|
-
changes: {
|
|
236
|
-
rect: this.startElement
|
|
237
|
-
},
|
|
724
|
+
changes: { rect: this.startElement },
|
|
238
725
|
metadata: this.state === "dragging" ? void 0 : {
|
|
239
726
|
handle: this.activeHandle || void 0,
|
|
240
727
|
maintainAspectRatio: this.config.maintainAspectRatio
|
|
@@ -244,18 +731,29 @@ class DragResizeController {
|
|
|
244
731
|
}
|
|
245
732
|
this.reset();
|
|
246
733
|
}
|
|
734
|
+
// ---------------------------------------------------------------------------
|
|
735
|
+
// Private: state management
|
|
736
|
+
// ---------------------------------------------------------------------------
|
|
247
737
|
reset() {
|
|
248
738
|
this.state = "idle";
|
|
249
739
|
this.startPoint = null;
|
|
250
740
|
this.startElement = null;
|
|
741
|
+
this.startRotationElement = null;
|
|
742
|
+
this.gestureRotationCenter = null;
|
|
251
743
|
this.activeHandle = null;
|
|
252
744
|
this.currentPosition = null;
|
|
253
745
|
this.activeVertexIndex = null;
|
|
254
746
|
this.startVertices = [];
|
|
747
|
+
this.rotationCenter = null;
|
|
748
|
+
this.centerScreen = null;
|
|
749
|
+
this.initialRotation = 0;
|
|
750
|
+
this.lastComputedRotation = 0;
|
|
751
|
+
this.rotationDelta = 0;
|
|
752
|
+
this.rotationSnappedAngle = null;
|
|
255
753
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
// Private: coordinate transformation (screen → page → local)
|
|
756
|
+
// ---------------------------------------------------------------------------
|
|
259
757
|
calculateDelta(clientX, clientY) {
|
|
260
758
|
if (!this.startPoint) return { x: 0, y: 0 };
|
|
261
759
|
const rawDelta = {
|
|
@@ -276,18 +774,50 @@ class DragResizeController {
|
|
|
276
774
|
y: -sin * scaledX + cos * scaledY
|
|
277
775
|
};
|
|
278
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Calculate delta projected into the annotation's local (unrotated) coordinate space.
|
|
779
|
+
* Used for resize and vertex-edit where mouse movement must be mapped to the
|
|
780
|
+
* annotation's own axes, accounting for its rotation.
|
|
781
|
+
*/
|
|
782
|
+
calculateLocalDelta(clientX, clientY) {
|
|
783
|
+
const pageDelta = this.calculateDelta(clientX, clientY);
|
|
784
|
+
const { annotationRotation = 0 } = this.config;
|
|
785
|
+
if (annotationRotation === 0) return pageDelta;
|
|
786
|
+
const rad = annotationRotation * Math.PI / 180;
|
|
787
|
+
const cos = Math.cos(rad);
|
|
788
|
+
const sin = Math.sin(rad);
|
|
789
|
+
return {
|
|
790
|
+
x: cos * pageDelta.x + sin * pageDelta.y,
|
|
791
|
+
y: -sin * pageDelta.x + cos * pageDelta.y
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
// ---------------------------------------------------------------------------
|
|
795
|
+
// Private: vertex clamping
|
|
796
|
+
// ---------------------------------------------------------------------------
|
|
279
797
|
clampPoint(p) {
|
|
280
798
|
var _a;
|
|
281
799
|
const bbox = (_a = this.config.constraints) == null ? void 0 : _a.boundingBox;
|
|
282
800
|
if (!bbox) return p;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
801
|
+
const { annotationRotation = 0 } = this.config;
|
|
802
|
+
if (annotationRotation === 0) {
|
|
803
|
+
return {
|
|
804
|
+
x: Math.max(0, Math.min(p.x, bbox.width)),
|
|
805
|
+
y: Math.max(0, Math.min(p.y, bbox.height))
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const center = this.gestureRotationCenter ?? this.config.rotationCenter ?? {
|
|
809
|
+
x: this.config.element.origin.x + this.config.element.size.width / 2,
|
|
810
|
+
y: this.config.element.origin.y + this.config.element.size.height / 2
|
|
286
811
|
};
|
|
812
|
+
const visual = rotatePointAround(p, center, annotationRotation);
|
|
813
|
+
const clampedX = Math.max(0, Math.min(visual.x, bbox.width));
|
|
814
|
+
const clampedY = Math.max(0, Math.min(visual.y, bbox.height));
|
|
815
|
+
if (clampedX === visual.x && clampedY === visual.y) return p;
|
|
816
|
+
return rotatePointAround({ x: clampedX, y: clampedY }, center, -annotationRotation);
|
|
287
817
|
}
|
|
288
818
|
calculateVertexPosition(clientX, clientY) {
|
|
289
819
|
if (this.activeVertexIndex === null) return this.startVertices;
|
|
290
|
-
const delta = this.
|
|
820
|
+
const delta = this.calculateLocalDelta(clientX, clientY);
|
|
291
821
|
const newVertices = [...this.startVertices];
|
|
292
822
|
const currentVertex = newVertices[this.activeVertexIndex];
|
|
293
823
|
const moved = {
|
|
@@ -297,6 +827,9 @@ class DragResizeController {
|
|
|
297
827
|
newVertices[this.activeVertexIndex] = this.clampPoint(moved);
|
|
298
828
|
return newVertices;
|
|
299
829
|
}
|
|
830
|
+
// ---------------------------------------------------------------------------
|
|
831
|
+
// Private: drag position
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
300
833
|
calculateDragPosition(delta) {
|
|
301
834
|
if (!this.startElement) return this.config.element;
|
|
302
835
|
const position = {
|
|
@@ -309,257 +842,77 @@ class DragResizeController {
|
|
|
309
842
|
height: this.startElement.size.height
|
|
310
843
|
}
|
|
311
844
|
};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
rect = this.enforceAspectRatio(rect, anchor, aspectRatio);
|
|
325
|
-
}
|
|
326
|
-
rect = this.clampToBounds(rect, anchor, aspectRatio);
|
|
327
|
-
return this.applyConstraints(rect);
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
331
|
-
*/
|
|
332
|
-
applyResizeDelta(delta, anchor) {
|
|
333
|
-
const start = this.startElement;
|
|
334
|
-
let x = start.origin.x;
|
|
335
|
-
let y = start.origin.y;
|
|
336
|
-
let width = start.size.width;
|
|
337
|
-
let height = start.size.height;
|
|
338
|
-
if (anchor.x === "left") {
|
|
339
|
-
width += delta.x;
|
|
340
|
-
} else if (anchor.x === "right") {
|
|
341
|
-
x += delta.x;
|
|
342
|
-
width -= delta.x;
|
|
343
|
-
}
|
|
344
|
-
if (anchor.y === "top") {
|
|
345
|
-
height += delta.y;
|
|
346
|
-
} else if (anchor.y === "bottom") {
|
|
347
|
-
y += delta.y;
|
|
348
|
-
height -= delta.y;
|
|
349
|
-
}
|
|
350
|
-
return { origin: { x, y }, size: { width, height } };
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Enforce aspect ratio while respecting the anchor.
|
|
354
|
-
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
355
|
-
* For corner handles, the anchor corner stays fixed.
|
|
356
|
-
*/
|
|
357
|
-
enforceAspectRatio(rect, anchor, aspectRatio) {
|
|
358
|
-
const start = this.startElement;
|
|
359
|
-
let { x, y } = rect.origin;
|
|
360
|
-
let { width, height } = rect.size;
|
|
361
|
-
const isEdgeHandle = anchor.x === "center" || anchor.y === "center";
|
|
362
|
-
if (isEdgeHandle) {
|
|
363
|
-
if (anchor.y === "center") {
|
|
364
|
-
height = width / aspectRatio;
|
|
365
|
-
y = start.origin.y + (start.size.height - height) / 2;
|
|
845
|
+
const { annotationRotation = 0, constraints } = this.config;
|
|
846
|
+
const bbox = constraints == null ? void 0 : constraints.boundingBox;
|
|
847
|
+
if (annotationRotation !== 0 && bbox) {
|
|
848
|
+
let aabbW;
|
|
849
|
+
let aabbH;
|
|
850
|
+
let offsetX;
|
|
851
|
+
let offsetY;
|
|
852
|
+
if (this.startRotationElement) {
|
|
853
|
+
aabbW = this.startRotationElement.size.width;
|
|
854
|
+
aabbH = this.startRotationElement.size.height;
|
|
855
|
+
offsetX = this.startRotationElement.origin.x - this.startElement.origin.x;
|
|
856
|
+
offsetY = this.startRotationElement.origin.y - this.startElement.origin.y;
|
|
366
857
|
} else {
|
|
367
|
-
|
|
368
|
-
|
|
858
|
+
const rad = Math.abs(annotationRotation * Math.PI / 180);
|
|
859
|
+
const cos = Math.abs(Math.cos(rad));
|
|
860
|
+
const sin = Math.abs(Math.sin(rad));
|
|
861
|
+
const w = position.size.width;
|
|
862
|
+
const h = position.size.height;
|
|
863
|
+
aabbW = w * cos + h * sin;
|
|
864
|
+
aabbH = w * sin + h * cos;
|
|
865
|
+
offsetX = (w - aabbW) / 2;
|
|
866
|
+
offsetY = (h - aabbH) / 2;
|
|
369
867
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
height = width / aspectRatio;
|
|
375
|
-
} else {
|
|
376
|
-
width = height * aspectRatio;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (anchor.x === "right") {
|
|
380
|
-
x = start.origin.x + start.size.width - width;
|
|
381
|
-
}
|
|
382
|
-
if (anchor.y === "bottom") {
|
|
383
|
-
y = start.origin.y + start.size.height - height;
|
|
868
|
+
let { x, y } = position.origin;
|
|
869
|
+
x = Math.max(-offsetX, Math.min(x, bbox.width - aabbW - offsetX));
|
|
870
|
+
y = Math.max(-offsetY, Math.min(y, bbox.height - aabbH - offsetY));
|
|
871
|
+
return { origin: { x, y }, size: position.size };
|
|
384
872
|
}
|
|
385
|
-
return
|
|
873
|
+
return applyConstraints(position, constraints, this.config.maintainAspectRatio ?? false);
|
|
386
874
|
}
|
|
875
|
+
// ---------------------------------------------------------------------------
|
|
876
|
+
// Private: rotation
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
387
878
|
/**
|
|
388
|
-
*
|
|
879
|
+
* Calculate the angle from the center to a point in screen coordinates.
|
|
389
880
|
*/
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
if (anchor.x === "left") {
|
|
416
|
-
x = anchorX;
|
|
417
|
-
} else if (anchor.x === "right") {
|
|
418
|
-
x = anchorX - width;
|
|
419
|
-
} else {
|
|
420
|
-
x = start.origin.x + (start.size.width - width) / 2;
|
|
421
|
-
}
|
|
422
|
-
if (anchor.y === "top") {
|
|
423
|
-
y = anchorY;
|
|
424
|
-
} else if (anchor.y === "bottom") {
|
|
425
|
-
y = anchorY - height;
|
|
426
|
-
} else {
|
|
427
|
-
y = start.origin.y + (start.size.height - height) / 2;
|
|
428
|
-
}
|
|
429
|
-
x = Math.max(0, Math.min(x, bbox.width - width));
|
|
430
|
-
y = Math.max(0, Math.min(y, bbox.height - height));
|
|
431
|
-
return { origin: { x, y }, size: { width, height } };
|
|
432
|
-
}
|
|
433
|
-
applyConstraints(position) {
|
|
434
|
-
const { constraints } = this.config;
|
|
435
|
-
if (!constraints) return position;
|
|
436
|
-
let {
|
|
437
|
-
origin: { x, y },
|
|
438
|
-
size: { width, height }
|
|
439
|
-
} = position;
|
|
440
|
-
const minW = constraints.minWidth ?? 1;
|
|
441
|
-
const minH = constraints.minHeight ?? 1;
|
|
442
|
-
const maxW = constraints.maxWidth;
|
|
443
|
-
const maxH = constraints.maxHeight;
|
|
444
|
-
if (this.config.maintainAspectRatio && width > 0 && height > 0) {
|
|
445
|
-
const ratio = width / height;
|
|
446
|
-
if (width < minW) {
|
|
447
|
-
width = minW;
|
|
448
|
-
height = width / ratio;
|
|
449
|
-
}
|
|
450
|
-
if (height < minH) {
|
|
451
|
-
height = minH;
|
|
452
|
-
width = height * ratio;
|
|
453
|
-
}
|
|
454
|
-
if (maxW !== void 0 && width > maxW) {
|
|
455
|
-
width = maxW;
|
|
456
|
-
height = width / ratio;
|
|
457
|
-
}
|
|
458
|
-
if (maxH !== void 0 && height > maxH) {
|
|
459
|
-
height = maxH;
|
|
460
|
-
width = height * ratio;
|
|
881
|
+
calculateAngleFromMouse(clientX, clientY) {
|
|
882
|
+
if (!this.centerScreen) return this.initialRotation;
|
|
883
|
+
const dx = clientX - this.centerScreen.x;
|
|
884
|
+
const dy = clientY - this.centerScreen.y;
|
|
885
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
886
|
+
if (dist < 10) return this.lastComputedRotation;
|
|
887
|
+
const pageRotOffset = (this.config.pageRotation ?? 0) * 90;
|
|
888
|
+
const angleDeg = Math.atan2(dy, dx) * (180 / Math.PI) + 90 - pageRotOffset;
|
|
889
|
+
return normalizeAngle(Math.round(angleDeg));
|
|
890
|
+
}
|
|
891
|
+
applyRotationSnapping(angle) {
|
|
892
|
+
const snapAngles = this.config.rotationSnapAngles ?? [0, 90, 180, 270];
|
|
893
|
+
const threshold = this.config.rotationSnapThreshold ?? 4;
|
|
894
|
+
const normalizedAngle = normalizeAngle(angle);
|
|
895
|
+
for (const candidate of snapAngles) {
|
|
896
|
+
const normalizedCandidate = normalizeAngle(candidate);
|
|
897
|
+
const diff = Math.abs(normalizedAngle - normalizedCandidate);
|
|
898
|
+
const minimalDiff = Math.min(diff, 360 - diff);
|
|
899
|
+
if (minimalDiff <= threshold) {
|
|
900
|
+
return {
|
|
901
|
+
angle: normalizedCandidate,
|
|
902
|
+
isSnapped: true,
|
|
903
|
+
snapTarget: normalizedCandidate
|
|
904
|
+
};
|
|
461
905
|
}
|
|
462
|
-
} else {
|
|
463
|
-
width = Math.max(minW, width);
|
|
464
|
-
height = Math.max(minH, height);
|
|
465
|
-
if (maxW !== void 0) width = Math.min(maxW, width);
|
|
466
|
-
if (maxH !== void 0) height = Math.min(maxH, height);
|
|
467
|
-
}
|
|
468
|
-
if (constraints.boundingBox) {
|
|
469
|
-
x = Math.max(0, Math.min(x, constraints.boundingBox.width - width));
|
|
470
|
-
y = Math.max(0, Math.min(y, constraints.boundingBox.height - height));
|
|
471
906
|
}
|
|
472
|
-
return {
|
|
907
|
+
return { angle: normalizedAngle, isSnapped: false };
|
|
473
908
|
}
|
|
474
909
|
}
|
|
475
|
-
function diagonalCursor(handle, rot) {
|
|
476
|
-
const diag0 = {
|
|
477
|
-
nw: "nwse-resize",
|
|
478
|
-
ne: "nesw-resize",
|
|
479
|
-
sw: "nesw-resize",
|
|
480
|
-
se: "nwse-resize"
|
|
481
|
-
};
|
|
482
|
-
if (handle === "n" || handle === "s") return "ns-resize";
|
|
483
|
-
if (handle === "e" || handle === "w") return "ew-resize";
|
|
484
|
-
if (rot % 2 === 0) return diag0[handle];
|
|
485
|
-
return { nw: "nesw-resize", ne: "nwse-resize", sw: "nwse-resize", se: "nesw-resize" }[handle];
|
|
486
|
-
}
|
|
487
|
-
function edgeOffset(k, spacing, mode) {
|
|
488
|
-
const base = -k / 2;
|
|
489
|
-
if (mode === "center") return base;
|
|
490
|
-
return mode === "outside" ? base - spacing : base + spacing;
|
|
491
|
-
}
|
|
492
|
-
function describeResizeFromConfig(cfg, ui = {}) {
|
|
493
|
-
const {
|
|
494
|
-
handleSize = 8,
|
|
495
|
-
spacing = 1,
|
|
496
|
-
offsetMode = "outside",
|
|
497
|
-
includeSides = false,
|
|
498
|
-
zIndex = 3,
|
|
499
|
-
rotationAwareCursor = true
|
|
500
|
-
} = ui;
|
|
501
|
-
const rotation = (cfg.pageRotation ?? 0) % 4;
|
|
502
|
-
const off = (edge) => ({
|
|
503
|
-
[edge]: edgeOffset(handleSize, spacing, offsetMode) + "px"
|
|
504
|
-
});
|
|
505
|
-
const corners = [
|
|
506
|
-
["nw", { ...off("top"), ...off("left") }],
|
|
507
|
-
["ne", { ...off("top"), ...off("right") }],
|
|
508
|
-
["sw", { ...off("bottom"), ...off("left") }],
|
|
509
|
-
["se", { ...off("bottom"), ...off("right") }]
|
|
510
|
-
];
|
|
511
|
-
const sides = includeSides ? [
|
|
512
|
-
["n", { ...off("top"), left: `calc(50% - ${handleSize / 2}px)` }],
|
|
513
|
-
["s", { ...off("bottom"), left: `calc(50% - ${handleSize / 2}px)` }],
|
|
514
|
-
["w", { ...off("left"), top: `calc(50% - ${handleSize / 2}px)` }],
|
|
515
|
-
["e", { ...off("right"), top: `calc(50% - ${handleSize / 2}px)` }]
|
|
516
|
-
] : [];
|
|
517
|
-
const all = [...corners, ...sides];
|
|
518
|
-
return all.map(([handle, pos]) => ({
|
|
519
|
-
handle,
|
|
520
|
-
style: {
|
|
521
|
-
position: "absolute",
|
|
522
|
-
width: handleSize + "px",
|
|
523
|
-
height: handleSize + "px",
|
|
524
|
-
borderRadius: "50%",
|
|
525
|
-
zIndex,
|
|
526
|
-
cursor: rotationAwareCursor ? diagonalCursor(handle, rotation) : "default",
|
|
527
|
-
touchAction: "none",
|
|
528
|
-
...pos
|
|
529
|
-
},
|
|
530
|
-
attrs: { "data-epdf-handle": handle }
|
|
531
|
-
}));
|
|
532
|
-
}
|
|
533
|
-
function describeVerticesFromConfig(cfg, ui = {}, liveVertices) {
|
|
534
|
-
const { vertexSize = 12, zIndex = 4 } = ui;
|
|
535
|
-
const rect = cfg.element;
|
|
536
|
-
const scale = cfg.scale ?? 1;
|
|
537
|
-
const verts = liveVertices ?? cfg.vertices ?? [];
|
|
538
|
-
return verts.map((v, i) => {
|
|
539
|
-
const left = (v.x - rect.origin.x) * scale - vertexSize / 2;
|
|
540
|
-
const top = (v.y - rect.origin.y) * scale - vertexSize / 2;
|
|
541
|
-
return {
|
|
542
|
-
handle: "nw",
|
|
543
|
-
// not used; kept for type
|
|
544
|
-
style: {
|
|
545
|
-
position: "absolute",
|
|
546
|
-
left: left + "px",
|
|
547
|
-
top: top + "px",
|
|
548
|
-
width: vertexSize + "px",
|
|
549
|
-
height: vertexSize + "px",
|
|
550
|
-
borderRadius: "50%",
|
|
551
|
-
cursor: "pointer",
|
|
552
|
-
zIndex,
|
|
553
|
-
touchAction: "none"
|
|
554
|
-
},
|
|
555
|
-
attrs: { "data-epdf-vertex": i }
|
|
556
|
-
};
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
910
|
function useDragResize(options) {
|
|
560
911
|
const { onUpdate, enabled = true, ...config } = options;
|
|
561
912
|
const controllerRef = useRef(null);
|
|
562
913
|
const onUpdateRef = useRef(onUpdate);
|
|
914
|
+
const activePointerIdRef = useRef(null);
|
|
915
|
+
const activeTargetRef = useRef(null);
|
|
563
916
|
useEffect(() => {
|
|
564
917
|
onUpdateRef.current = onUpdate;
|
|
565
918
|
}, [onUpdate]);
|
|
@@ -577,12 +930,81 @@ function useDragResize(options) {
|
|
|
577
930
|
}
|
|
578
931
|
}, [
|
|
579
932
|
config.element,
|
|
933
|
+
config.rotationCenter,
|
|
934
|
+
config.rotationElement,
|
|
580
935
|
config.constraints,
|
|
581
936
|
config.maintainAspectRatio,
|
|
582
937
|
config.pageRotation,
|
|
938
|
+
config.annotationRotation,
|
|
583
939
|
config.scale,
|
|
584
940
|
config.vertices
|
|
585
941
|
]);
|
|
942
|
+
const endPointerSession = useCallback((pointerId) => {
|
|
943
|
+
var _a, _b;
|
|
944
|
+
const activePointerId = activePointerIdRef.current;
|
|
945
|
+
const target = activeTargetRef.current;
|
|
946
|
+
const id = pointerId ?? activePointerId;
|
|
947
|
+
if (target && id !== null) {
|
|
948
|
+
try {
|
|
949
|
+
if ((_a = target.hasPointerCapture) == null ? void 0 : _a.call(target, id)) {
|
|
950
|
+
(_b = target.releasePointerCapture) == null ? void 0 : _b.call(target, id);
|
|
951
|
+
}
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
activePointerIdRef.current = null;
|
|
956
|
+
activeTargetRef.current = null;
|
|
957
|
+
}, []);
|
|
958
|
+
const startPointerSession = useCallback(
|
|
959
|
+
(e) => {
|
|
960
|
+
var _a;
|
|
961
|
+
if (activePointerIdRef.current !== null && activePointerIdRef.current !== e.pointerId) {
|
|
962
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
963
|
+
endPointerSession(activePointerIdRef.current);
|
|
964
|
+
}
|
|
965
|
+
const target = e.currentTarget;
|
|
966
|
+
activePointerIdRef.current = e.pointerId;
|
|
967
|
+
activeTargetRef.current = target;
|
|
968
|
+
try {
|
|
969
|
+
target.setPointerCapture(e.pointerId);
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
[endPointerSession]
|
|
974
|
+
);
|
|
975
|
+
useEffect(() => {
|
|
976
|
+
const eventTarget = globalThis;
|
|
977
|
+
const handleGlobalPointerEnd = (e) => {
|
|
978
|
+
var _a;
|
|
979
|
+
const activePointerId = activePointerIdRef.current;
|
|
980
|
+
if (activePointerId === null || e.pointerId !== activePointerId) return;
|
|
981
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
982
|
+
endPointerSession(e.pointerId);
|
|
983
|
+
};
|
|
984
|
+
const handleWindowBlur = () => {
|
|
985
|
+
var _a;
|
|
986
|
+
if (activePointerIdRef.current === null) return;
|
|
987
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
988
|
+
endPointerSession();
|
|
989
|
+
};
|
|
990
|
+
eventTarget.addEventListener("pointerup", handleGlobalPointerEnd, true);
|
|
991
|
+
eventTarget.addEventListener("pointercancel", handleGlobalPointerEnd, true);
|
|
992
|
+
eventTarget.addEventListener("blur", handleWindowBlur, true);
|
|
993
|
+
return () => {
|
|
994
|
+
eventTarget.removeEventListener("pointerup", handleGlobalPointerEnd, true);
|
|
995
|
+
eventTarget.removeEventListener("pointercancel", handleGlobalPointerEnd, true);
|
|
996
|
+
eventTarget.removeEventListener("blur", handleWindowBlur, true);
|
|
997
|
+
};
|
|
998
|
+
}, [endPointerSession]);
|
|
999
|
+
useEffect(() => {
|
|
1000
|
+
return () => {
|
|
1001
|
+
var _a;
|
|
1002
|
+
if (activePointerIdRef.current !== null) {
|
|
1003
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
1004
|
+
endPointerSession();
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}, [endPointerSession]);
|
|
586
1008
|
const handleDragStart = useCallback(
|
|
587
1009
|
(e) => {
|
|
588
1010
|
var _a;
|
|
@@ -590,23 +1012,46 @@ function useDragResize(options) {
|
|
|
590
1012
|
e.preventDefault();
|
|
591
1013
|
e.stopPropagation();
|
|
592
1014
|
(_a = controllerRef.current) == null ? void 0 : _a.startDrag(e.clientX, e.clientY);
|
|
593
|
-
|
|
1015
|
+
startPointerSession(e);
|
|
594
1016
|
},
|
|
595
|
-
[enabled]
|
|
1017
|
+
[enabled, startPointerSession]
|
|
1018
|
+
);
|
|
1019
|
+
const handleMove = useCallback(
|
|
1020
|
+
(e) => {
|
|
1021
|
+
var _a;
|
|
1022
|
+
e.preventDefault();
|
|
1023
|
+
e.stopPropagation();
|
|
1024
|
+
const activePointerId = activePointerIdRef.current;
|
|
1025
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1026
|
+
(_a = controllerRef.current) == null ? void 0 : _a.move(e.clientX, e.clientY, e.buttons);
|
|
1027
|
+
if (activePointerIdRef.current === e.pointerId && e.buttons === 0) {
|
|
1028
|
+
endPointerSession(e.pointerId);
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
[endPointerSession]
|
|
1032
|
+
);
|
|
1033
|
+
const handleEndLike = useCallback(
|
|
1034
|
+
(e) => {
|
|
1035
|
+
var _a;
|
|
1036
|
+
e.preventDefault();
|
|
1037
|
+
e.stopPropagation();
|
|
1038
|
+
const activePointerId = activePointerIdRef.current;
|
|
1039
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1040
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
1041
|
+
endPointerSession(e.pointerId);
|
|
1042
|
+
},
|
|
1043
|
+
[endPointerSession]
|
|
1044
|
+
);
|
|
1045
|
+
const handleLostPointerCapture = useCallback(
|
|
1046
|
+
(e) => {
|
|
1047
|
+
var _a;
|
|
1048
|
+
const activePointerId = activePointerIdRef.current;
|
|
1049
|
+
if (activePointerId === null || e.pointerId !== activePointerId) return;
|
|
1050
|
+
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
1051
|
+
endPointerSession(e.pointerId);
|
|
1052
|
+
},
|
|
1053
|
+
[endPointerSession]
|
|
596
1054
|
);
|
|
597
|
-
const handleMove = useCallback((e) => {
|
|
598
|
-
var _a;
|
|
599
|
-
e.preventDefault();
|
|
600
|
-
e.stopPropagation();
|
|
601
|
-
(_a = controllerRef.current) == null ? void 0 : _a.move(e.clientX, e.clientY);
|
|
602
|
-
}, []);
|
|
603
|
-
const handleEnd = useCallback((e) => {
|
|
604
|
-
var _a, _b, _c;
|
|
605
|
-
e.preventDefault();
|
|
606
|
-
e.stopPropagation();
|
|
607
|
-
(_a = controllerRef.current) == null ? void 0 : _a.end();
|
|
608
|
-
(_c = (_b = e.currentTarget).releasePointerCapture) == null ? void 0 : _c.call(_b, e.pointerId);
|
|
609
|
-
}, []);
|
|
610
1055
|
const createResizeHandler = useCallback(
|
|
611
1056
|
(handle) => ({
|
|
612
1057
|
onPointerDown: (e) => {
|
|
@@ -615,13 +1060,14 @@ function useDragResize(options) {
|
|
|
615
1060
|
e.preventDefault();
|
|
616
1061
|
e.stopPropagation();
|
|
617
1062
|
(_a = controllerRef.current) == null ? void 0 : _a.startResize(handle, e.clientX, e.clientY);
|
|
618
|
-
|
|
1063
|
+
startPointerSession(e);
|
|
619
1064
|
},
|
|
620
1065
|
onPointerMove: handleMove,
|
|
621
|
-
onPointerUp:
|
|
622
|
-
onPointerCancel:
|
|
1066
|
+
onPointerUp: handleEndLike,
|
|
1067
|
+
onPointerCancel: handleEndLike,
|
|
1068
|
+
onLostPointerCapture: handleLostPointerCapture
|
|
623
1069
|
}),
|
|
624
|
-
[enabled, handleMove,
|
|
1070
|
+
[enabled, handleMove, handleEndLike, handleLostPointerCapture, startPointerSession]
|
|
625
1071
|
);
|
|
626
1072
|
const createVertexHandler = useCallback(
|
|
627
1073
|
(vertexIndex) => ({
|
|
@@ -631,41 +1077,74 @@ function useDragResize(options) {
|
|
|
631
1077
|
e.preventDefault();
|
|
632
1078
|
e.stopPropagation();
|
|
633
1079
|
(_a = controllerRef.current) == null ? void 0 : _a.startVertexEdit(vertexIndex, e.clientX, e.clientY);
|
|
634
|
-
|
|
1080
|
+
startPointerSession(e);
|
|
635
1081
|
},
|
|
636
1082
|
onPointerMove: handleMove,
|
|
637
|
-
onPointerUp:
|
|
638
|
-
onPointerCancel:
|
|
1083
|
+
onPointerUp: handleEndLike,
|
|
1084
|
+
onPointerCancel: handleEndLike,
|
|
1085
|
+
onLostPointerCapture: handleLostPointerCapture
|
|
639
1086
|
}),
|
|
640
|
-
[enabled, handleMove,
|
|
1087
|
+
[enabled, handleMove, handleEndLike, handleLostPointerCapture, startPointerSession]
|
|
1088
|
+
);
|
|
1089
|
+
const createRotationHandler = useCallback(
|
|
1090
|
+
(initialRotation = 0, orbitRadiusPx) => ({
|
|
1091
|
+
onPointerDown: (e) => {
|
|
1092
|
+
var _a;
|
|
1093
|
+
if (!enabled) return;
|
|
1094
|
+
e.preventDefault();
|
|
1095
|
+
e.stopPropagation();
|
|
1096
|
+
const handleRect = e.currentTarget.getBoundingClientRect();
|
|
1097
|
+
const handleCenterX = handleRect.left + handleRect.width / 2;
|
|
1098
|
+
const handleCenterY = handleRect.top + handleRect.height / 2;
|
|
1099
|
+
(_a = controllerRef.current) == null ? void 0 : _a.startRotation(
|
|
1100
|
+
handleCenterX,
|
|
1101
|
+
handleCenterY,
|
|
1102
|
+
initialRotation,
|
|
1103
|
+
orbitRadiusPx
|
|
1104
|
+
);
|
|
1105
|
+
startPointerSession(e);
|
|
1106
|
+
},
|
|
1107
|
+
onPointerMove: handleMove,
|
|
1108
|
+
onPointerUp: handleEndLike,
|
|
1109
|
+
onPointerCancel: handleEndLike,
|
|
1110
|
+
onLostPointerCapture: handleLostPointerCapture
|
|
1111
|
+
}),
|
|
1112
|
+
[enabled, handleMove, handleEndLike, handleLostPointerCapture, startPointerSession]
|
|
641
1113
|
);
|
|
642
1114
|
return {
|
|
643
1115
|
dragProps: enabled ? {
|
|
644
1116
|
onPointerDown: handleDragStart,
|
|
645
1117
|
onPointerMove: handleMove,
|
|
646
|
-
onPointerUp:
|
|
647
|
-
onPointerCancel:
|
|
1118
|
+
onPointerUp: handleEndLike,
|
|
1119
|
+
onPointerCancel: handleEndLike,
|
|
1120
|
+
onLostPointerCapture: handleLostPointerCapture
|
|
648
1121
|
} : {},
|
|
649
1122
|
createResizeProps: createResizeHandler,
|
|
650
|
-
createVertexProps: createVertexHandler
|
|
1123
|
+
createVertexProps: createVertexHandler,
|
|
1124
|
+
createRotationProps: createRotationHandler
|
|
651
1125
|
};
|
|
652
1126
|
}
|
|
653
1127
|
function useInteractionHandles(opts) {
|
|
1128
|
+
var _a, _b, _c, _d, _e, _f;
|
|
654
1129
|
const {
|
|
655
1130
|
controller,
|
|
656
1131
|
resizeUI,
|
|
657
1132
|
vertexUI,
|
|
1133
|
+
rotationUI,
|
|
658
1134
|
includeVertices = false,
|
|
1135
|
+
includeRotation = false,
|
|
1136
|
+
currentRotation = 0,
|
|
659
1137
|
handleAttrs,
|
|
660
|
-
vertexAttrs
|
|
1138
|
+
vertexAttrs,
|
|
1139
|
+
rotationAttrs
|
|
661
1140
|
} = opts;
|
|
662
|
-
const { dragProps, createResizeProps, createVertexProps } = useDragResize(controller);
|
|
1141
|
+
const { dragProps, createResizeProps, createVertexProps, createRotationProps } = useDragResize(controller);
|
|
663
1142
|
const resize = useMemo(() => {
|
|
664
1143
|
const desc = describeResizeFromConfig(controller, resizeUI);
|
|
665
1144
|
return desc.map((d) => {
|
|
666
|
-
var
|
|
1145
|
+
var _a2;
|
|
667
1146
|
return {
|
|
668
|
-
key: (
|
|
1147
|
+
key: (_a2 = d.attrs) == null ? void 0 : _a2["data-epdf-handle"],
|
|
669
1148
|
style: d.style,
|
|
670
1149
|
...createResizeProps(d.handle),
|
|
671
1150
|
...d.attrs ?? {},
|
|
@@ -679,6 +1158,7 @@ function useInteractionHandles(opts) {
|
|
|
679
1158
|
controller.element.size.height,
|
|
680
1159
|
controller.scale,
|
|
681
1160
|
controller.pageRotation,
|
|
1161
|
+
controller.annotationRotation,
|
|
682
1162
|
controller.maintainAspectRatio,
|
|
683
1163
|
resizeUI == null ? void 0 : resizeUI.handleSize,
|
|
684
1164
|
resizeUI == null ? void 0 : resizeUI.spacing,
|
|
@@ -713,7 +1193,44 @@ function useInteractionHandles(opts) {
|
|
|
713
1193
|
createVertexProps,
|
|
714
1194
|
vertexAttrs
|
|
715
1195
|
]);
|
|
716
|
-
|
|
1196
|
+
const rotation = useMemo(() => {
|
|
1197
|
+
if (!includeRotation) return null;
|
|
1198
|
+
const desc = describeRotationFromConfig(controller, rotationUI, currentRotation);
|
|
1199
|
+
return {
|
|
1200
|
+
handle: {
|
|
1201
|
+
style: desc.handleStyle,
|
|
1202
|
+
...createRotationProps(currentRotation, desc.radius),
|
|
1203
|
+
...desc.attrs ?? {},
|
|
1204
|
+
...(rotationAttrs == null ? void 0 : rotationAttrs()) ?? {}
|
|
1205
|
+
},
|
|
1206
|
+
connector: {
|
|
1207
|
+
style: desc.connectorStyle,
|
|
1208
|
+
"data-epdf-rotation-connector": true
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
}, [
|
|
1212
|
+
includeRotation,
|
|
1213
|
+
controller.element.origin.x,
|
|
1214
|
+
controller.element.origin.y,
|
|
1215
|
+
controller.element.size.width,
|
|
1216
|
+
controller.element.size.height,
|
|
1217
|
+
(_a = controller.rotationCenter) == null ? void 0 : _a.x,
|
|
1218
|
+
(_b = controller.rotationCenter) == null ? void 0 : _b.y,
|
|
1219
|
+
(_c = controller.rotationElement) == null ? void 0 : _c.origin.x,
|
|
1220
|
+
(_d = controller.rotationElement) == null ? void 0 : _d.origin.y,
|
|
1221
|
+
(_e = controller.rotationElement) == null ? void 0 : _e.size.width,
|
|
1222
|
+
(_f = controller.rotationElement) == null ? void 0 : _f.size.height,
|
|
1223
|
+
controller.scale,
|
|
1224
|
+
currentRotation,
|
|
1225
|
+
rotationUI == null ? void 0 : rotationUI.handleSize,
|
|
1226
|
+
rotationUI == null ? void 0 : rotationUI.margin,
|
|
1227
|
+
rotationUI == null ? void 0 : rotationUI.zIndex,
|
|
1228
|
+
rotationUI == null ? void 0 : rotationUI.showConnector,
|
|
1229
|
+
rotationUI == null ? void 0 : rotationUI.connectorWidth,
|
|
1230
|
+
createRotationProps,
|
|
1231
|
+
rotationAttrs
|
|
1232
|
+
]);
|
|
1233
|
+
return { dragProps, resize, vertices, rotation };
|
|
717
1234
|
}
|
|
718
1235
|
function useDoublePressProps(onDouble, { delay = 300, tolerancePx = 18 } = {}) {
|
|
719
1236
|
const last = useRef({ t: 0, x: 0, y: 0 });
|