@scaleflex/crop 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/a11y/aria.d.ts +5 -0
- package/dist/a11y/keyboard.d.ts +13 -0
- package/dist/animation/lerp.d.ts +15 -0
- package/dist/animation/spring.d.ts +15 -0
- package/dist/canvas/bleed-layer.d.ts +6 -0
- package/dist/canvas/crop-frame.d.ts +32 -0
- package/dist/canvas/grid-layer.d.ts +7 -0
- package/dist/canvas/hit-test.d.ts +10 -0
- package/dist/canvas/image-layer.d.ts +28 -0
- package/dist/canvas/overlay-layer.d.ts +6 -0
- package/dist/canvas/renderer.d.ts +34 -0
- package/dist/chunks/sfx-crop-1LGASewd.cjs +353 -0
- package/dist/chunks/sfx-crop-CEe6OfTZ.js +2030 -0
- package/dist/core/config.d.ts +10 -0
- package/dist/core/crop-controller.d.ts +65 -0
- package/dist/core/types.d.ts +270 -0
- package/dist/define.cjs +1194 -0
- package/dist/define.d.ts +1 -0
- package/dist/define.js +1746 -0
- package/dist/elements/base.d.ts +17 -0
- package/dist/elements/icons.d.ts +21 -0
- package/dist/elements/parse-shapes.d.ts +13 -0
- package/dist/elements/popover-anchor.d.ts +20 -0
- package/dist/elements/sfx-crop-canvas.d.ts +24 -0
- package/dist/elements/sfx-crop-canvas.styles.d.ts +1 -0
- package/dist/elements/sfx-crop-rotate.d.ts +42 -0
- package/dist/elements/sfx-crop-rotate.styles.d.ts +5 -0
- package/dist/elements/sfx-crop-shapes.d.ts +67 -0
- package/dist/elements/sfx-crop-shapes.styles.d.ts +6 -0
- package/dist/elements/sfx-crop-toolbar.d.ts +64 -0
- package/dist/elements/sfx-crop-toolbar.styles.d.ts +7 -0
- package/dist/elements/sfx-crop-zoom.d.ts +66 -0
- package/dist/elements/sfx-crop-zoom.styles.d.ts +7 -0
- package/dist/elements/sfx-crop.d.ts +134 -0
- package/dist/elements/sfx-crop.styles.d.ts +9 -0
- package/dist/export/exporter.d.ts +19 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +65 -0
- package/dist/interactions/drag-crop.d.ts +10 -0
- package/dist/interactions/pinch-zoom.d.ts +14 -0
- package/dist/interactions/pointer-tracker.d.ts +29 -0
- package/dist/interactions/resize-handles.d.ts +13 -0
- package/dist/interactions/wheel-zoom.d.ts +12 -0
- package/dist/react/define-CVJd5aYk.cjs +1545 -0
- package/dist/react/define-t4Z6KaLY.js +2590 -0
- package/dist/react/index-B-csHwK2.cjs +2 -0
- package/dist/react/index-CktjrogS.js +1468 -0
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.d.ts +21 -0
- package/dist/react/index.js +10 -0
- package/dist/react/sfx-crop.d.ts +86 -0
- package/dist/react/use-sfx-crop-controller.d.ts +74 -0
- package/dist/react/use-sfx-crop.d.ts +31 -0
- package/dist/styles/shared.css.d.ts +20 -0
- package/dist/transforms/constrain.d.ts +68 -0
- package/dist/transforms/matrix.d.ts +23 -0
- package/dist/transforms/transform-state.d.ts +12 -0
- package/dist/utils/events.d.ts +16 -0
- package/dist/utils/math.d.ts +12 -0
- package/package.json +108 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SfxCropConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Vertical space (in CSS px) reserved below the canvas for the toolbar when
|
|
4
|
+
* fitting the image to the host container. Both the controller and the
|
|
5
|
+
* renderer reach for this; keep them in sync by importing from here.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TOOLBAR_RESERVE_PX = 80;
|
|
8
|
+
export declare const DEFAULT_CONFIG: SfxCropConfig;
|
|
9
|
+
export declare function mergeConfig(partial: Partial<SfxCropConfig>): SfxCropConfig;
|
|
10
|
+
export declare function validateConfig(config: SfxCropConfig): string[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SfxCropConfig, TransformState, CropShapeName, TransformParams, CropRect } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Callbacks invoked by the controller in response to state transitions.
|
|
4
|
+
* All are optional. Designed for bridging into CustomEvents (<sfx-crop>)
|
|
5
|
+
* or into imperative listeners.
|
|
6
|
+
*/
|
|
7
|
+
export interface CropControllerCallbacks {
|
|
8
|
+
onReady?(): void;
|
|
9
|
+
onImageLoad?(image: HTMLImageElement): void;
|
|
10
|
+
onError?(error: Error): void;
|
|
11
|
+
onChange?(state: TransformState): void;
|
|
12
|
+
onCropChange?(crop: CropRect): void;
|
|
13
|
+
/** Fired whenever internal state syncs the toolbar's rotation slider. */
|
|
14
|
+
onRotationSync?(degrees: number): void;
|
|
15
|
+
/** Fired whenever internal state syncs the toolbar's shape selector. */
|
|
16
|
+
onShapeSync?(shape: CropShapeName): void;
|
|
17
|
+
/** Fired whenever internal state syncs the zoom slider. */
|
|
18
|
+
onScaleSync?(scale: number): void;
|
|
19
|
+
/**
|
|
20
|
+
* Fired on every wheel-zoom notch so the host can surface the zoom
|
|
21
|
+
* slider UI while scrolling. Separate from {@link onScaleSync} (which
|
|
22
|
+
* also fires for slider drags) so the host can distinguish wheel from
|
|
23
|
+
* pointer activity.
|
|
24
|
+
*/
|
|
25
|
+
onWheelZoomActivity?(): void;
|
|
26
|
+
/** Fired when loading/error UI needs to change. */
|
|
27
|
+
onLoadingChange?(loading: boolean, error: string | null): void;
|
|
28
|
+
}
|
|
29
|
+
export interface CropControllerOptions {
|
|
30
|
+
/** The canvas the renderer writes to (pre-created by the host element). */
|
|
31
|
+
canvas: HTMLCanvasElement;
|
|
32
|
+
/** Element hosting resize/keyboard/ARIA — usually the <sfx-crop> host itself. */
|
|
33
|
+
host: HTMLElement;
|
|
34
|
+
/** Layout reference — the wrapping container whose dimensions drive the fit-scale math. */
|
|
35
|
+
layoutContainer: HTMLElement;
|
|
36
|
+
/** Merged config (see {@link mergeConfig}). */
|
|
37
|
+
config: SfxCropConfig;
|
|
38
|
+
/** Optional callbacks. */
|
|
39
|
+
callbacks?: CropControllerCallbacks;
|
|
40
|
+
}
|
|
41
|
+
export interface CropController {
|
|
42
|
+
loadImage(src: string): Promise<void>;
|
|
43
|
+
getTransformState(): TransformState;
|
|
44
|
+
getCropRect(): CropRect;
|
|
45
|
+
setCropShape(shape: CropShapeName): void;
|
|
46
|
+
setCropRect(rect: CropRect): void;
|
|
47
|
+
rotateLeft(): void;
|
|
48
|
+
flipHorizontal(): void;
|
|
49
|
+
setRotation(deg: number): void;
|
|
50
|
+
setScale(scale: number): void;
|
|
51
|
+
reset(): void;
|
|
52
|
+
toCanvas(): HTMLCanvasElement;
|
|
53
|
+
toBlob(type?: string, quality?: number): Promise<Blob>;
|
|
54
|
+
toDataURL(type?: string, quality?: number): string;
|
|
55
|
+
toTransformParams(): TransformParams;
|
|
56
|
+
update(config: Partial<SfxCropConfig>): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Non-DOM-owning factory. The element that owns the DOM (e.g. `<sfx-crop>`)
|
|
61
|
+
* provides the canvas + layout container and listens to state via callbacks.
|
|
62
|
+
* No toolbar, zoom slider, or overlay divs are created here — those are the
|
|
63
|
+
* host's responsibility.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createCropController(opts: CropControllerOptions): CropController;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
export interface Point {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Size {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Built-in shape presets. `CropShapeName` below widens this to accept any
|
|
11
|
+
* free-form `"W:H"` string while keeping autocomplete on the known values
|
|
12
|
+
* — the classic `T | (string & {})` idiom preserves IntelliSense but lets
|
|
13
|
+
* consumers pass ad-hoc ratios like `'7:2'` or `'11:8'` without extending
|
|
14
|
+
* the library.
|
|
15
|
+
*/
|
|
16
|
+
export type CropShapeBuiltin = 'free' | 'square' | 'circle' | 'rounded-rect' | '16:9' | '4:3' | '3:2' | '5:4' | '2:1' | '9:16' | '3:4' | '2:3' | '4:5' | '1:2';
|
|
17
|
+
/**
|
|
18
|
+
* The full shape-name API — a built-in preset OR any consumer-supplied
|
|
19
|
+
* `"W:H"` string. Runtime validation lives in `parseRatio()` and
|
|
20
|
+
* `isValidShape()`.
|
|
21
|
+
*/
|
|
22
|
+
export type CropShapeName = CropShapeBuiltin | (string & {});
|
|
23
|
+
/**
|
|
24
|
+
* Named icon slots a consumer can override. Values are raw SVG strings —
|
|
25
|
+
* they're inserted via `unsafeHTML`, so treat them the same way as the
|
|
26
|
+
* built-in icons: static, author-trusted content. Never pass user input.
|
|
27
|
+
* Any slot left undefined falls back to the library's default SVG.
|
|
28
|
+
*
|
|
29
|
+
* Use from React:
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <SfxCrop icons={{ rotateLeft: '<svg>…</svg>', loupe: '<svg>…</svg>' }} />
|
|
32
|
+
* ```
|
|
33
|
+
* From HTML:
|
|
34
|
+
* ```js
|
|
35
|
+
* document.querySelector('sfx-crop').icons = { rotateLeft: '<svg>…</svg>' };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export type CropIconOverrides = Partial<{
|
|
39
|
+
/** 90° counter-clockwise rotate button in the toolbar. */
|
|
40
|
+
rotateLeft: string;
|
|
41
|
+
/** Flip horizontal button in the toolbar. */
|
|
42
|
+
flipHorizontal: string;
|
|
43
|
+
/** Collapsed fine-rotation (±45°) trigger. */
|
|
44
|
+
tilt: string;
|
|
45
|
+
/** Collapsed zoom trigger (magnifying glass). */
|
|
46
|
+
loupe: string;
|
|
47
|
+
/** `+` button inside the expanded zoom popover. */
|
|
48
|
+
zoomIn: string;
|
|
49
|
+
/** `−` button inside the expanded zoom popover. */
|
|
50
|
+
zoomOut: string;
|
|
51
|
+
/** Shape-selector trigger (generic "aspect / crop shape"). */
|
|
52
|
+
cropAspect: string;
|
|
53
|
+
/** "Custom" (Free) option icon in the shape dropdown. */
|
|
54
|
+
cropCustom: string;
|
|
55
|
+
/** Circle shape option icon. */
|
|
56
|
+
cropCircle: string;
|
|
57
|
+
/** Rounded-rect shape option icon. */
|
|
58
|
+
cropRoundedRect: string;
|
|
59
|
+
/** Landscape orientation tab icon. */
|
|
60
|
+
orientLandscape: string;
|
|
61
|
+
/** Portrait orientation tab icon. */
|
|
62
|
+
orientPortrait: string;
|
|
63
|
+
/** Chevron used on the shape-selector trigger. */
|
|
64
|
+
chevronDown: string;
|
|
65
|
+
/** "Reset" button in the toolbar. */
|
|
66
|
+
reset: string;
|
|
67
|
+
}>;
|
|
68
|
+
/** @deprecated Use CropShapeName instead */
|
|
69
|
+
export type CropShape = CropShapeName;
|
|
70
|
+
export interface CropShapeConfig {
|
|
71
|
+
type: 'free' | 'rect' | 'circle' | 'rounded-rect';
|
|
72
|
+
ratio?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface AspectRatioPreset {
|
|
75
|
+
label: string;
|
|
76
|
+
ratio: number | null;
|
|
77
|
+
shape: 'rect' | 'ellipse';
|
|
78
|
+
}
|
|
79
|
+
export type HandlePosition = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 's' | 'e' | 'w';
|
|
80
|
+
export interface NormalizedRect {
|
|
81
|
+
x: number;
|
|
82
|
+
y: number;
|
|
83
|
+
width: number;
|
|
84
|
+
height: number;
|
|
85
|
+
}
|
|
86
|
+
export interface CropRect {
|
|
87
|
+
/** Normalized [0,1] coordinates relative to the original image */
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
width: number;
|
|
91
|
+
height: number;
|
|
92
|
+
}
|
|
93
|
+
export interface TransformState {
|
|
94
|
+
/** Rotation in 90° increments (0, 90, 180, 270) */
|
|
95
|
+
quarterTurns: number;
|
|
96
|
+
/** Fine rotation in degrees (-45 to +45) */
|
|
97
|
+
rotation: number;
|
|
98
|
+
/** Horizontal flip */
|
|
99
|
+
flipH: boolean;
|
|
100
|
+
/** Vertical flip */
|
|
101
|
+
flipV: boolean;
|
|
102
|
+
/** Scale / zoom level (1 = fit) */
|
|
103
|
+
scale: number;
|
|
104
|
+
/** Image pan offset (in canvas pixels, relative to center) */
|
|
105
|
+
panX: number;
|
|
106
|
+
panY: number;
|
|
107
|
+
/** Crop rectangle in normalized coordinates */
|
|
108
|
+
cropRect: NormalizedRect;
|
|
109
|
+
/**
|
|
110
|
+
* Pivot for fine-rotation, in normalized image-space. Captured when the
|
|
111
|
+
* user tilts from 0°; kept stable across crop-rect edits so resizing
|
|
112
|
+
* the frame does not drag the tilted photo along. Cleared on reset to 0°.
|
|
113
|
+
*/
|
|
114
|
+
rotationPivot?: {
|
|
115
|
+
x: number;
|
|
116
|
+
y: number;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export interface DisplayState {
|
|
120
|
+
quarterTurns: number;
|
|
121
|
+
rotation: number;
|
|
122
|
+
flipH: number;
|
|
123
|
+
flipV: number;
|
|
124
|
+
scale: number;
|
|
125
|
+
panX: number;
|
|
126
|
+
panY: number;
|
|
127
|
+
cropRect: NormalizedRect;
|
|
128
|
+
rotationPivot?: {
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
};
|
|
132
|
+
gridOpacity: number;
|
|
133
|
+
/**
|
|
134
|
+
* True while the user is actively dragging/resizing/panning. The renderer
|
|
135
|
+
* snaps cropRect and pan directly to target instead of lerping, so the
|
|
136
|
+
* frame follows the cursor 1:1 instead of drifting behind it.
|
|
137
|
+
*/
|
|
138
|
+
interactive?: boolean;
|
|
139
|
+
}
|
|
140
|
+
export interface HitTarget {
|
|
141
|
+
type: 'handle' | 'crop-area' | 'move-handle' | 'outside' | 'none';
|
|
142
|
+
position?: HandlePosition;
|
|
143
|
+
}
|
|
144
|
+
export type CursorStyle = 'default' | 'move' | 'grab' | 'grabbing' | 'crosshair' | 'nwse-resize' | 'nesw-resize' | 'ns-resize' | 'ew-resize' | 'not-allowed';
|
|
145
|
+
export interface SpringConfig {
|
|
146
|
+
stiffness: number;
|
|
147
|
+
damping: number;
|
|
148
|
+
mass: number;
|
|
149
|
+
precision: number;
|
|
150
|
+
}
|
|
151
|
+
export interface LerpConfig {
|
|
152
|
+
factor: number;
|
|
153
|
+
precision: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @internal
|
|
157
|
+
*
|
|
158
|
+
* Internal config shape shared with {@link import('./crop-controller').createCropController}
|
|
159
|
+
* so attribute deltas and defaults stay aligned with the element. Consumers
|
|
160
|
+
* interact with `<sfx-crop>` via HTML attributes and DOM properties — see the
|
|
161
|
+
* `@property` declarations in `src/elements/sfx-crop.ts` for the public API.
|
|
162
|
+
*/
|
|
163
|
+
export interface SfxCropConfig {
|
|
164
|
+
/** Image source URL */
|
|
165
|
+
src: string;
|
|
166
|
+
/**
|
|
167
|
+
* Display variant.
|
|
168
|
+
* - `'classic'` (default): photo fills the editor at its own aspect; a
|
|
169
|
+
* movable / resizable crop frame floats over it, dimmed outside.
|
|
170
|
+
* - `'fixed'`: the editor area itself IS the crop frame — sized to the
|
|
171
|
+
* `cropShape` aspect and centered. The photo pans/zooms/rotates UNDER it
|
|
172
|
+
* and always covers it (no empty gaps). No resize handles; toolbar overlays
|
|
173
|
+
* the frame.
|
|
174
|
+
*/
|
|
175
|
+
variant: 'classic' | 'fixed';
|
|
176
|
+
/** Initial crop rect (normalized 0-1) */
|
|
177
|
+
initialCrop?: CropRect | null;
|
|
178
|
+
/** Initial fine rotation degrees (-45 to 45) */
|
|
179
|
+
initialRotation?: number;
|
|
180
|
+
/** Initial scale/zoom level */
|
|
181
|
+
initialScale?: number;
|
|
182
|
+
/** Initial crop shape */
|
|
183
|
+
cropShape: CropShapeName;
|
|
184
|
+
/** Custom aspect ratio presets */
|
|
185
|
+
customAspectRatios?: Array<{
|
|
186
|
+
name: string;
|
|
187
|
+
ratio: number;
|
|
188
|
+
}>;
|
|
189
|
+
/** Minimum crop size in pixels */
|
|
190
|
+
minCropSize: number;
|
|
191
|
+
/** Available shapes in the selector */
|
|
192
|
+
availableShapes: CropShapeName[];
|
|
193
|
+
/** Minimum zoom level */
|
|
194
|
+
minScale: number;
|
|
195
|
+
/** Maximum zoom level */
|
|
196
|
+
maxScale: number;
|
|
197
|
+
/** Color theme */
|
|
198
|
+
theme: 'light' | 'dark';
|
|
199
|
+
/** Show grid overlay during interaction */
|
|
200
|
+
showGrid: boolean | 'interaction';
|
|
201
|
+
/** Show rotation slider */
|
|
202
|
+
showRotateSlider: boolean;
|
|
203
|
+
/** Show zoom slider */
|
|
204
|
+
showZoomSlider: boolean;
|
|
205
|
+
/** Show shape selector */
|
|
206
|
+
showShapeSelector: boolean;
|
|
207
|
+
/** Show rotate button */
|
|
208
|
+
showRotateButton: boolean;
|
|
209
|
+
/** Show flip button */
|
|
210
|
+
showFlipButton: boolean;
|
|
211
|
+
/** Toolbar position */
|
|
212
|
+
toolbarPosition: 'bottom' | 'top';
|
|
213
|
+
/** Show toolbar */
|
|
214
|
+
showToolbar: boolean;
|
|
215
|
+
/** Overlay mask color */
|
|
216
|
+
overlayColor: string;
|
|
217
|
+
/** Handle size in pixels */
|
|
218
|
+
handleSize: number;
|
|
219
|
+
/** Handle color */
|
|
220
|
+
handleColor: string;
|
|
221
|
+
/** Border radius for rounded-rect crop shape (in pixels) */
|
|
222
|
+
borderRadius: number;
|
|
223
|
+
/** Output format for toBlob/toDataURL */
|
|
224
|
+
outputType: string;
|
|
225
|
+
/** Output quality (0-1) for lossy formats */
|
|
226
|
+
outputQuality: number;
|
|
227
|
+
/** Maximum output width (0 = original) */
|
|
228
|
+
maxOutputWidth: number;
|
|
229
|
+
/** Maximum output height (0 = original) */
|
|
230
|
+
maxOutputHeight: number;
|
|
231
|
+
/** Show print bleed margin guides inside crop area */
|
|
232
|
+
showBleedMargin: boolean;
|
|
233
|
+
/** Bleed margin offset in pixels */
|
|
234
|
+
bleedMarginSize: number;
|
|
235
|
+
/** Bleed margin line color */
|
|
236
|
+
bleedMarginColor: string;
|
|
237
|
+
/** Enable animations */
|
|
238
|
+
enableAnimations: boolean;
|
|
239
|
+
/** Animation speed multiplier */
|
|
240
|
+
animationSpeed: number;
|
|
241
|
+
/** Enable keyboard navigation */
|
|
242
|
+
keyboard: boolean;
|
|
243
|
+
/** Enable pinch-to-zoom */
|
|
244
|
+
pinchZoom: boolean;
|
|
245
|
+
/** Enable mouse wheel zoom */
|
|
246
|
+
wheelZoom: boolean;
|
|
247
|
+
}
|
|
248
|
+
export interface TransformParams {
|
|
249
|
+
/** Total rotation in degrees */
|
|
250
|
+
rotation: number;
|
|
251
|
+
/** Whether flipped horizontally */
|
|
252
|
+
flipH: boolean;
|
|
253
|
+
/** Whether flipped vertically */
|
|
254
|
+
flipV: boolean;
|
|
255
|
+
/** Scale level */
|
|
256
|
+
scale: number;
|
|
257
|
+
/** Crop in original image pixel coordinates */
|
|
258
|
+
crop: {
|
|
259
|
+
x: number;
|
|
260
|
+
y: number;
|
|
261
|
+
width: number;
|
|
262
|
+
height: number;
|
|
263
|
+
};
|
|
264
|
+
/** Output width in pixels */
|
|
265
|
+
outputWidth: number;
|
|
266
|
+
/** Output height in pixels */
|
|
267
|
+
outputHeight: number;
|
|
268
|
+
}
|
|
269
|
+
/** @deprecated Use TransformParams instead */
|
|
270
|
+
export type CropResult = TransformParams;
|