@cropvue/core 0.1.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/index.cjs +1183 -0
- package/dist/index.d.cts +424 -0
- package/dist/index.d.mts +424 -0
- package/dist/index.d.ts +424 -0
- package/dist/index.mjs +1138 -0
- package/package.json +43 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { Ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface TransformState {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
scale: number;
|
|
8
|
+
rotation: number;
|
|
9
|
+
flipX: boolean;
|
|
10
|
+
flipY: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface CropState {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
stencil: StencilType;
|
|
18
|
+
points?: Point[];
|
|
19
|
+
aspectRatio?: number | null;
|
|
20
|
+
minWidth?: number;
|
|
21
|
+
minHeight?: number;
|
|
22
|
+
maxWidth?: number;
|
|
23
|
+
maxHeight?: number;
|
|
24
|
+
}
|
|
25
|
+
type StencilType = 'rectangle' | 'circle' | 'freeform';
|
|
26
|
+
interface Point {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}
|
|
30
|
+
interface ImageData {
|
|
31
|
+
element: HTMLImageElement;
|
|
32
|
+
naturalWidth: number;
|
|
33
|
+
naturalHeight: number;
|
|
34
|
+
originalFile?: File;
|
|
35
|
+
originalSize?: number;
|
|
36
|
+
}
|
|
37
|
+
interface CropResult {
|
|
38
|
+
blob: Blob;
|
|
39
|
+
file: File;
|
|
40
|
+
url: string;
|
|
41
|
+
coords: CropCoordinates;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
originalWidth: number;
|
|
45
|
+
originalHeight: number;
|
|
46
|
+
}
|
|
47
|
+
interface CropCoordinates {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
rotation: number;
|
|
53
|
+
flipX: boolean;
|
|
54
|
+
flipY: boolean;
|
|
55
|
+
scale: number;
|
|
56
|
+
}
|
|
57
|
+
interface UploadResult {
|
|
58
|
+
url?: string;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
type UploadFn = (file: File, options: {
|
|
62
|
+
onProgress: (percent: number) => void;
|
|
63
|
+
signal: AbortSignal;
|
|
64
|
+
}) => Promise<UploadResult>;
|
|
65
|
+
interface QueueItem {
|
|
66
|
+
id: string;
|
|
67
|
+
file: File;
|
|
68
|
+
thumbnail: string;
|
|
69
|
+
result?: CropResult;
|
|
70
|
+
status: 'pending' | 'cropping' | 'done';
|
|
71
|
+
}
|
|
72
|
+
type CropVueError = {
|
|
73
|
+
type: 'file-too-large';
|
|
74
|
+
maxSize: number;
|
|
75
|
+
actualSize: number;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'invalid-type';
|
|
78
|
+
accepted: string[];
|
|
79
|
+
actual: string;
|
|
80
|
+
} | {
|
|
81
|
+
type: 'load-failed';
|
|
82
|
+
message: string;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'canvas-limit';
|
|
85
|
+
maxDimension: number;
|
|
86
|
+
} | {
|
|
87
|
+
type: 'upload-failed';
|
|
88
|
+
message: string;
|
|
89
|
+
} | {
|
|
90
|
+
type: 'compress-failed';
|
|
91
|
+
message: string;
|
|
92
|
+
};
|
|
93
|
+
type OutputFormat = 'auto' | 'webp' | 'jpeg' | 'png';
|
|
94
|
+
interface CropperOptions {
|
|
95
|
+
stencil?: StencilType;
|
|
96
|
+
aspectRatio?: number | null;
|
|
97
|
+
minWidth?: number;
|
|
98
|
+
minHeight?: number;
|
|
99
|
+
maxWidth?: number;
|
|
100
|
+
maxHeight?: number;
|
|
101
|
+
outputFormat?: OutputFormat;
|
|
102
|
+
outputQuality?: number;
|
|
103
|
+
outputMaxWidth?: number;
|
|
104
|
+
outputMaxHeight?: number;
|
|
105
|
+
}
|
|
106
|
+
interface DropzoneOptions {
|
|
107
|
+
accept?: string[];
|
|
108
|
+
maxSize?: number;
|
|
109
|
+
multiple?: boolean;
|
|
110
|
+
onFiles?: (files: File[]) => void;
|
|
111
|
+
onError?: (error: CropVueError) => void;
|
|
112
|
+
}
|
|
113
|
+
interface UploaderOptions {
|
|
114
|
+
url?: string;
|
|
115
|
+
fieldName?: string;
|
|
116
|
+
headers?: Record<string, string>;
|
|
117
|
+
handler?: UploadFn;
|
|
118
|
+
}
|
|
119
|
+
interface CompressorOptions {
|
|
120
|
+
format?: OutputFormat;
|
|
121
|
+
quality?: number;
|
|
122
|
+
maxWidth?: number;
|
|
123
|
+
maxHeight?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
declare function useCropper(options?: CropperOptions): {
|
|
127
|
+
image: Ref<ImageData | null, ImageData | null>;
|
|
128
|
+
transform: Ref<TransformState, TransformState>;
|
|
129
|
+
crop: Ref<CropState, CropState>;
|
|
130
|
+
isReady: Ref<boolean, boolean>;
|
|
131
|
+
loadFile: (file: File) => Promise<void>;
|
|
132
|
+
loadUrl: (url: string) => Promise<void>;
|
|
133
|
+
rotateLeft: () => void;
|
|
134
|
+
rotateRight: () => void;
|
|
135
|
+
rotateTo: (degrees: number) => void;
|
|
136
|
+
flipX: () => void;
|
|
137
|
+
flipY: () => void;
|
|
138
|
+
zoomTo: (scale: number) => void;
|
|
139
|
+
zoomBy: (delta: number) => void;
|
|
140
|
+
panTo: (x: number, y: number) => void;
|
|
141
|
+
reset: () => void;
|
|
142
|
+
setCropArea: (area: Partial<CropState>) => void;
|
|
143
|
+
setStencil: (stencil: StencilType) => void;
|
|
144
|
+
setAspectRatio: (ratio: number | null) => void;
|
|
145
|
+
getResult: (opts?: {
|
|
146
|
+
format?: "auto" | "webp" | "jpeg" | "png";
|
|
147
|
+
quality?: number;
|
|
148
|
+
maxWidth?: number;
|
|
149
|
+
maxHeight?: number;
|
|
150
|
+
}) => Promise<CropResult>;
|
|
151
|
+
getPreviewUrl: () => string;
|
|
152
|
+
canvasRef: Ref<HTMLCanvasElement | null, HTMLCanvasElement | null>;
|
|
153
|
+
renderToCanvas: () => void;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
declare function validateFile(file: File, options: {
|
|
157
|
+
accept: string[];
|
|
158
|
+
maxSize: number;
|
|
159
|
+
}): CropVueError | null;
|
|
160
|
+
declare function useDropzone(options?: DropzoneOptions): {
|
|
161
|
+
isDragging: Ref<boolean, boolean>;
|
|
162
|
+
files: Ref<File[], File[]>;
|
|
163
|
+
dropzoneRef: Ref<HTMLElement | null, HTMLElement | null>;
|
|
164
|
+
open: () => void;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
interface Queue {
|
|
168
|
+
items: QueueItem[];
|
|
169
|
+
currentIndex: number;
|
|
170
|
+
add: (files: File[]) => void;
|
|
171
|
+
remove: (index: number) => void;
|
|
172
|
+
select: (index: number) => void;
|
|
173
|
+
next: () => void;
|
|
174
|
+
previous: () => void;
|
|
175
|
+
clear: () => void;
|
|
176
|
+
setResult: (index: number, result: CropResult) => void;
|
|
177
|
+
}
|
|
178
|
+
declare function createQueue(): Queue;
|
|
179
|
+
declare function useImageQueue(): {
|
|
180
|
+
images: vue.Ref<{
|
|
181
|
+
id: string;
|
|
182
|
+
file: {
|
|
183
|
+
readonly lastModified: number;
|
|
184
|
+
readonly name: string;
|
|
185
|
+
readonly webkitRelativePath: string;
|
|
186
|
+
readonly size: number;
|
|
187
|
+
readonly type: string;
|
|
188
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
189
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
190
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
191
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
192
|
+
text: () => Promise<string>;
|
|
193
|
+
};
|
|
194
|
+
thumbnail: string;
|
|
195
|
+
result?: {
|
|
196
|
+
blob: {
|
|
197
|
+
readonly size: number;
|
|
198
|
+
readonly type: string;
|
|
199
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
200
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
201
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
202
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
203
|
+
text: () => Promise<string>;
|
|
204
|
+
};
|
|
205
|
+
file: {
|
|
206
|
+
readonly lastModified: number;
|
|
207
|
+
readonly name: string;
|
|
208
|
+
readonly webkitRelativePath: string;
|
|
209
|
+
readonly size: number;
|
|
210
|
+
readonly type: string;
|
|
211
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
212
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
213
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
214
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
215
|
+
text: () => Promise<string>;
|
|
216
|
+
};
|
|
217
|
+
url: string;
|
|
218
|
+
coords: {
|
|
219
|
+
x: number;
|
|
220
|
+
y: number;
|
|
221
|
+
width: number;
|
|
222
|
+
height: number;
|
|
223
|
+
rotation: number;
|
|
224
|
+
flipX: boolean;
|
|
225
|
+
flipY: boolean;
|
|
226
|
+
scale: number;
|
|
227
|
+
};
|
|
228
|
+
width: number;
|
|
229
|
+
height: number;
|
|
230
|
+
originalWidth: number;
|
|
231
|
+
originalHeight: number;
|
|
232
|
+
} | undefined;
|
|
233
|
+
status: "pending" | "cropping" | "done";
|
|
234
|
+
}[], QueueItem[] | {
|
|
235
|
+
id: string;
|
|
236
|
+
file: {
|
|
237
|
+
readonly lastModified: number;
|
|
238
|
+
readonly name: string;
|
|
239
|
+
readonly webkitRelativePath: string;
|
|
240
|
+
readonly size: number;
|
|
241
|
+
readonly type: string;
|
|
242
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
243
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
244
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
245
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
246
|
+
text: () => Promise<string>;
|
|
247
|
+
};
|
|
248
|
+
thumbnail: string;
|
|
249
|
+
result?: {
|
|
250
|
+
blob: {
|
|
251
|
+
readonly size: number;
|
|
252
|
+
readonly type: string;
|
|
253
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
254
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
255
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
256
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
257
|
+
text: () => Promise<string>;
|
|
258
|
+
};
|
|
259
|
+
file: {
|
|
260
|
+
readonly lastModified: number;
|
|
261
|
+
readonly name: string;
|
|
262
|
+
readonly webkitRelativePath: string;
|
|
263
|
+
readonly size: number;
|
|
264
|
+
readonly type: string;
|
|
265
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
266
|
+
bytes: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
267
|
+
slice: (start?: number, end?: number, contentType?: string) => Blob;
|
|
268
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
269
|
+
text: () => Promise<string>;
|
|
270
|
+
};
|
|
271
|
+
url: string;
|
|
272
|
+
coords: {
|
|
273
|
+
x: number;
|
|
274
|
+
y: number;
|
|
275
|
+
width: number;
|
|
276
|
+
height: number;
|
|
277
|
+
rotation: number;
|
|
278
|
+
flipX: boolean;
|
|
279
|
+
flipY: boolean;
|
|
280
|
+
scale: number;
|
|
281
|
+
};
|
|
282
|
+
width: number;
|
|
283
|
+
height: number;
|
|
284
|
+
originalWidth: number;
|
|
285
|
+
originalHeight: number;
|
|
286
|
+
} | undefined;
|
|
287
|
+
status: "pending" | "cropping" | "done";
|
|
288
|
+
}[]>;
|
|
289
|
+
current: vue.Ref<number, number>;
|
|
290
|
+
add: (files: File[]) => void;
|
|
291
|
+
remove: (index: number) => void;
|
|
292
|
+
select: (index: number) => void;
|
|
293
|
+
next: () => void;
|
|
294
|
+
previous: () => void;
|
|
295
|
+
clear: () => void;
|
|
296
|
+
results: vue.ComputedRef<CropResult[]>;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
declare function createUploadHandler(options: UploaderOptions): UploadFn;
|
|
300
|
+
declare function useUploader(options?: UploaderOptions): {
|
|
301
|
+
upload: (file: File | Blob) => Promise<UploadResult>;
|
|
302
|
+
isUploading: vue.Ref<boolean, boolean>;
|
|
303
|
+
progress: vue.Ref<number, number>;
|
|
304
|
+
error: vue.Ref<string | null, string | null>;
|
|
305
|
+
abort: () => void;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
declare function chooseOutputFormat(format: OutputFormat, inputMime: string): string;
|
|
309
|
+
declare function compressBlob(canvas: HTMLCanvasElement, options: {
|
|
310
|
+
format: OutputFormat;
|
|
311
|
+
quality: number;
|
|
312
|
+
inputMime: string;
|
|
313
|
+
maxInputSize?: number;
|
|
314
|
+
}): Promise<Blob>;
|
|
315
|
+
declare function useCompressor(options?: CompressorOptions): {
|
|
316
|
+
compress: (canvas: HTMLCanvasElement, inputFile?: File) => Promise<Blob>;
|
|
317
|
+
isCompressing: vue.Ref<boolean, boolean>;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
declare function handlePan(state: TransformState, dx: number, dy: number): TransformState;
|
|
321
|
+
declare function handleZoom(state: TransformState, delta: number, centerX: number, centerY: number): TransformState;
|
|
322
|
+
type HandlePosition = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 's' | 'e' | 'w';
|
|
323
|
+
declare function handleCropResize(crop: CropState, handle: HandlePosition, dx: number, dy: number, bounds: {
|
|
324
|
+
width: number;
|
|
325
|
+
height: number;
|
|
326
|
+
}): CropState;
|
|
327
|
+
declare function handleCropMove(crop: CropState, dx: number, dy: number, bounds: {
|
|
328
|
+
width: number;
|
|
329
|
+
height: number;
|
|
330
|
+
}): CropState;
|
|
331
|
+
declare function handleKeyboard(state: TransformState, key: string, shiftKey: boolean): TransformState;
|
|
332
|
+
|
|
333
|
+
interface PointerHandlerOptions {
|
|
334
|
+
onPan: (dx: number, dy: number) => void;
|
|
335
|
+
onZoom: (delta: number, centerX: number, centerY: number) => void;
|
|
336
|
+
onCropResize: (handle: HandlePosition, dx: number, dy: number) => void;
|
|
337
|
+
onCropMove: (dx: number, dy: number) => void;
|
|
338
|
+
onKeyboard: (key: string, shiftKey: boolean) => void;
|
|
339
|
+
getHandleAtPoint: (e: PointerEvent) => HandlePosition | null;
|
|
340
|
+
isInsideCropArea: (e: PointerEvent) => boolean;
|
|
341
|
+
displayScale: () => number;
|
|
342
|
+
}
|
|
343
|
+
interface PointerHandlerCleanup {
|
|
344
|
+
destroy: () => void;
|
|
345
|
+
}
|
|
346
|
+
declare function usePointerHandler(element: HTMLElement, options: PointerHandlerOptions): PointerHandlerCleanup;
|
|
347
|
+
|
|
348
|
+
declare function createTransformState(overrides?: Partial<TransformState>): TransformState;
|
|
349
|
+
declare function createCropState(dimensions: {
|
|
350
|
+
width: number;
|
|
351
|
+
height: number;
|
|
352
|
+
}, overrides?: Partial<CropState>): CropState;
|
|
353
|
+
declare function snapRotation(degrees: number): number;
|
|
354
|
+
declare function applyRotation(state: TransformState, degrees: number): TransformState;
|
|
355
|
+
declare function applyFlip(state: TransformState, axis: 'x' | 'y'): TransformState;
|
|
356
|
+
declare function applyZoom(state: TransformState, delta: number): TransformState;
|
|
357
|
+
declare function applyPan(state: TransformState, dx: number, dy: number): TransformState;
|
|
358
|
+
declare function clampTransform(state: TransformState, bounds: {
|
|
359
|
+
minX: number;
|
|
360
|
+
maxX: number;
|
|
361
|
+
minY: number;
|
|
362
|
+
maxY: number;
|
|
363
|
+
}): TransformState;
|
|
364
|
+
declare function resetTransform(_state: TransformState): TransformState;
|
|
365
|
+
|
|
366
|
+
interface RenderOptions {
|
|
367
|
+
maxWidth?: number;
|
|
368
|
+
maxHeight?: number;
|
|
369
|
+
}
|
|
370
|
+
interface ExportOptions {
|
|
371
|
+
format: string;
|
|
372
|
+
quality: number;
|
|
373
|
+
}
|
|
374
|
+
declare function renderCrop(canvas: HTMLCanvasElement, image: HTMLImageElement, crop: CropState, transform: TransformState, options?: RenderOptions): void;
|
|
375
|
+
declare function exportCrop(canvas: HTMLCanvasElement, options: ExportOptions): Promise<Blob>;
|
|
376
|
+
|
|
377
|
+
interface ContainerBounds {
|
|
378
|
+
containerWidth: number;
|
|
379
|
+
containerHeight: number;
|
|
380
|
+
}
|
|
381
|
+
declare function constrainCropSize(crop: CropState, bounds: ContainerBounds): CropState;
|
|
382
|
+
declare function constrainAspectRatio(crop: CropState): CropState;
|
|
383
|
+
declare function constrainCropPosition(crop: CropState, bounds: ContainerBounds): CropState;
|
|
384
|
+
|
|
385
|
+
interface ContainerDimensions {
|
|
386
|
+
containerWidth: number;
|
|
387
|
+
containerHeight: number;
|
|
388
|
+
}
|
|
389
|
+
declare function getRectangleClipPath(rect: {
|
|
390
|
+
x: number;
|
|
391
|
+
y: number;
|
|
392
|
+
width: number;
|
|
393
|
+
height: number;
|
|
394
|
+
}, container: ContainerDimensions): string;
|
|
395
|
+
declare function getCircleClipPath(rect: {
|
|
396
|
+
x: number;
|
|
397
|
+
y: number;
|
|
398
|
+
width: number;
|
|
399
|
+
height: number;
|
|
400
|
+
}, _container: ContainerDimensions): string;
|
|
401
|
+
declare function getFreeformClipPath(points: Point[]): string;
|
|
402
|
+
declare function isPointInsideStencil(px: number, py: number, crop: Pick<CropState, 'stencil' | 'x' | 'y' | 'width' | 'height' | 'points'>): boolean;
|
|
403
|
+
|
|
404
|
+
declare function getMaxCanvasSize(): number;
|
|
405
|
+
declare function downsampleDimensions(width: number, height: number, maxDimension: number): {
|
|
406
|
+
width: number;
|
|
407
|
+
height: number;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
declare function detectMimeType(file: File): string;
|
|
411
|
+
declare function supportsWebP(): boolean;
|
|
412
|
+
declare function getMimeForFormat(format: OutputFormat): string;
|
|
413
|
+
declare function hasTransparency(mime: string): boolean;
|
|
414
|
+
|
|
415
|
+
declare function loadImageFromFile(file: File): Promise<ImageData>;
|
|
416
|
+
declare function loadImageFromUrl(url: string): Promise<ImageData>;
|
|
417
|
+
declare function needsDownsample(width: number, height: number): boolean;
|
|
418
|
+
declare function getSafeDimensions(width: number, height: number): {
|
|
419
|
+
width: number;
|
|
420
|
+
height: number;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
export { applyFlip, applyPan, applyRotation, applyZoom, chooseOutputFormat, clampTransform, compressBlob, constrainAspectRatio, constrainCropPosition, constrainCropSize, createCropState, createQueue, createTransformState, createUploadHandler, detectMimeType, downsampleDimensions, exportCrop, getCircleClipPath, getFreeformClipPath, getMaxCanvasSize, getMimeForFormat, getRectangleClipPath, getSafeDimensions, handleCropMove, handleCropResize, handleKeyboard, handlePan, handleZoom, hasTransparency, isPointInsideStencil, loadImageFromFile, loadImageFromUrl, needsDownsample, renderCrop, resetTransform, snapRotation, supportsWebP, useCompressor, useCropper, useDropzone, useImageQueue, usePointerHandler, useUploader, validateFile };
|
|
424
|
+
export type { CompressorOptions, CropCoordinates, CropResult, CropState, CropVueError, CropperOptions, DropzoneOptions, HandlePosition, ImageData, OutputFormat, Point, PointerHandlerCleanup, PointerHandlerOptions, QueueItem, StencilType, TransformState, UploadFn, UploadResult, UploaderOptions };
|