@wordpress/image-cropper 1.0.1-next.6deb34194.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +196 -0
  4. package/build/components/image-cropper/index.js +97 -0
  5. package/build/components/image-cropper/index.js.map +7 -0
  6. package/build/constants.js +37 -0
  7. package/build/constants.js.map +7 -0
  8. package/build/index.js +52 -0
  9. package/build/index.js.map +7 -0
  10. package/build/provider/index.js +86 -0
  11. package/build/provider/index.js.map +7 -0
  12. package/build/provider/use-image-cropper.js +189 -0
  13. package/build/provider/use-image-cropper.js.map +7 -0
  14. package/build/types.js +19 -0
  15. package/build/types.js.map +7 -0
  16. package/build/utils.js +107 -0
  17. package/build/utils.js.map +7 -0
  18. package/build-module/components/image-cropper/index.js +66 -0
  19. package/build-module/components/image-cropper/index.js.map +7 -0
  20. package/build-module/constants.js +10 -0
  21. package/build-module/constants.js.map +7 -0
  22. package/build-module/index.js +12 -0
  23. package/build-module/index.js.map +7 -0
  24. package/build-module/provider/index.js +50 -0
  25. package/build-module/provider/index.js.map +7 -0
  26. package/build-module/provider/use-image-cropper.js +167 -0
  27. package/build-module/provider/use-image-cropper.js.map +7 -0
  28. package/build-module/types.js +1 -0
  29. package/build-module/types.js.map +7 -0
  30. package/build-module/utils.js +78 -0
  31. package/build-module/utils.js.map +7 -0
  32. package/build-types/components/image-cropper/index.d.ts +3 -0
  33. package/build-types/components/image-cropper/index.d.ts.map +1 -0
  34. package/build-types/constants.d.ts +4 -0
  35. package/build-types/constants.d.ts.map +1 -0
  36. package/build-types/index.d.ts +5 -0
  37. package/build-types/index.d.ts.map +1 -0
  38. package/build-types/provider/index.d.ts +7 -0
  39. package/build-types/provider/index.d.ts.map +1 -0
  40. package/build-types/provider/use-image-cropper.d.ts +15 -0
  41. package/build-types/provider/use-image-cropper.d.ts.map +1 -0
  42. package/build-types/stories/index.story.d.ts +28 -0
  43. package/build-types/stories/index.story.d.ts.map +1 -0
  44. package/build-types/types.d.ts +42 -0
  45. package/build-types/types.d.ts.map +1 -0
  46. package/build-types/utils.d.ts +60 -0
  47. package/build-types/utils.d.ts.map +1 -0
  48. package/package.json +49 -0
  49. package/src/components/image-cropper/index.tsx +84 -0
  50. package/src/constants.ts +3 -0
  51. package/src/index.ts +4 -0
  52. package/src/provider/index.tsx +57 -0
  53. package/src/provider/use-image-cropper.ts +220 -0
  54. package/src/stories/index.story.tsx +351 -0
  55. package/src/stories/style.css +17 -0
  56. package/src/types.ts +54 -0
  57. package/src/utils.ts +146 -0
  58. package/tsconfig.json +8 -0
  59. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,167 @@
