@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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Position, Rect } from '@embedpdf/models';
|
|
2
|
+
import { ResizeHandle } from './drag-resize-controller';
|
|
3
|
+
/** Anchor describes which edges stay fixed when resizing. */
|
|
4
|
+
export type Anchor = {
|
|
5
|
+
x: 'left' | 'right' | 'center';
|
|
6
|
+
y: 'top' | 'bottom' | 'center';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Derive anchor from handle.
|
|
10
|
+
* - 'e' means we're dragging east → left edge is anchored
|
|
11
|
+
* - 'nw' means we're dragging north-west → bottom-right corner is anchored
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAnchor(handle: ResizeHandle): Anchor;
|
|
14
|
+
/** Get the anchor point (the visually fixed point) in page space for a given rect and anchor. */
|
|
15
|
+
export declare function getAnchorPoint(rect: Rect, anchor: Anchor): Position;
|
|
16
|
+
export interface ResizeConstraints {
|
|
17
|
+
minWidth?: number;
|
|
18
|
+
minHeight?: number;
|
|
19
|
+
maxWidth?: number;
|
|
20
|
+
maxHeight?: number;
|
|
21
|
+
boundingBox?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyResizeDelta(startRect: Rect, delta: Position, anchor: Anchor): Rect;
|
|
30
|
+
/**
|
|
31
|
+
* Enforce aspect ratio while respecting the anchor.
|
|
32
|
+
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
33
|
+
* For corner handles, the anchor corner stays fixed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function enforceAspectRatio(rect: Rect, startRect: Rect, anchor: Anchor, aspectRatio: number): Rect;
|
|
36
|
+
/**
|
|
37
|
+
* Clamp rect to bounding box while respecting anchor.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clampToBounds(rect: Rect, startRect: Rect, anchor: Anchor, bbox: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
} | undefined, maintainAspectRatio: boolean): Rect;
|
|
43
|
+
/**
|
|
44
|
+
* Reposition rect from current size so the start-gesture anchor remains fixed.
|
|
45
|
+
* This prevents translation drift when constraints clamp width/height.
|
|
46
|
+
*/
|
|
47
|
+
export declare function reanchorRect(rect: Rect, startRect: Rect, anchor: Anchor): Rect;
|
|
48
|
+
/**
|
|
49
|
+
* Apply min/max constraints. Also used by the drag pipeline for position clamping.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyConstraints(position: Rect, constraints: ResizeConstraints | undefined, maintainAspectRatio: boolean, skipBoundingClamp?: boolean): Rect;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a rect, when rotated, fits within the given page bounds.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isRectWithinRotatedBounds(rect: Rect, angleDegrees: number, bbox: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}): boolean;
|
|
59
|
+
export interface ResizeConfig {
|
|
60
|
+
startRect: Rect;
|
|
61
|
+
maintainAspectRatio?: boolean;
|
|
62
|
+
annotationRotation?: number;
|
|
63
|
+
constraints?: ResizeConstraints;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculate the new rect after a resize operation.
|
|
67
|
+
*
|
|
68
|
+
* For non-rotated annotations, runs the pipeline once with local bound clamping.
|
|
69
|
+
* For rotated annotations, uses a binary search to find the largest delta that
|
|
70
|
+
* keeps the visual AABB within page bounds.
|
|
71
|
+
*/
|
|
72
|
+
export declare function computeResizedRect(delta: Position, handle: ResizeHandle, config: ResizeConfig): Rect;
|
|
@@ -13,10 +13,43 @@ export interface VertexUI {
|
|
|
13
13
|
vertexSize?: number;
|
|
14
14
|
zIndex?: number;
|
|
15
15
|
}
|
|
16
|
+
export interface RotationUI {
|
|
17
|
+
/** Handle size in px (default 32) */
|
|
18
|
+
handleSize?: number;
|
|
19
|
+
/** Screen-pixel gap between bounding box edge and handle center (default 30) */
|
|
20
|
+
margin?: number;
|
|
21
|
+
/** z-index of the rotation handle (default 5) */
|
|
22
|
+
zIndex?: number;
|
|
23
|
+
/** Whether to show the connector line from center to handle (default true) */
|
|
24
|
+
showConnector?: boolean;
|
|
25
|
+
/** Connector line width in px (default 1) */
|
|
26
|
+
connectorWidth?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Screen-pixel gap between the rect edge and the rotation handle center (default orbit margin). */
|
|
29
|
+
export declare const ROTATION_HANDLE_MARGIN = 35;
|
|
16
30
|
export interface HandleDescriptor {
|
|
17
31
|
handle: ResizeHandle;
|
|
18
32
|
style: Record<string, number | string>;
|
|
19
33
|
attrs?: Record<string, any>;
|
|
20
34
|
}
|
|
35
|
+
export interface RotationHandleDescriptor {
|
|
36
|
+
/** Style for the rotation handle itself */
|
|
37
|
+
handleStyle: Record<string, number | string>;
|
|
38
|
+
/** Style for the connector line (if shown) */
|
|
39
|
+
connectorStyle: Record<string, number | string>;
|
|
40
|
+
/** Orbit radius in screen pixels used to position the handle. */
|
|
41
|
+
radius: number;
|
|
42
|
+
/** Attributes for the handle element */
|
|
43
|
+
attrs?: Record<string, any>;
|
|
44
|
+
}
|
|
21
45
|
export declare function describeResizeFromConfig(cfg: DragResizeConfig, ui?: ResizeUI): HandleDescriptor[];
|
|
22
46
|
export declare function describeVerticesFromConfig(cfg: DragResizeConfig, ui?: VertexUI, liveVertices?: Position[]): HandleDescriptor[];
|
|
47
|
+
/**
|
|
48
|
+
* Describe the rotation handle position and style.
|
|
49
|
+
* The rotation handle orbits around the center of the bounding box based on the current angle.
|
|
50
|
+
*
|
|
51
|
+
* @param cfg - The drag/resize config containing the element rect and scale
|
|
52
|
+
* @param ui - UI customization options
|
|
53
|
+
* @param currentAngle - The current rotation angle in degrees (0 = top, clockwise positive)
|
|
54
|
+
*/
|
|
55
|
+
export declare function describeRotationFromConfig(cfg: DragResizeConfig, ui?: RotationUI, currentAngle?: number): RotationHandleDescriptor;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Position, Rect } from '@embedpdf/models';
|
|
2
2
|
export interface DragResizeConfig {
|
|
3
3
|
element: Rect;
|
|
4
|
+
/**
|
|
5
|
+
* Optional world-space pivot to use for rotation interactions.
|
|
6
|
+
* Defaults to the center of `element`.
|
|
7
|
+
*/
|
|
8
|
+
rotationCenter?: Position;
|
|
9
|
+
/**
|
|
10
|
+
* Optional rect used for rotation-handle orbit layout (typically the visible AABB container).
|
|
11
|
+
* Defaults to `element`.
|
|
12
|
+
*/
|
|
13
|
+
rotationElement?: Rect;
|
|
4
14
|
vertices?: Position[];
|
|
5
15
|
constraints?: {
|
|
6
16
|
minWidth?: number;
|
|
@@ -14,20 +24,37 @@ export interface DragResizeConfig {
|
|
|
14
24
|
};
|
|
15
25
|
maintainAspectRatio?: boolean;
|
|
16
26
|
pageRotation?: number;
|
|
27
|
+
/** Rotation of the annotation itself in degrees (used to project mouse deltas into local space for resize/vertex-edit) */
|
|
28
|
+
annotationRotation?: number;
|
|
17
29
|
scale?: number;
|
|
30
|
+
rotationSnapAngles?: number[];
|
|
31
|
+
rotationSnapThreshold?: number;
|
|
18
32
|
}
|
|
19
|
-
export type InteractionState = 'idle' | 'dragging' | 'resizing' | 'vertex-editing';
|
|
33
|
+
export type InteractionState = 'idle' | 'dragging' | 'resizing' | 'vertex-editing' | 'rotating';
|
|
20
34
|
export type ResizeHandle = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 'e' | 's' | 'w';
|
|
21
35
|
export interface TransformData {
|
|
22
|
-
type: 'move' | 'resize' | 'vertex-edit';
|
|
36
|
+
type: 'move' | 'resize' | 'vertex-edit' | 'rotate';
|
|
23
37
|
changes: {
|
|
24
38
|
rect?: Rect;
|
|
25
39
|
vertices?: Position[];
|
|
40
|
+
rotation?: number;
|
|
26
41
|
};
|
|
27
42
|
metadata?: {
|
|
28
43
|
handle?: ResizeHandle;
|
|
29
44
|
vertexIndex?: number;
|
|
30
45
|
maintainAspectRatio?: boolean;
|
|
46
|
+
/** The rotation angle in degrees */
|
|
47
|
+
rotationAngle?: number;
|
|
48
|
+
/** The center point used for rotation */
|
|
49
|
+
rotationCenter?: Position;
|
|
50
|
+
rotationDelta?: number;
|
|
51
|
+
isSnapped?: boolean;
|
|
52
|
+
snappedAngle?: number;
|
|
53
|
+
/** Screen-space cursor position during the gesture */
|
|
54
|
+
cursorPosition?: {
|
|
55
|
+
clientX: number;
|
|
56
|
+
clientY: number;
|
|
57
|
+
};
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
export interface InteractionEvent {
|
|
@@ -35,7 +62,7 @@ export interface InteractionEvent {
|
|
|
35
62
|
transformData?: TransformData;
|
|
36
63
|
}
|
|
37
64
|
/**
|
|
38
|
-
* Pure geometric controller that manages drag/resize/vertex-edit logic.
|
|
65
|
+
* Pure geometric controller that manages drag/resize/vertex-edit/rotate logic.
|
|
39
66
|
*/
|
|
40
67
|
export declare class DragResizeController {
|
|
41
68
|
private config;
|
|
@@ -43,44 +70,43 @@ export declare class DragResizeController {
|
|
|
43
70
|
private state;
|
|
44
71
|
private startPoint;
|
|
45
72
|
private startElement;
|
|
73
|
+
private startRotationElement;
|
|
74
|
+
private gestureRotationCenter;
|
|
46
75
|
private activeHandle;
|
|
47
76
|
private currentPosition;
|
|
48
77
|
private activeVertexIndex;
|
|
49
78
|
private startVertices;
|
|
50
79
|
private currentVertices;
|
|
80
|
+
private rotationCenter;
|
|
81
|
+
private centerScreen;
|
|
82
|
+
private initialRotation;
|
|
83
|
+
private lastComputedRotation;
|
|
84
|
+
private rotationDelta;
|
|
85
|
+
private rotationSnappedAngle;
|
|
51
86
|
constructor(config: DragResizeConfig, onUpdate: (event: InteractionEvent) => void);
|
|
52
87
|
updateConfig(config: Partial<DragResizeConfig>): void;
|
|
53
88
|
startDrag(clientX: number, clientY: number): void;
|
|
54
89
|
startResize(handle: ResizeHandle, clientX: number, clientY: number): void;
|
|
55
90
|
startVertexEdit(vertexIndex: number, clientX: number, clientY: number): void;
|
|
56
|
-
|
|
91
|
+
startRotation(clientX: number, clientY: number, initialRotation?: number, orbitRadiusPx?: number): void;
|
|
92
|
+
move(clientX: number, clientY: number, buttons?: number): void;
|
|
57
93
|
end(): void;
|
|
58
94
|
cancel(): void;
|
|
59
95
|
private reset;
|
|
60
|
-
private getCurrentPosition;
|
|
61
96
|
private calculateDelta;
|
|
62
97
|
private transformDelta;
|
|
98
|
+
/**
|
|
99
|
+
* Calculate delta projected into the annotation's local (unrotated) coordinate space.
|
|
100
|
+
* Used for resize and vertex-edit where mouse movement must be mapped to the
|
|
101
|
+
* annotation's own axes, accounting for its rotation.
|
|
102
|
+
*/
|
|
103
|
+
private calculateLocalDelta;
|
|
63
104
|
private clampPoint;
|
|
64
105
|
private calculateVertexPosition;
|
|
65
106
|
private calculateDragPosition;
|
|
66
107
|
/**
|
|
67
|
-
* Calculate the
|
|
68
|
-
* Pipeline: applyDelta → enforceAspectRatio → clampToBounds → applyConstraints
|
|
69
|
-
*/
|
|
70
|
-
private calculateResizePosition;
|
|
71
|
-
/**
|
|
72
|
-
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
73
|
-
*/
|
|
74
|
-
private applyResizeDelta;
|
|
75
|
-
/**
|
|
76
|
-
* Enforce aspect ratio while respecting the anchor.
|
|
77
|
-
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
78
|
-
* For corner handles, the anchor corner stays fixed.
|
|
79
|
-
*/
|
|
80
|
-
private enforceAspectRatio;
|
|
81
|
-
/**
|
|
82
|
-
* Clamp rect to bounding box while respecting anchor and aspect ratio.
|
|
108
|
+
* Calculate the angle from the center to a point in screen coordinates.
|
|
83
109
|
*/
|
|
84
|
-
private
|
|
85
|
-
private
|
|
110
|
+
private calculateAngleFromMouse;
|
|
111
|
+
private applyRotationSnapping;
|
|
86
112
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Position, Rect } from '@embedpdf/models';
|
|
2
|
+
import { ResizeHandle } from './drag-resize-controller';
|
|
3
|
+
/** Anchor describes which edges stay fixed when resizing. */
|
|
4
|
+
export type Anchor = {
|
|
5
|
+
x: 'left' | 'right' | 'center';
|
|
6
|
+
y: 'top' | 'bottom' | 'center';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Derive anchor from handle.
|
|
10
|
+
* - 'e' means we're dragging east → left edge is anchored
|
|
11
|
+
* - 'nw' means we're dragging north-west → bottom-right corner is anchored
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAnchor(handle: ResizeHandle): Anchor;
|
|
14
|
+
/** Get the anchor point (the visually fixed point) in page space for a given rect and anchor. */
|
|
15
|
+
export declare function getAnchorPoint(rect: Rect, anchor: Anchor): Position;
|
|
16
|
+
export interface ResizeConstraints {
|
|
17
|
+
minWidth?: number;
|
|
18
|
+
minHeight?: number;
|
|
19
|
+
maxWidth?: number;
|
|
20
|
+
maxHeight?: number;
|
|
21
|
+
boundingBox?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyResizeDelta(startRect: Rect, delta: Position, anchor: Anchor): Rect;
|
|
30
|
+
/**
|
|
31
|
+
* Enforce aspect ratio while respecting the anchor.
|
|
32
|
+
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
33
|
+
* For corner handles, the anchor corner stays fixed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function enforceAspectRatio(rect: Rect, startRect: Rect, anchor: Anchor, aspectRatio: number): Rect;
|
|
36
|
+
/**
|
|
37
|
+
* Clamp rect to bounding box while respecting anchor.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clampToBounds(rect: Rect, startRect: Rect, anchor: Anchor, bbox: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
} | undefined, maintainAspectRatio: boolean): Rect;
|
|
43
|
+
/**
|
|
44
|
+
* Reposition rect from current size so the start-gesture anchor remains fixed.
|
|
45
|
+
* This prevents translation drift when constraints clamp width/height.
|
|
46
|
+
*/
|
|
47
|
+
export declare function reanchorRect(rect: Rect, startRect: Rect, anchor: Anchor): Rect;
|
|
48
|
+
/**
|
|
49
|
+
* Apply min/max constraints. Also used by the drag pipeline for position clamping.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyConstraints(position: Rect, constraints: ResizeConstraints | undefined, maintainAspectRatio: boolean, skipBoundingClamp?: boolean): Rect;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a rect, when rotated, fits within the given page bounds.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isRectWithinRotatedBounds(rect: Rect, angleDegrees: number, bbox: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}): boolean;
|
|
59
|
+
export interface ResizeConfig {
|
|
60
|
+
startRect: Rect;
|
|
61
|
+
maintainAspectRatio?: boolean;
|
|
62
|
+
annotationRotation?: number;
|
|
63
|
+
constraints?: ResizeConstraints;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculate the new rect after a resize operation.
|
|
67
|
+
*
|
|
68
|
+
* For non-rotated annotations, runs the pipeline once with local bound clamping.
|
|
69
|
+
* For rotated annotations, uses a binary search to find the largest delta that
|
|
70
|
+
* keeps the visual AABB within page bounds.
|
|
71
|
+
*/
|
|
72
|
+
export declare function computeResizedRect(delta: Position, handle: ResizeHandle, config: ResizeConfig): Rect;
|
|
@@ -13,10 +13,43 @@ export interface VertexUI {
|
|
|
13
13
|
vertexSize?: number;
|
|
14
14
|
zIndex?: number;
|
|
15
15
|
}
|
|
16
|
+
export interface RotationUI {
|
|
17
|
+
/** Handle size in px (default 32) */
|
|
18
|
+
handleSize?: number;
|
|
19
|
+
/** Screen-pixel gap between bounding box edge and handle center (default 30) */
|
|
20
|
+
margin?: number;
|
|
21
|
+
/** z-index of the rotation handle (default 5) */
|
|
22
|
+
zIndex?: number;
|
|
23
|
+
/** Whether to show the connector line from center to handle (default true) */
|
|
24
|
+
showConnector?: boolean;
|
|
25
|
+
/** Connector line width in px (default 1) */
|
|
26
|
+
connectorWidth?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Screen-pixel gap between the rect edge and the rotation handle center (default orbit margin). */
|
|
29
|
+
export declare const ROTATION_HANDLE_MARGIN = 35;
|
|
16
30
|
export interface HandleDescriptor {
|
|
17
31
|
handle: ResizeHandle;
|
|
18
32
|
style: Record<string, number | string>;
|
|
19
33
|
attrs?: Record<string, any>;
|
|
20
34
|
}
|
|
35
|
+
export interface RotationHandleDescriptor {
|
|
36
|
+
/** Style for the rotation handle itself */
|
|
37
|
+
handleStyle: Record<string, number | string>;
|
|
38
|
+
/** Style for the connector line (if shown) */
|
|
39
|
+
connectorStyle: Record<string, number | string>;
|
|
40
|
+
/** Orbit radius in screen pixels used to position the handle. */
|
|
41
|
+
radius: number;
|
|
42
|
+
/** Attributes for the handle element */
|
|
43
|
+
attrs?: Record<string, any>;
|
|
44
|
+
}
|
|
21
45
|
export declare function describeResizeFromConfig(cfg: DragResizeConfig, ui?: ResizeUI): HandleDescriptor[];
|
|
22
46
|
export declare function describeVerticesFromConfig(cfg: DragResizeConfig, ui?: VertexUI, liveVertices?: Position[]): HandleDescriptor[];
|
|
47
|
+
/**
|
|
48
|
+
* Describe the rotation handle position and style.
|
|
49
|
+
* The rotation handle orbits around the center of the bounding box based on the current angle.
|
|
50
|
+
*
|
|
51
|
+
* @param cfg - The drag/resize config containing the element rect and scale
|
|
52
|
+
* @param ui - UI customization options
|
|
53
|
+
* @param currentAngle - The current rotation angle in degrees (0 = top, clockwise positive)
|
|
54
|
+
*/
|
|
55
|
+
export declare function describeRotationFromConfig(cfg: DragResizeConfig, ui?: RotationUI, currentAngle?: number): RotationHandleDescriptor;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Position, Rect } from '@embedpdf/models';
|
|
2
2
|
export interface DragResizeConfig {
|
|
3
3
|
element: Rect;
|
|
4
|
+
/**
|
|
5
|
+
* Optional world-space pivot to use for rotation interactions.
|
|
6
|
+
* Defaults to the center of `element`.
|
|
7
|
+
*/
|
|
8
|
+
rotationCenter?: Position;
|
|
9
|
+
/**
|
|
10
|
+
* Optional rect used for rotation-handle orbit layout (typically the visible AABB container).
|
|
11
|
+
* Defaults to `element`.
|
|
12
|
+
*/
|
|
13
|
+
rotationElement?: Rect;
|
|
4
14
|
vertices?: Position[];
|
|
5
15
|
constraints?: {
|
|
6
16
|
minWidth?: number;
|
|
@@ -14,20 +24,37 @@ export interface DragResizeConfig {
|
|
|
14
24
|
};
|
|
15
25
|
maintainAspectRatio?: boolean;
|
|
16
26
|
pageRotation?: number;
|
|
27
|
+
/** Rotation of the annotation itself in degrees (used to project mouse deltas into local space for resize/vertex-edit) */
|
|
28
|
+
annotationRotation?: number;
|
|
17
29
|
scale?: number;
|
|
30
|
+
rotationSnapAngles?: number[];
|
|
31
|
+
rotationSnapThreshold?: number;
|
|
18
32
|
}
|
|
19
|
-
export type InteractionState = 'idle' | 'dragging' | 'resizing' | 'vertex-editing';
|
|
33
|
+
export type InteractionState = 'idle' | 'dragging' | 'resizing' | 'vertex-editing' | 'rotating';
|
|
20
34
|
export type ResizeHandle = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 'e' | 's' | 'w';
|
|
21
35
|
export interface TransformData {
|
|
22
|
-
type: 'move' | 'resize' | 'vertex-edit';
|
|
36
|
+
type: 'move' | 'resize' | 'vertex-edit' | 'rotate';
|
|
23
37
|
changes: {
|
|
24
38
|
rect?: Rect;
|
|
25
39
|
vertices?: Position[];
|
|
40
|
+
rotation?: number;
|
|
26
41
|
};
|
|
27
42
|
metadata?: {
|
|
28
43
|
handle?: ResizeHandle;
|
|
29
44
|
vertexIndex?: number;
|
|
30
45
|
maintainAspectRatio?: boolean;
|
|
46
|
+
/** The rotation angle in degrees */
|
|
47
|
+
rotationAngle?: number;
|
|
48
|
+
/** The center point used for rotation */
|
|
49
|
+
rotationCenter?: Position;
|
|
50
|
+
rotationDelta?: number;
|
|
51
|
+
isSnapped?: boolean;
|
|
52
|
+
snappedAngle?: number;
|
|
53
|
+
/** Screen-space cursor position during the gesture */
|
|
54
|
+
cursorPosition?: {
|
|
55
|
+
clientX: number;
|
|
56
|
+
clientY: number;
|
|
57
|
+
};
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
export interface InteractionEvent {
|
|
@@ -35,7 +62,7 @@ export interface InteractionEvent {
|
|
|
35
62
|
transformData?: TransformData;
|
|
36
63
|
}
|
|
37
64
|
/**
|
|
38
|
-
* Pure geometric controller that manages drag/resize/vertex-edit logic.
|
|
65
|
+
* Pure geometric controller that manages drag/resize/vertex-edit/rotate logic.
|
|
39
66
|
*/
|
|
40
67
|
export declare class DragResizeController {
|
|
41
68
|
private config;
|
|
@@ -43,44 +70,43 @@ export declare class DragResizeController {
|
|
|
43
70
|
private state;
|
|
44
71
|
private startPoint;
|
|
45
72
|
private startElement;
|
|
73
|
+
private startRotationElement;
|
|
74
|
+
private gestureRotationCenter;
|
|
46
75
|
private activeHandle;
|
|
47
76
|
private currentPosition;
|
|
48
77
|
private activeVertexIndex;
|
|
49
78
|
private startVertices;
|
|
50
79
|
private currentVertices;
|
|
80
|
+
private rotationCenter;
|
|
81
|
+
private centerScreen;
|
|
82
|
+
private initialRotation;
|
|
83
|
+
private lastComputedRotation;
|
|
84
|
+
private rotationDelta;
|
|
85
|
+
private rotationSnappedAngle;
|
|
51
86
|
constructor(config: DragResizeConfig, onUpdate: (event: InteractionEvent) => void);
|
|
52
87
|
updateConfig(config: Partial<DragResizeConfig>): void;
|
|
53
88
|
startDrag(clientX: number, clientY: number): void;
|
|
54
89
|
startResize(handle: ResizeHandle, clientX: number, clientY: number): void;
|
|
55
90
|
startVertexEdit(vertexIndex: number, clientX: number, clientY: number): void;
|
|
56
|
-
|
|
91
|
+
startRotation(clientX: number, clientY: number, initialRotation?: number, orbitRadiusPx?: number): void;
|
|
92
|
+
move(clientX: number, clientY: number, buttons?: number): void;
|
|
57
93
|
end(): void;
|
|
58
94
|
cancel(): void;
|
|
59
95
|
private reset;
|
|
60
|
-
private getCurrentPosition;
|
|
61
96
|
private calculateDelta;
|
|
62
97
|
private transformDelta;
|
|
98
|
+
/**
|
|
99
|
+
* Calculate delta projected into the annotation's local (unrotated) coordinate space.
|
|
100
|
+
* Used for resize and vertex-edit where mouse movement must be mapped to the
|
|
101
|
+
* annotation's own axes, accounting for its rotation.
|
|
102
|
+
*/
|
|
103
|
+
private calculateLocalDelta;
|
|
63
104
|
private clampPoint;
|
|
64
105
|
private calculateVertexPosition;
|
|
65
106
|
private calculateDragPosition;
|
|
66
107
|
/**
|
|
67
|
-
* Calculate the
|
|
68
|
-
* Pipeline: applyDelta → enforceAspectRatio → clampToBounds → applyConstraints
|
|
69
|
-
*/
|
|
70
|
-
private calculateResizePosition;
|
|
71
|
-
/**
|
|
72
|
-
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
73
|
-
*/
|
|
74
|
-
private applyResizeDelta;
|
|
75
|
-
/**
|
|
76
|
-
* Enforce aspect ratio while respecting the anchor.
|
|
77
|
-
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
78
|
-
* For corner handles, the anchor corner stays fixed.
|
|
79
|
-
*/
|
|
80
|
-
private enforceAspectRatio;
|
|
81
|
-
/**
|
|
82
|
-
* Clamp rect to bounding box while respecting anchor and aspect ratio.
|
|
108
|
+
* Calculate the angle from the center to a point in screen coordinates.
|
|
83
109
|
*/
|
|
84
|
-
private
|
|
85
|
-
private
|
|
110
|
+
private calculateAngleFromMouse;
|
|
111
|
+
private applyRotationSnapping;
|
|
86
112
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Position, Rect } from '@embedpdf/models';
|
|
2
|
+
import { ResizeHandle } from './drag-resize-controller';
|
|
3
|
+
/** Anchor describes which edges stay fixed when resizing. */
|
|
4
|
+
export type Anchor = {
|
|
5
|
+
x: 'left' | 'right' | 'center';
|
|
6
|
+
y: 'top' | 'bottom' | 'center';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Derive anchor from handle.
|
|
10
|
+
* - 'e' means we're dragging east → left edge is anchored
|
|
11
|
+
* - 'nw' means we're dragging north-west → bottom-right corner is anchored
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAnchor(handle: ResizeHandle): Anchor;
|
|
14
|
+
/** Get the anchor point (the visually fixed point) in page space for a given rect and anchor. */
|
|
15
|
+
export declare function getAnchorPoint(rect: Rect, anchor: Anchor): Position;
|
|
16
|
+
export interface ResizeConstraints {
|
|
17
|
+
minWidth?: number;
|
|
18
|
+
minHeight?: number;
|
|
19
|
+
maxWidth?: number;
|
|
20
|
+
maxHeight?: number;
|
|
21
|
+
boundingBox?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Apply the mouse delta to produce a raw (unconstrained) resized rect.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyResizeDelta(startRect: Rect, delta: Position, anchor: Anchor): Rect;
|
|
30
|
+
/**
|
|
31
|
+
* Enforce aspect ratio while respecting the anchor.
|
|
32
|
+
* For edge handles (center anchor on one axis), the rect expands symmetrically on that axis.
|
|
33
|
+
* For corner handles, the anchor corner stays fixed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function enforceAspectRatio(rect: Rect, startRect: Rect, anchor: Anchor, aspectRatio: number): Rect;
|
|
36
|
+
/**
|
|
37
|
+
* Clamp rect to bounding box while respecting anchor.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clampToBounds(rect: Rect, startRect: Rect, anchor: Anchor, bbox: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
} | undefined, maintainAspectRatio: boolean): Rect;
|
|
43
|
+
/**
|
|
44
|
+
* Reposition rect from current size so the start-gesture anchor remains fixed.
|
|
45
|
+
* This prevents translation drift when constraints clamp width/height.
|
|
46
|
+
*/
|
|
47
|
+
export declare function reanchorRect(rect: Rect, startRect: Rect, anchor: Anchor): Rect;
|
|
48
|
+
/**
|
|
49
|
+
* Apply min/max constraints. Also used by the drag pipeline for position clamping.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyConstraints(position: Rect, constraints: ResizeConstraints | undefined, maintainAspectRatio: boolean, skipBoundingClamp?: boolean): Rect;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a rect, when rotated, fits within the given page bounds.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isRectWithinRotatedBounds(rect: Rect, angleDegrees: number, bbox: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}): boolean;
|
|
59
|
+
export interface ResizeConfig {
|
|
60
|
+
startRect: Rect;
|
|
61
|
+
maintainAspectRatio?: boolean;
|
|
62
|
+
annotationRotation?: number;
|
|
63
|
+
constraints?: ResizeConstraints;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculate the new rect after a resize operation.
|
|
67
|
+
*
|
|
68
|
+
* For non-rotated annotations, runs the pipeline once with local bound clamping.
|
|
69
|
+
* For rotated annotations, uses a binary search to find the largest delta that
|
|
70
|
+
* keeps the visual AABB within page bounds.
|
|
71
|
+
*/
|
|
72
|
+
export declare function computeResizedRect(delta: Position, handle: ResizeHandle, config: ResizeConfig): Rect;
|
|
@@ -13,10 +13,43 @@ export interface VertexUI {
|
|
|
13
13
|
vertexSize?: number;
|
|
14
14
|
zIndex?: number;
|
|
15
15
|
}
|
|
16
|
+
export interface RotationUI {
|
|
17
|
+
/** Handle size in px (default 32) */
|
|
18
|
+
handleSize?: number;
|
|
19
|
+
/** Screen-pixel gap between bounding box edge and handle center (default 30) */
|
|
20
|
+
margin?: number;
|
|
21
|
+
/** z-index of the rotation handle (default 5) */
|
|
22
|
+
zIndex?: number;
|
|
23
|
+
/** Whether to show the connector line from center to handle (default true) */
|
|
24
|
+
showConnector?: boolean;
|
|
25
|
+
/** Connector line width in px (default 1) */
|
|
26
|
+
connectorWidth?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Screen-pixel gap between the rect edge and the rotation handle center (default orbit margin). */
|
|
29
|
+
export declare const ROTATION_HANDLE_MARGIN = 35;
|
|
16
30
|
export interface HandleDescriptor {
|
|
17
31
|
handle: ResizeHandle;
|
|
18
32
|
style: Record<string, number | string>;
|
|
19
33
|
attrs?: Record<string, any>;
|
|
20
34
|
}
|
|
35
|
+
export interface RotationHandleDescriptor {
|
|
36
|
+
/** Style for the rotation handle itself */
|
|
37
|
+
handleStyle: Record<string, number | string>;
|
|
38
|
+
/** Style for the connector line (if shown) */
|
|
39
|
+
connectorStyle: Record<string, number | string>;
|
|
40
|
+
/** Orbit radius in screen pixels used to position the handle. */
|
|
41
|
+
radius: number;
|
|
42
|
+
/** Attributes for the handle element */
|
|
43
|
+
attrs?: Record<string, any>;
|
|
44
|
+
}
|
|
21
45
|
export declare function describeResizeFromConfig(cfg: DragResizeConfig, ui?: ResizeUI): HandleDescriptor[];
|
|
22
46
|
export declare function describeVerticesFromConfig(cfg: DragResizeConfig, ui?: VertexUI, liveVertices?: Position[]): HandleDescriptor[];
|
|
47
|
+
/**
|
|
48
|
+
* Describe the rotation handle position and style.
|
|
49
|
+
* The rotation handle orbits around the center of the bounding box based on the current angle.
|
|
50
|
+
*
|
|
51
|
+
* @param cfg - The drag/resize config containing the element rect and scale
|
|
52
|
+
* @param ui - UI customization options
|
|
53
|
+
* @param currentAngle - The current rotation angle in degrees (0 = top, clockwise positive)
|
|
54
|
+
*/
|
|
55
|
+
export declare function describeRotationFromConfig(cfg: DragResizeConfig, ui?: RotationUI, currentAngle?: number): RotationHandleDescriptor;
|
|
@@ -23,4 +23,5 @@ export declare function useDragResize(getOptions: () => UseDragResizeOptions): {
|
|
|
23
23
|
};
|
|
24
24
|
createResizeProps: (handle: ResizeHandle) => ResizeHandleEventProps;
|
|
25
25
|
createVertexProps: (vertexIndex: number) => ResizeHandleEventProps;
|
|
26
|
+
createRotationProps: (initialRotation?: number, orbitRadiusPx?: number) => ResizeHandleEventProps;
|
|
26
27
|
};
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { UseDragResizeOptions } from './use-drag-resize.svelte';
|
|
2
|
-
import { ResizeUI, VertexUI } from '../../shared-svelte/plugin-interaction-primitives';
|
|
2
|
+
import { ResizeUI, VertexUI, RotationUI } from '../../shared-svelte/plugin-interaction-primitives';
|
|
3
3
|
export type HandleElementProps = {
|
|
4
|
-
key
|
|
4
|
+
key?: string | number;
|
|
5
5
|
style: string;
|
|
6
6
|
onpointerdown: (e: PointerEvent) => void;
|
|
7
7
|
onpointermove: (e: PointerEvent) => void;
|
|
8
8
|
onpointerup: (e: PointerEvent) => void;
|
|
9
9
|
onpointercancel: (e: PointerEvent) => void;
|
|
10
10
|
} & Record<string, any>;
|
|
11
|
+
export type RotationHandleElementProps = {
|
|
12
|
+
/** Props for the rotation handle element */
|
|
13
|
+
handle: HandleElementProps;
|
|
14
|
+
/** Props for the connector line element (if shown) */
|
|
15
|
+
connector: {
|
|
16
|
+
style: string;
|
|
17
|
+
} & Record<string, any>;
|
|
18
|
+
};
|
|
11
19
|
export declare function useInteractionHandles(getOpts: () => {
|
|
12
20
|
controller: UseDragResizeOptions;
|
|
13
21
|
resizeUI?: ResizeUI;
|
|
14
22
|
vertexUI?: VertexUI;
|
|
23
|
+
rotationUI?: RotationUI;
|
|
15
24
|
includeVertices?: boolean;
|
|
25
|
+
includeRotation?: boolean;
|
|
26
|
+
/** Current rotation angle of the annotation (for initializing rotation interaction) */
|
|
27
|
+
currentRotation?: number;
|
|
16
28
|
handleAttrs?: (h: 'nw' | 'ne' | 'sw' | 'se' | 'n' | 'e' | 's' | 'w') => Record<string, any> | void;
|
|
17
29
|
vertexAttrs?: (i: number) => Record<string, any> | void;
|
|
30
|
+
rotationAttrs?: () => Record<string, any> | void;
|
|
18
31
|
}): {
|
|
19
32
|
readonly dragProps: {
|
|
20
33
|
onpointerdown: (e: PointerEvent) => void;
|
|
@@ -29,4 +42,5 @@ export declare function useInteractionHandles(getOpts: () => {
|
|
|
29
42
|
};
|
|
30
43
|
readonly resize: HandleElementProps[];
|
|
31
44
|
readonly vertices: HandleElementProps[];
|
|
45
|
+
readonly rotation: RotationHandleElementProps | null;
|
|
32
46
|
};
|