@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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/a11y/aria.d.ts +5 -0
  4. package/dist/a11y/keyboard.d.ts +13 -0
  5. package/dist/animation/lerp.d.ts +15 -0
  6. package/dist/animation/spring.d.ts +15 -0
  7. package/dist/canvas/bleed-layer.d.ts +6 -0
  8. package/dist/canvas/crop-frame.d.ts +32 -0
  9. package/dist/canvas/grid-layer.d.ts +7 -0
  10. package/dist/canvas/hit-test.d.ts +10 -0
  11. package/dist/canvas/image-layer.d.ts +28 -0
  12. package/dist/canvas/overlay-layer.d.ts +6 -0
  13. package/dist/canvas/renderer.d.ts +34 -0
  14. package/dist/chunks/sfx-crop-1LGASewd.cjs +353 -0
  15. package/dist/chunks/sfx-crop-CEe6OfTZ.js +2030 -0
  16. package/dist/core/config.d.ts +10 -0
  17. package/dist/core/crop-controller.d.ts +65 -0
  18. package/dist/core/types.d.ts +270 -0
  19. package/dist/define.cjs +1194 -0
  20. package/dist/define.d.ts +1 -0
  21. package/dist/define.js +1746 -0
  22. package/dist/elements/base.d.ts +17 -0
  23. package/dist/elements/icons.d.ts +21 -0
  24. package/dist/elements/parse-shapes.d.ts +13 -0
  25. package/dist/elements/popover-anchor.d.ts +20 -0
  26. package/dist/elements/sfx-crop-canvas.d.ts +24 -0
  27. package/dist/elements/sfx-crop-canvas.styles.d.ts +1 -0
  28. package/dist/elements/sfx-crop-rotate.d.ts +42 -0
  29. package/dist/elements/sfx-crop-rotate.styles.d.ts +5 -0
  30. package/dist/elements/sfx-crop-shapes.d.ts +67 -0
  31. package/dist/elements/sfx-crop-shapes.styles.d.ts +6 -0
  32. package/dist/elements/sfx-crop-toolbar.d.ts +64 -0
  33. package/dist/elements/sfx-crop-toolbar.styles.d.ts +7 -0
  34. package/dist/elements/sfx-crop-zoom.d.ts +66 -0
  35. package/dist/elements/sfx-crop-zoom.styles.d.ts +7 -0
  36. package/dist/elements/sfx-crop.d.ts +134 -0
  37. package/dist/elements/sfx-crop.styles.d.ts +9 -0
  38. package/dist/export/exporter.d.ts +19 -0
  39. package/dist/index.cjs +2 -0
  40. package/dist/index.d.ts +22 -0
  41. package/dist/index.js +65 -0
  42. package/dist/interactions/drag-crop.d.ts +10 -0
  43. package/dist/interactions/pinch-zoom.d.ts +14 -0
  44. package/dist/interactions/pointer-tracker.d.ts +29 -0
  45. package/dist/interactions/resize-handles.d.ts +13 -0
  46. package/dist/interactions/wheel-zoom.d.ts +12 -0
  47. package/dist/react/define-CVJd5aYk.cjs +1545 -0
  48. package/dist/react/define-t4Z6KaLY.js +2590 -0
  49. package/dist/react/index-B-csHwK2.cjs +2 -0
  50. package/dist/react/index-CktjrogS.js +1468 -0
  51. package/dist/react/index.cjs +2 -0
  52. package/dist/react/index.d.ts +21 -0
  53. package/dist/react/index.js +10 -0
  54. package/dist/react/sfx-crop.d.ts +86 -0
  55. package/dist/react/use-sfx-crop-controller.d.ts +74 -0
  56. package/dist/react/use-sfx-crop.d.ts +31 -0
  57. package/dist/styles/shared.css.d.ts +20 -0
  58. package/dist/transforms/constrain.d.ts +68 -0
  59. package/dist/transforms/matrix.d.ts +23 -0
  60. package/dist/transforms/transform-state.d.ts +12 -0
  61. package/dist/utils/events.d.ts +16 -0
  62. package/dist/utils/math.d.ts +12 -0
  63. 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;