1
+ // packages/image-cropper/src/provider/use-image-cropper.ts
2
+ import { dequal } from "dequal";
3
+ import { useMemo, useState, useCallback } from "@wordpress/element";
4
+ import { MIN_ZOOM } from "../constants";
5
+ import {
6
+ getCroppedImage as getCroppedImageUtil,
7
+ normalizeRotation
8
+ } from "../utils";
9
+ var DEFAULT_INITIAL_STATE = {
10
+ crop: {
11
+ x: 0,
12
+ y: 0,
13
+ width: 100,
14
+ height: 100
15
+ },
16
+ zoom: MIN_ZOOM,
17
+ rotation: 0,
18
+ aspectRatio: 1,
19
+ flip: {
20
+ horizontal: false,
21
+ vertical: false
22
+ }
23
+ };
24
+ var DEFAULT_CROP_MEDIA_POSITION = {
25
+ x: 0,
26
+ y: 0
27
+ };
28
+ var DEFAULT_CROPPER_STATE = {
29
+ crop: DEFAULT_CROP_MEDIA_POSITION,
30
+ croppedArea: DEFAULT_INITIAL_STATE.crop,
31
+ croppedAreaPixels: null,
32
+ zoom: DEFAULT_INITIAL_STATE.zoom,
33
+ rotation: DEFAULT_INITIAL_STATE.rotation,
34
+ flip: DEFAULT_INITIAL_STATE.flip,
35
+ aspectRatio: DEFAULT_INITIAL_STATE.aspectRatio,
36
+ mediaSize: null
37
+ };
38
+ function useCropper() {
39
+ const [cropperState, setInternalCropperState] = useState(
40
+ DEFAULT_CROPPER_STATE
41
+ );
42
+ const [resetState, setInternalResetState] = useState(null);
43
+ const setCropperState = useCallback(
44
+ (newState) => {
45
+ setInternalCropperState((prev) => {
46
+ const updates = typeof newState === "function" ? newState(prev) : newState;
47
+ const normalizedUpdates = { ...updates };
48
+ if ("rotation" in normalizedUpdates && normalizedUpdates.rotation !== void 0) {
49
+ normalizedUpdates.rotation = normalizeRotation(
50
+ normalizedUpdates.rotation
51
+ );
52
+ }
53
+ return { ...prev, ...normalizedUpdates };
54
+ });
55
+ },
56
+ []
57
+ );
58
+ const setResetState = useCallback(
59
+ (newResetState = null) => {
60
+ if (!newResetState) {
61
+ setInternalResetState(null);
62
+ setCropperState(DEFAULT_CROPPER_STATE);
63
+ return;
64
+ }
65
+ if (typeof newResetState === "object") {
66
+ const initialState = {
67
+ ...DEFAULT_INITIAL_STATE,
68
+ ...newResetState
69
+ };
70
+ setInternalResetState(initialState);
71
+ setCropperState(initialState);
72
+ }
73
+ },
74
+ [setCropperState, setInternalResetState]
75
+ );
76
+ const reset = useCallback(() => {
77
+ if (resetState) {
78
+ const resetUpdates = {
79
+ // Reset media position to center
80
+ crop: { x: 0, y: 0 },
81
+ // Reset cropped area pixels (will be recalculated)
82
+ croppedAreaPixels: null
83
+ };
84
+ if (resetState.crop) {
85
+ resetUpdates.croppedArea = resetState.crop;
86
+ }
87
+ if (resetState.zoom !== void 0) {
88
+ resetUpdates.zoom = resetState.zoom;
89
+ }
90
+ if (resetState.rotation !== void 0) {
91
+ resetUpdates.rotation = resetState.rotation;
92
+ }
93
+ if (resetState.aspectRatio !== void 0) {
94
+ resetUpdates.aspectRatio = resetState.aspectRatio;
95
+ }
96
+ if (resetState.flip !== void 0) {
97
+ resetUpdates.flip = resetState.flip;
98
+ }
99
+ setCropperState(resetUpdates);
100
+ } else {
101
+ setCropperState({ ...DEFAULT_CROPPER_STATE });
102
+ }
103
+ }, [resetState, setCropperState]);
104
+ const isDirty = useMemo(() => {
105
+ if (resetState) {
106
+ const currentState2 = {
107
+ crop: cropperState.croppedAreaPixels || cropperState.croppedArea,
108
+ zoom: cropperState.zoom,
109
+ rotation: normalizeRotation(cropperState.rotation),
110
+ aspectRatio: cropperState.aspectRatio,
111
+ flip: cropperState.flip
112
+ };
113
+ return false === dequal(currentState2, resetState);
114
+ }
115
+ const currentState = {
116
+ crop: cropperState.croppedArea,
117
+ zoom: cropperState.zoom,
118
+ rotation: normalizeRotation(cropperState.rotation),
119
+ aspectRatio: cropperState.aspectRatio,
120
+ flip: cropperState.flip
121
+ };
122
+ return false === dequal(currentState, DEFAULT_INITIAL_STATE);
123
+ }, [cropperState, resetState]);
124
+ const getCroppedImage = useCallback(
125
+ async (src) => {
126
+ if (!cropperState.croppedAreaPixels) {
127
+ return null;
128
+ }
129
+ return getCroppedImageUtil(
130
+ src,
131
+ cropperState.croppedAreaPixels,
132
+ cropperState.rotation,
133
+ cropperState.flip
134
+ );
135
+ },
136
+ [
137
+ cropperState.croppedAreaPixels,
138
+ cropperState.rotation,
139
+ cropperState.flip
140
+ ]
141
+ );
142
+ return useMemo(
143
+ () => ({
144
+ cropperState,
145
+ setCropperState,
146
+ resetState,
147
+ setResetState,
148
+ isDirty,
149
+ reset,
150
+ getCroppedImage
151
+ }),
152
+ [
153
+ cropperState,
154
+ setCropperState,
155
+ resetState,
156
+ setResetState,
157
+ isDirty,
158
+ reset,
159
+ getCroppedImage
160
+ ]
161
+ );
162
+ }
163
+ export {
164
+ DEFAULT_INITIAL_STATE,
165
+ useCropper as default
166
+ };
167
+ //# sourceMappingURL=use-image-cropper.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/provider/use-image-cropper.ts"],
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport { dequal } from 'dequal';\n\n/**\n * WordPress dependencies\n */\nimport { useMemo, useState, useCallback } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport type { Point, ImageCropperState, CropperState } from '../types';\nimport { MIN_ZOOM } from '../constants';\nimport {\n\tgetCroppedImage as getCroppedImageUtil,\n\tnormalizeRotation,\n} from '../utils';\n\nexport const DEFAULT_INITIAL_STATE: Required< ImageCropperState > = {\n\tcrop: {\n\t\tx: 0,\n\t\ty: 0,\n\t\twidth: 100,\n\t\theight: 100,\n\t},\n\tzoom: MIN_ZOOM,\n\trotation: 0,\n\taspectRatio: 1,\n\tflip: {\n\t\thorizontal: false,\n\t\tvertical: false,\n\t},\n};\n\nconst DEFAULT_CROP_MEDIA_POSITION: Point = {\n\tx: 0,\n\ty: 0,\n};\n\nconst DEFAULT_CROPPER_STATE: CropperState = {\n\tcrop: DEFAULT_CROP_MEDIA_POSITION,\n\tcroppedArea: DEFAULT_INITIAL_STATE.crop,\n\tcroppedAreaPixels: null,\n\tzoom: DEFAULT_INITIAL_STATE.zoom,\n\trotation: DEFAULT_INITIAL_STATE.rotation,\n\tflip: DEFAULT_INITIAL_STATE.flip,\n\taspectRatio: DEFAULT_INITIAL_STATE.aspectRatio,\n\tmediaSize: null,\n};\n\nexport default function useCropper() {\n\tconst [ cropperState, setInternalCropperState ] = useState< CropperState >(\n\t\tDEFAULT_CROPPER_STATE\n\t);\n\tconst [ resetState, setInternalResetState ] =\n\t\tuseState< ImageCropperState | null >( null );\n\n\t// Unified setter that supports both partial updates and function updates\n\tconst setCropperState = useCallback(\n\t\t(\n\t\t\tnewState:\n\t\t\t\t| Partial< CropperState >\n\t\t\t\t| ( ( prev: CropperState ) => Partial< CropperState > )\n\t\t) => {\n\t\t\tsetInternalCropperState( ( prev ) => {\n\t\t\t\tconst updates =\n\t\t\t\t\ttypeof newState === 'function'\n\t\t\t\t\t\t? newState( prev )\n\t\t\t\t\t\t: newState;\n\n\t\t\t\t// Apply normalization to rotation if it's being updated\n\t\t\t\tconst normalizedUpdates = { ...updates };\n\t\t\t\tif (\n\t\t\t\t\t'rotation' in normalizedUpdates &&\n\t\t\t\t\tnormalizedUpdates.rotation !== undefined\n\t\t\t\t) {\n\t\t\t\t\tnormalizedUpdates.rotation = normalizeRotation(\n\t\t\t\t\t\tnormalizedUpdates.rotation\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn { ...prev, ...normalizedUpdates };\n\t\t\t} );\n\t\t},\n\t\t[]\n\t);\n\n\tconst setResetState = useCallback(\n\t\t( newResetState: Partial< ImageCropperState > | null = null ) => {\n\t\t\t// If null, wipe the reset state and reset the cropper state to the default state.\n\t\t\tif ( ! newResetState ) {\n\t\t\t\tsetInternalResetState( null );\n\t\t\t\tsetCropperState( DEFAULT_CROPPER_STATE );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( typeof newResetState === 'object' ) {\n\t\t\t\tconst initialState = {\n\t\t\t\t\t...DEFAULT_INITIAL_STATE,\n\t\t\t\t\t...newResetState,\n\t\t\t\t};\n\t\t\t\tsetInternalResetState( initialState );\n\t\t\t\tsetCropperState( initialState );\n\t\t\t}\n\t\t},\n\t\t[ setCropperState, setInternalResetState ]\n\t);\n\n\t/*\n\t * Resets the cropper state.\n\t */\n\tconst reset = useCallback( () => {\n\t\tif ( resetState ) {\n\t\t\t// Convert ImageCropperState to CropperState updates\n\t\t\tconst resetUpdates: Partial< CropperState > = {\n\t\t\t\t// Reset media position to center\n\t\t\t\tcrop: { x: 0, y: 0 },\n\t\t\t\t// Reset cropped area pixels (will be recalculated)\n\t\t\t\tcroppedAreaPixels: null,\n\t\t\t};\n\n\t\t\t// Set the cropped area from resetState (this is the target crop area)\n\t\t\tif ( resetState.crop ) {\n\t\t\t\tresetUpdates.croppedArea = resetState.crop;\n\t\t\t}\n\t\t\tif ( resetState.zoom !== undefined ) {\n\t\t\t\tresetUpdates.zoom = resetState.zoom;\n\t\t\t}\n\t\t\tif ( resetState.rotation !== undefined ) {\n\t\t\t\tresetUpdates.rotation = resetState.rotation;\n\t\t\t}\n\t\t\tif ( resetState.aspectRatio !== undefined ) {\n\t\t\t\tresetUpdates.aspectRatio = resetState.aspectRatio;\n\t\t\t}\n\t\t\tif ( resetState.flip !== undefined ) {\n\t\t\t\tresetUpdates.flip = resetState.flip;\n\t\t\t}\n\n\t\t\tsetCropperState( resetUpdates );\n\t\t} else {\n\t\t\tsetCropperState( { ...DEFAULT_CROPPER_STATE } );\n\t\t}\n\t}, [ resetState, setCropperState ] );\n\n\t/*\n\t * Returns true if the cropper state is dirty.\n\t * Compare against reset state if available, otherwise against default state.\n\t */\n\tconst isDirty = useMemo( () => {\n\t\tif ( resetState ) {\n\t\t\t// Compare the relevant cropper properties against reset state\n\t\t\tconst currentState = {\n\t\t\t\tcrop:\n\t\t\t\t\tcropperState.croppedAreaPixels || cropperState.croppedArea,\n\t\t\t\tzoom: cropperState.zoom,\n\t\t\t\trotation: normalizeRotation( cropperState.rotation ),\n\t\t\t\taspectRatio: cropperState.aspectRatio,\n\t\t\t\tflip: cropperState.flip,\n\t\t\t};\n\t\t\treturn false === dequal( currentState, resetState );\n\t\t}\n\n\t\t// Compare against default state using percentage values\n\t\tconst currentState = {\n\t\t\tcrop: cropperState.croppedArea,\n\t\t\tzoom: cropperState.zoom,\n\t\t\trotation: normalizeRotation( cropperState.rotation ),\n\t\t\taspectRatio: cropperState.aspectRatio,\n\t\t\tflip: cropperState.flip,\n\t\t};\n\t\treturn false === dequal( currentState, DEFAULT_INITIAL_STATE );\n\t}, [ cropperState, resetState ] );\n\n\t/**\n\t * Returns the cropped image.\n\t *\n\t * @param {string} src - The source of the image to crop.\n\t * @return {Promise<string | null>} A promise that resolves to the cropped image.\n\t */\n\tconst getCroppedImage = useCallback(\n\t\tasync ( src: string ) => {\n\t\t\tif ( ! cropperState.croppedAreaPixels ) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn getCroppedImageUtil(\n\t\t\t\tsrc,\n\t\t\t\tcropperState.croppedAreaPixels,\n\t\t\t\tcropperState.rotation,\n\t\t\t\tcropperState.flip\n\t\t\t);\n\t\t},\n\t\t[\n\t\t\tcropperState.croppedAreaPixels,\n\t\t\tcropperState.rotation,\n\t\t\tcropperState.flip,\n\t\t]\n\t);\n\n\treturn useMemo(\n\t\t() => ( {\n\t\t\tcropperState,\n\t\t\tsetCropperState,\n\t\t\tresetState,\n\t\t\tsetResetState,\n\t\t\tisDirty,\n\t\t\treset,\n\t\t\tgetCroppedImage,\n\t\t} ),\n\t\t[\n\t\t\tcropperState,\n\t\t\tsetCropperState,\n\t\t\tresetState,\n\t\t\tsetResetState,\n\t\t\tisDirty,\n\t\t\treset,\n\t\t\tgetCroppedImage,\n\t\t]\n\t);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["currentState"]
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,78 @@
1
+ // packages/image-cropper/src/utils.ts
2
+ var normalizeRotation = (rotation) => {
3
+ if (rotation >= 0) {
4
+ return rotation % 360;
5
+ }
6
+ return (360 + rotation % 360) % 360;
7
+ };
8
+ var createImage = (url) => new Promise((resolve, reject) => {
9
+ const image = new Image();
10
+ image.addEventListener("load", () => resolve(image));
11
+ image.addEventListener("error", (error) => reject(error));
12
+ image.setAttribute("crossOrigin", "anonymous");
13
+ image.src = url;
14
+ });
15
+ function getRadianAngle(degreeValue) {
16
+ return degreeValue * Math.PI / 180;
17
+ }
18
+ function rotateSize(width, height, rotation) {
19
+ const rotRad = getRadianAngle(rotation);
20
+ return {
21
+ width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
22
+ height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height)
23
+ };
24
+ }
25
+ async function getCroppedImage(imageSrc, pixelCrop, rotation = 0, flip = { horizontal: false, vertical: false }) {
26
+ try {
27
+ const image = await createImage(imageSrc);
28
+ const canvas = document.createElement("canvas");
29
+ const ctx = canvas.getContext("2d");
30
+ if (!ctx) {
31
+ return null;
32
+ }
33
+ const rotRad = getRadianAngle(rotation);
34
+ const { width: boundingBoxWidth, height: boundingBoxHeight } = rotateSize(image.width, image.height, rotation);
35
+ canvas.width = boundingBoxWidth;
36
+ canvas.height = boundingBoxHeight;
37
+ ctx.translate(boundingBoxWidth / 2, boundingBoxHeight / 2);
38
+ ctx.rotate(rotRad);
39
+ ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
40
+ ctx.translate(-image.width / 2, -image.height / 2);
41
+ ctx.drawImage(image, 0, 0);
42
+ const croppedCanvas = document.createElement("canvas");
43
+ const croppedCtx = croppedCanvas.getContext("2d");
44
+ if (!croppedCtx) {
45
+ return null;
46
+ }
47
+ croppedCanvas.width = pixelCrop.width;
48
+ croppedCanvas.height = pixelCrop.height;
49
+ croppedCtx.drawImage(
50
+ canvas,
51
+ pixelCrop.x,
52
+ pixelCrop.y,
53
+ pixelCrop.width,
54
+ pixelCrop.height,
55
+ 0,
56
+ 0,
57
+ pixelCrop.width,
58
+ pixelCrop.height
59
+ );
60
+ return new Promise((resolve) => {
61
+ croppedCanvas.toBlob((file) => {
62
+ if (file) {
63
+ resolve(URL.createObjectURL(file));
64
+ }
65
+ }, "image/jpeg");
66
+ });
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ export {
72
+ createImage,
73
+ getCroppedImage,
74
+ getRadianAngle,
75
+ normalizeRotation,
76
+ rotateSize
77
+ };
78
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/utils.ts"],
4
+ "sourcesContent": ["/**\n * Normalizes a rotation value to be within the range of 0-360 degrees.\n * So a rotation of -90 becomes 270, -45 becomes 315, etc.\n * This is required because the `/media/edit` calculates the rotation value\n * by subtracting the rotation value from 0. ImageMagick rotates clockwise only and will subtract the rotation value from 360.\n * It's utterly confusing, and probably in need of a refactor.\n *\n * @param {number} rotation - The rotation value to normalize.\n * @return {number} The normalized rotation value.\n */\nexport const normalizeRotation = ( rotation: number ) => {\n\tif ( rotation >= 0 ) {\n\t\treturn rotation % 360;\n\t}\n\n\t// For negative rotations, convert to positive clockwise equivalent\n\treturn ( 360 + ( rotation % 360 ) ) % 360;\n};\n\n/**\n * Creates an image from a URL.\n *\n * @param {string} url - The URL of the image to create.\n * @return {Promise<HTMLImageElement>} A promise that resolves to the image.\n */\nexport const createImage = ( url: string ) =>\n\tnew Promise( ( resolve, reject ) => {\n\t\tconst image = new Image();\n\t\timage.addEventListener( 'load', () => resolve( image ) );\n\t\timage.addEventListener( 'error', ( error ) => reject( error ) );\n\t\timage.setAttribute( 'crossOrigin', 'anonymous' );\n\t\timage.src = url;\n\t} );\n\n/**\n * Converts a degree value to a radian value.\n *\n * @param {number} degreeValue - The degree value to convert.\n * @return {number} The radian value.\n */\nexport function getRadianAngle( degreeValue: number ) {\n\treturn ( degreeValue * Math.PI ) / 180;\n}\n\n/**\n * Returns the new bounding area of a rotated rectangle.\n *\n * @param {number} width - The width of the rectangle.\n * @param {number} height - The height of the rectangle.\n * @param {number} rotation - The rotation of the rectangle.\n * @return {Object} The new bounding area of the rotated rectangle.\n */\nexport function rotateSize( width: number, height: number, rotation: number ) {\n\tconst rotRad = getRadianAngle( rotation );\n\n\treturn {\n\t\twidth:\n\t\t\tMath.abs( Math.cos( rotRad ) * width ) +\n\t\t\tMath.abs( Math.sin( rotRad ) * height ),\n\t\theight:\n\t\t\tMath.abs( Math.sin( rotRad ) * width ) +\n\t\t\tMath.abs( Math.cos( rotRad ) * height ),\n\t};\n}\n\n/**\n * Crops an image to a given area.\n *\n * @param {string} imageSrc - The source of the image to crop.\n * @param {Object} pixelCrop - The area to crop.\n * @param pixelCrop.x\n * @param pixelCrop.y\n * @param pixelCrop.width\n * @param pixelCrop.height\n * @param {number} rotation - The rotation of the image.\n * @param {Object} flip - The flip of the image.\n * @return {Promise<string | null>} A promise that resolves to the cropped image.\n */\nexport async function getCroppedImage(\n\timageSrc: string,\n\tpixelCrop: { x: number; y: number; width: number; height: number },\n\trotation = 0,\n\tflip = { horizontal: false, vertical: false }\n): Promise< string | null > {\n\ttry {\n\t\tconst image = ( await createImage( imageSrc ) ) as HTMLImageElement;\n\t\tconst canvas = document.createElement( 'canvas' );\n\t\tconst ctx = canvas.getContext( '2d' );\n\n\t\tif ( ! ctx ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Calculate the rotation angle in radians.\n\t\tconst rotRad = getRadianAngle( rotation );\n\n\t\tconst { width: boundingBoxWidth, height: boundingBoxHeight } =\n\t\t\trotateSize( image.width, image.height, rotation );\n\n\t\t// Set canvas size to match the bounding box.\n\t\tcanvas.width = boundingBoxWidth;\n\t\tcanvas.height = boundingBoxHeight;\n\n\t\t// Translate and draw canvas context to a central location to allow rotating and flipping around the center.\n\t\tctx.translate( boundingBoxWidth / 2, boundingBoxHeight / 2 );\n\t\tctx.rotate( rotRad );\n\t\tctx.scale( flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1 );\n\t\tctx.translate( -image.width / 2, -image.height / 2 );\n\t\tctx.drawImage( image, 0, 0 );\n\n\t\tconst croppedCanvas = document.createElement( 'canvas' );\n\t\tconst croppedCtx = croppedCanvas.getContext( '2d' );\n\n\t\tif ( ! croppedCtx ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Set the size of the cropped canvas.\n\t\tcroppedCanvas.width = pixelCrop.width;\n\t\tcroppedCanvas.height = pixelCrop.height;\n\n\t\t// Draw the cropped image onto the new canvas.\n\t\tcroppedCtx.drawImage(\n\t\t\tcanvas,\n\t\t\tpixelCrop.x,\n\t\t\tpixelCrop.y,\n\t\t\tpixelCrop.width,\n\t\t\tpixelCrop.height,\n\t\t\t0,\n\t\t\t0,\n\t\t\tpixelCrop.width,\n\t\t\tpixelCrop.height\n\t\t);\n\n\t\t// Return as a blob.\n\t\treturn new Promise( ( resolve ) => {\n\t\t\tcroppedCanvas.toBlob( ( file ) => {\n\t\t\t\tif ( file ) {\n\t\t\t\t\tresolve( URL.createObjectURL( file ) );\n\t\t\t\t}\n\t\t\t}, 'image/jpeg' );\n\t\t} );\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],
5
+ "mappings": ";AAUO,IAAM,oBAAoB,CAAE,aAAsB;AACxD,MAAK,YAAY,GAAI;AACpB,WAAO,WAAW;AAAA,EACnB;AAGA,UAAS,MAAQ,WAAW,OAAU;AACvC;AAQO,IAAM,cAAc,CAAE,QAC5B,IAAI,QAAS,CAAE,SAAS,WAAY;AACnC,QAAM,QAAQ,IAAI,MAAM;AACxB,QAAM,iBAAkB,QAAQ,MAAM,QAAS,KAAM,CAAE;AACvD,QAAM,iBAAkB,SAAS,CAAE,UAAW,OAAQ,KAAM,CAAE;AAC9D,QAAM,aAAc,eAAe,WAAY;AAC/C,QAAM,MAAM;AACb,CAAE;AAQI,SAAS,eAAgB,aAAsB;AACrD,SAAS,cAAc,KAAK,KAAO;AACpC;AAUO,SAAS,WAAY,OAAe,QAAgB,UAAmB;AAC7E,QAAM,SAAS,eAAgB,QAAS;AAExC,SAAO;AAAA,IACN,OACC,KAAK,IAAK,KAAK,IAAK,MAAO,IAAI,KAAM,IACrC,KAAK,IAAK,KAAK,IAAK,MAAO,IAAI,MAAO;AAAA,IACvC,QACC,KAAK,IAAK,KAAK,IAAK,MAAO,IAAI,KAAM,IACrC,KAAK,IAAK,KAAK,IAAK,MAAO,IAAI,MAAO;AAAA,EACxC;AACD;AAeA,eAAsB,gBACrB,UACA,WACA,WAAW,GACX,OAAO,EAAE,YAAY,OAAO,UAAU,MAAM,GACjB;AAC3B,MAAI;AACH,UAAM,QAAU,MAAM,YAAa,QAAS;AAC5C,UAAM,SAAS,SAAS,cAAe,QAAS;AAChD,UAAM,MAAM,OAAO,WAAY,IAAK;AAEpC,QAAK,CAAE,KAAM;AACZ,aAAO;AAAA,IACR;AAGA,UAAM,SAAS,eAAgB,QAAS;AAExC,UAAM,EAAE,OAAO,kBAAkB,QAAQ,kBAAkB,IAC1D,WAAY,MAAM,OAAO,MAAM,QAAQ,QAAS;AAGjD,WAAO,QAAQ;AACf,WAAO,SAAS;AAGhB,QAAI,UAAW,mBAAmB,GAAG,oBAAoB,CAAE;AAC3D,QAAI,OAAQ,MAAO;AACnB,QAAI,MAAO,KAAK,aAAa,KAAK,GAAG,KAAK,WAAW,KAAK,CAAE;AAC5D,QAAI,UAAW,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAM,SAAS,CAAE;AACnD,QAAI,UAAW,OAAO,GAAG,CAAE;AAE3B,UAAM,gBAAgB,SAAS,cAAe,QAAS;AACvD,UAAM,aAAa,cAAc,WAAY,IAAK;AAElD,QAAK,CAAE,YAAa;AACnB,aAAO;AAAA,IACR;AAGA,kBAAc,QAAQ,UAAU;AAChC,kBAAc,SAAS,UAAU;AAGjC,eAAW;AAAA,MACV;AAAA,MACA,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,UAAU;AAAA,IACX;AAGA,WAAO,IAAI,QAAS,CAAE,YAAa;AAClC,oBAAc,OAAQ,CAAE,SAAU;AACjC,YAAK,MAAO;AACX,kBAAS,IAAI,gBAAiB,IAAK,CAAE;AAAA,QACtC;AAAA,MACD,GAAG,YAAa;AAAA,IACjB,CAAE;AAAA,EACH,QAAQ;AACP,WAAO;AAAA,EACR;AACD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { ImageCropperProps } from '../../types';
2
+ export default function ImageCropper({ src, onLoad, minZoom, maxZoom, ...props }: ImageCropperProps): import("react").JSX.Element;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/image-cropper/index.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAQ,iBAAiB,EAAoB,MAAM,aAAa,CAAC;AAG7E,MAAM,CAAC,OAAO,UAAU,YAAY,CAAE,EACrC,GAAG,EACH,MAAM,EACN,OAAkB,EAClB,OAAkB,EAClB,GAAG,KAAK,EACR,EAAE,iBAAiB,+BA4DnB"}
@@ -0,0 +1,4 @@
1
+ export declare const MIN_ZOOM = 1;
2
+ export declare const MAX_ZOOM = 5;
3
+ export declare const DEFAULT_ASPECT_RATIO_SLUG = "original";
4
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,yBAAyB,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { default as ImageCropper } from './components/image-cropper';
2
+ export { useImageCropper, default as ImageCropperProvider } from './provider';
3
+ export * from './types';
4
+ export { normalizeRotation } from './utils';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC9E,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ImageCropperContextValue } from '../types';
2
+ export declare const ImageCropperContext: import("react").Context<ImageCropperContextValue>;
3
+ export default function ImageCropperProvider({ children, }: {
4
+ children: React.ReactNode;
5
+ }): import("react").JSX.Element;
6
+ export declare const useImageCropper: () => ImageCropperContextValue;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAGzD,eAAO,MAAM,mBAAmB,mDAiB7B,CAAC;AAEJ,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAE,EAC7C,QAAQ,GACR,EAAE;IACF,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B,+BAaA;AAED,eAAO,MAAM,eAAe,gCAM3B,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import type { ImageCropperState, CropperState } from '../types';
5
+ export declare const DEFAULT_INITIAL_STATE: Required<ImageCropperState>;
6
+ export default function useCropper(): {
7
+ cropperState: CropperState;
8
+ setCropperState: (newState: Partial<CropperState> | ((prev: CropperState) => Partial<CropperState>)) => void;
9
+ resetState: ImageCropperState | null;
10
+ setResetState: (newResetState?: Partial<ImageCropperState> | null) => void;
11
+ isDirty: boolean;
12
+ reset: () => void;
13
+ getCroppedImage: (src: string) => Promise<string | null>;
14
+ };
15
+ //# sourceMappingURL=use-image-cropper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-image-cropper.d.ts","sourceRoot":"","sources":["../../src/provider/use-image-cropper.ts"],"names":[],"mappings":"AAUA;;GAEG;AACH,OAAO,KAAK,EAAS,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAOvE,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAE,iBAAiB,CAc9D,CAAC;AAkBF,MAAM,CAAC,OAAO,UAAU,UAAU;;gCAW5B,OAAO,CAAE,YAAY,CAAE,GACvB,CAAE,CAAE,IAAI,EAAE,YAAY,KAAM,OAAO,CAAE,YAAY,CAAE,CAAE;;oCA0BxC,OAAO,CAAE,iBAAiB,CAAE,GAAG,IAAI;;;2BA2FvC,MAAM;EAsCpB"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import ImageCropper from '../components/image-cropper';
5
+ import type { ImageCropperProps } from '../types';
6
+ import './style.css';
7
+ declare const _default: {
8
+ title: string;
9
+ component: typeof ImageCropper;
10
+ };
11
+ export default _default;
12
+ export declare const Default: {
13
+ render: (args: ImageCropperProps) => import("react").JSX.Element;
14
+ args: {
15
+ src: string;
16
+ minZoom: number;
17
+ maxZoom: number;
18
+ };
19
+ };
20
+ export declare const WithControls: {
21
+ render: (args: ImageCropperProps) => import("react").JSX.Element;
22
+ args: {
23
+ src: string;
24
+ minZoom: number;
25
+ maxZoom: number;
26
+ };
27
+ };
28
+ //# sourceMappingURL=index.story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.story.d.ts","sourceRoot":"","sources":["../../src/stories/index.story.tsx"],"names":[],"mappings":"AAYA;;GAEG;AACH,OAAO,YAAY,MAAM,6BAA6B,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAa,MAAM,UAAU,CAAC;AAE7D,OAAO,aAAa,CAAC;;;;;AAErB,wBAGE;AAcF,eAAO,MAAM,OAAO;mBAZa,iBAAiB;;;;;;CAmBjD,CAAC;AAmLF,eAAO,MAAM,YAAY;mBAjLa,iBAAiB;;;;;;CAwLtD,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { Point, Area, MediaSize } from 'react-easy-crop';
5
+ export type { Point, Area, MediaSize };
6
+ export type Flip = {
7
+ horizontal: boolean;
8
+ vertical: boolean;
9
+ };
10
+ export interface ImageCropperState {
11
+ rotation?: number;
12
+ crop?: Area;
13
+ zoom?: number;
14
+ flip?: Flip;
15
+ aspectRatio?: number;
16
+ }
17
+ export interface CropperState {
18
+ crop: Point;
19
+ croppedArea?: Area;
20
+ croppedAreaPixels?: Area | null;
21
+ zoom: number;
22
+ rotation: number;
23
+ flip: Flip;
24
+ aspectRatio: number;
25
+ mediaSize: MediaSize | null;
26
+ }
27
+ export interface ImageCropperProps {
28
+ src: string;
29
+ onLoad?: (mediaSize: MediaSize) => void;
30
+ minZoom?: number;
31
+ maxZoom?: number;
32
+ }
33
+ export interface ImageCropperContextValue {
34
+ cropperState: CropperState;
35
+ setCropperState: (newState: Partial<CropperState> | ((prev: CropperState) => Partial<CropperState>)) => void;
36
+ resetState: ImageCropperState | null;
37
+ setResetState: (newResetState: Partial<ImageCropperState> | null) => void;
38
+ isDirty: boolean;
39
+ reset: () => void;
40
+ getCroppedImage: (src: string) => Promise<string | null>;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG9D,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAEvC,MAAM,MAAM,IAAI,GAAG;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,CAAE,SAAS,EAAE,SAAS,KAAM,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACxC,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,CAChB,QAAQ,EACL,OAAO,CAAE,YAAY,CAAE,GACvB,CAAE,CAAE,IAAI,EAAE,YAAY,KAAM,OAAO,CAAE,YAAY,CAAE,CAAE,KACpD,IAAI,CAAC;IACV,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,CACd,aAAa,EAAE,OAAO,CAAE,iBAAiB,CAAE,GAAG,IAAI,KAC9C,IAAI,CAAC;IACV,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,eAAe,EAAE,CAAE,GAAG,EAAE,MAAM,KAAM,OAAO,CAAE,MAAM,GAAG,IAAI,CAAE,CAAC;CAC7D"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Normalizes a rotation value to be within the range of 0-360 degrees.
3
+ * So a rotation of -90 becomes 270, -45 becomes 315, etc.
4
+ * This is required because the `/media/edit` calculates the rotation value
5
+ * by subtracting the rotation value from 0. ImageMagick rotates clockwise only and will subtract the rotation value from 360.
6
+ * It's utterly confusing, and probably in need of a refactor.
7
+ *
8
+ * @param {number} rotation - The rotation value to normalize.
9
+ * @return {number} The normalized rotation value.
10
+ */
11
+ export declare const normalizeRotation: (rotation: number) => number;
12
+ /**
13
+ * Creates an image from a URL.
14
+ *
15
+ * @param {string} url - The URL of the image to create.
16
+ * @return {Promise<HTMLImageElement>} A promise that resolves to the image.
17
+ */
18
+ export declare const createImage: (url: string) => Promise<unknown>;
19
+ /**
20
+ * Converts a degree value to a radian value.
21
+ *
22
+ * @param {number} degreeValue - The degree value to convert.
23
+ * @return {number} The radian value.
24
+ */
25
+ export declare function getRadianAngle(degreeValue: number): number;
26
+ /**
27
+ * Returns the new bounding area of a rotated rectangle.
28
+ *
29
+ * @param {number} width - The width of the rectangle.
30
+ * @param {number} height - The height of the rectangle.
31
+ * @param {number} rotation - The rotation of the rectangle.
32
+ * @return {Object} The new bounding area of the rotated rectangle.
33
+ */
34
+ export declare function rotateSize(width: number, height: number, rotation: number): {
35
+ width: number;
36
+ height: number;
37
+ };
38
+ /**
39
+ * Crops an image to a given area.
40
+ *
41
+ * @param {string} imageSrc - The source of the image to crop.
42
+ * @param {Object} pixelCrop - The area to crop.
43
+ * @param pixelCrop.x
44
+ * @param pixelCrop.y
45
+ * @param pixelCrop.width
46
+ * @param pixelCrop.height
47
+ * @param {number} rotation - The rotation of the image.
48
+ * @param {Object} flip - The flip of the image.
49
+ * @return {Promise<string | null>} A promise that resolves to the cropped image.
50
+ */
51
+ export declare function getCroppedImage(imageSrc: string, pixelCrop: {
52
+ x: number;
53
+ y: number;
54
+ width: number;
55
+ height: number;
56
+ }, rotation?: number, flip?: {
57
+ horizontal: boolean;
58
+ vertical: boolean;
59
+ }): Promise<string | null>;
60
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAK,UAAU,MAAM,WAOlD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAK,KAAK,MAAM,qBAOpC,CAAC;AAEL;;;;;GAKG;AACH,wBAAgB,cAAc,CAAE,WAAW,EAAE,MAAM,UAElD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;EAW1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAClE,QAAQ,SAAI,EACZ,IAAI;;;CAAyC,GAC3C,OAAO,CAAE,MAAM,GAAG,IAAI,CAAE,CA8D1B"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@wordpress/image-cropper",
3
+ "version": "1.0.1-next.6deb34194.0",
4
+ "description": "A basic image cropper component.",
5
+ "author": "The WordPress Contributors",
6
+ "license": "GPL-2.0-or-later",
7
+ "keywords": [
8
+ "wordpress",
9
+ "gutenberg",
10
+ "image cropper",
11
+ "cropper"
12
+ ],
13
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/image-cropper/README.md",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/WordPress/gutenberg.git",
17
+ "directory": "packages/image-cropper"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/WordPress/gutenberg/issues"
21
+ },
22
+ "engines": {
23
+ "node": ">=18.12.0",
24
+ "npm": ">=8.19.2"
25
+ },
26
+ "main": "build/index.js",
27
+ "module": "build-module/index.js",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./build-types/index.d.ts",
31
+ "import": "./build-module/index.js",
32
+ "require": "./build/index.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "react-native": "src/index",
37
+ "types": "build-types",
38
+ "sideEffects": false,
39
+ "dependencies": {
40
+ "@wordpress/element": "^6.36.1-next.6deb34194.0",
41
+ "clsx": "^2.1.1",
42
+ "dequal": "^2.0.3",
43
+ "react-easy-crop": "^5.4.2"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "gitHead": "457096e9e9b3896d2d4fe54fc19d7fb571ee9a44"
49
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import Cropper from 'react-easy-crop';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useCallback } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { useImageCropper } from '../../provider';
15
+ import type { Area, ImageCropperProps, MediaSize, Point } from '../../types';
16
+ import { MIN_ZOOM, MAX_ZOOM } from '../../constants';
17
+
18
+ export default function ImageCropper( {
19
+ src,
20
+ onLoad,
21
+ minZoom = MIN_ZOOM,
22
+ maxZoom = MAX_ZOOM,
23
+ ...props
24
+ }: ImageCropperProps ) {
25
+ const { cropperState, setCropperState } = useImageCropper();
26
+ const { crop, zoom, rotation, aspectRatio, flip } = cropperState;
27
+
28
+ const setCrop = ( newCrop: Point ) => setCropperState( { crop: newCrop } );
29
+ const setZoom = ( newZoom: number ) => setCropperState( { zoom: newZoom } );
30
+ const setRotation = ( newRotation: number ) =>
31
+ setCropperState( { rotation: newRotation } );
32
+ const setMediaSize = ( newMediaSize: MediaSize ) =>
33
+ setCropperState( { mediaSize: newMediaSize } );
34
+
35
+ /**
36
+ * Handles the crop complete event, when a user stops interacting with the cropper.
37
+ * Updates the cropped area in percentages and pixels.
38
+ *
39
+ * @param {Area} areaPercentage - The area percentage.
40
+ * @param {Area} areaPixels - The area pixels.
41
+ */
42
+ const onCropComplete = useCallback(
43
+ ( areaPercentage: Area, areaPixels: Area ) => {
44
+ setCropperState( {
45
+ croppedArea: areaPercentage,
46
+ croppedAreaPixels: areaPixels,
47
+ } );
48
+ },
49
+ [ setCropperState ]
50
+ );
51
+
52
+ return (
53
+ <Cropper
54
+ classes={ {
55
+ containerClassName: 'image-cropper__container',
56
+ cropAreaClassName: 'image-cropper__crop-area',
57
+ mediaClassName: 'image-cropper__image',
58
+ } }
59
+ minZoom={ minZoom }
60
+ maxZoom={ maxZoom }
61
+ rotation={ rotation }
62
+ image={ src }
63
+ setMediaSize={ setMediaSize }
64
+ crop={ crop }
65
+ zoom={ zoom }
66
+ aspect={ aspectRatio }
67
+ onCropChange={ setCrop }
68
+ onZoomChange={ setZoom }
69
+ onCropComplete={ onCropComplete }
70
+ onMediaLoaded={ ( loadedMediaSize: MediaSize ) => {
71
+ onLoad?.( loadedMediaSize );
72
+ } }
73
+ onRotationChange={ setRotation }
74
+ transform={ [
75
+ `translate(${ crop.x }px, ${ crop.y }px)`,
76
+ `rotateZ(${ rotation }deg)`,
77
+ `rotateY(${ flip.horizontal ? 180 : 0 }deg)`,
78
+ `rotateX(${ flip.vertical ? 180 : 0 }deg)`,
79
+ `scale(${ zoom })`,
80
+ ].join( ' ' ) }
81
+ { ...props }
82
+ />
83
+ );
84
+ }
@@ -0,0 +1,3 @@
1
+ export const MIN_ZOOM = 1;
2
+ export const MAX_ZOOM = 5;
3
+ export const DEFAULT_ASPECT_RATIO_SLUG = 'original';
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { default as ImageCropper } from './components/image-cropper';
2
+ export { useImageCropper, default as ImageCropperProvider } from './provider';
3
+ export * from './types';
4
+ export { normalizeRotation } from './utils';