@molecule/app-image-crop 1.0.0 → 1.0.2

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 (2) hide show
  1. package/README.md +312 -0
  2. package/package.json +12 -5
package/README.md ADDED
@@ -0,0 +1,312 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:51:05.136Z
7
+ -->
8
+
9
+ # @molecule/app-image-crop
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ Image crop core interface for molecule.dev.
16
+
17
+ Framework-agnostic contract for image cropping: crop-region **state**
18
+ (rect, rotation, zoom) plus cropped-canvas output. Bond a provider (e.g.
19
+ `@molecule/app-image-crop-cropperjs`) to supply the crop math; your UI
20
+ renders the preview and drag handles and feeds gestures into the instance.
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { setProvider, requireProvider } from '@molecule/app-image-crop'
26
+ import { provider } from '@molecule/app-image-crop-cropperjs'
27
+
28
+ setProvider(provider) // once, at app startup (bonds.ts)
29
+
30
+ const cropper = requireProvider().createCropper({
31
+ src: '/photos/avatar.jpg',
32
+ aspectRatio: 1,
33
+ circular: true,
34
+ })
35
+ const canvas = cropper.getCroppedCanvas({ width: 200, height: 200 })
36
+ canvas.toBlob((blob) => uploadAvatar(blob))
37
+ ```
38
+
39
+ ## Type
40
+
41
+ `core`
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ npm install @molecule/app-image-crop @molecule/app-bond
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### Interfaces
52
+
53
+ #### `CropData`
54
+
55
+ Crop region data describing the selected area and transformations.
56
+
57
+ ```typescript
58
+ interface CropData {
59
+ /** X coordinate of the crop area origin. */
60
+ x: number
61
+
62
+ /** Y coordinate of the crop area origin. */
63
+ y: number
64
+
65
+ /** Width of the crop area. */
66
+ width: number
67
+
68
+ /** Height of the crop area. */
69
+ height: number
70
+
71
+ /** Rotation angle in degrees. */
72
+ rotate: number
73
+
74
+ /** Horizontal scale factor. */
75
+ scaleX: number
76
+
77
+ /** Vertical scale factor. */
78
+ scaleY: number
79
+ }
80
+ ```
81
+
82
+ #### `CropperInstance`
83
+
84
+ A live cropper instance returned by the provider.
85
+
86
+ ```typescript
87
+ interface CropperInstance {
88
+ /**
89
+ * Generates a canvas element containing the cropped image.
90
+ *
91
+ * @param options - Optional output configuration.
92
+ * @returns An HTMLCanvasElement with the cropped result.
93
+ */
94
+ getCroppedCanvas(options?: OutputOptions): HTMLCanvasElement
95
+
96
+ /**
97
+ * Returns the current crop region data.
98
+ *
99
+ * @returns The current crop data.
100
+ */
101
+ getCropData(): CropData
102
+
103
+ /**
104
+ * Sets the crop region programmatically.
105
+ *
106
+ * @param data - The crop data to apply.
107
+ */
108
+ setCropData(data: CropData): void
109
+
110
+ /**
111
+ * Resets the cropper to its initial state.
112
+ */
113
+ reset(): void
114
+
115
+ /**
116
+ * Rotates the image by the specified degrees.
117
+ *
118
+ * @param degrees - Rotation angle in degrees (positive = clockwise).
119
+ */
120
+ rotate(degrees: number): void
121
+
122
+ /**
123
+ * Zooms the image by the specified ratio.
124
+ *
125
+ * @param ratio - Zoom ratio (positive to zoom in, negative to zoom out).
126
+ */
127
+ zoom(ratio: number): void
128
+
129
+ /**
130
+ * Destroys the cropper instance and cleans up resources.
131
+ */
132
+ destroy(): void
133
+ }
134
+ ```
135
+
136
+ #### `CropperOptions`
137
+
138
+ Configuration options for creating an image cropper.
139
+
140
+ ```typescript
141
+ interface CropperOptions {
142
+ /** Source image URL or data URI. */
143
+ src: string
144
+
145
+ /** Fixed aspect ratio (width / height). `undefined` for free-form. */
146
+ aspectRatio?: number
147
+
148
+ /** Minimum crop width in pixels. */
149
+ minWidth?: number
150
+
151
+ /** Minimum crop height in pixels. */
152
+ minHeight?: number
153
+
154
+ /** Maximum crop width in pixels. */
155
+ maxWidth?: number
156
+
157
+ /** Maximum crop height in pixels. */
158
+ maxHeight?: number
159
+
160
+ /** Whether the crop area should be circular. Defaults to `false`. */
161
+ circular?: boolean
162
+
163
+ /** Whether to show crop guide lines. Defaults to `true`. */
164
+ guides?: boolean
165
+ }
166
+ ```
167
+
168
+ #### `ImageCropProvider`
169
+
170
+ Image crop provider interface.
171
+
172
+ All image crop providers must implement this interface to create
173
+ and manage image cropping UI.
174
+
175
+ ```typescript
176
+ interface ImageCropProvider {
177
+ /** Provider name identifier. */
178
+ readonly name: string
179
+
180
+ /**
181
+ * Creates a new cropper instance.
182
+ *
183
+ * @param options - Configuration for the cropper.
184
+ * @returns A cropper instance for managing the crop operation.
185
+ */
186
+ createCropper(options: CropperOptions): CropperInstance
187
+ }
188
+ ```
189
+
190
+ #### `OutputOptions`
191
+
192
+ Output options for generating the cropped image.
193
+
194
+ ```typescript
195
+ interface OutputOptions {
196
+ /** Output width in pixels. */
197
+ width?: number
198
+
199
+ /** Output height in pixels. */
200
+ height?: number
201
+
202
+ /** Fill color for empty areas (e.g. after rotation). Defaults to `'transparent'`. */
203
+ fillColor?: string
204
+
205
+ /** Image quality for lossy formats (0-1). Defaults to `1`. */
206
+ quality?: number
207
+ }
208
+ ```
209
+
210
+ ### Functions
211
+
212
+ #### `getProvider()`
213
+
214
+ Retrieves the bonded image crop provider, or `null` if none is bonded.
215
+
216
+ ```typescript
217
+ function getProvider(): ImageCropProvider | null
218
+ ```
219
+
220
+ **Returns:** The active image crop provider, or `null`.
221
+
222
+ #### `hasProvider()`
223
+
224
+ Checks whether an image crop provider has been bonded.
225
+
226
+ ```typescript
227
+ function hasProvider(): boolean
228
+ ```
229
+
230
+ **Returns:** `true` if an image crop provider is available.
231
+
232
+ #### `requireProvider()`
233
+
234
+ Retrieves the bonded image crop provider, throwing if none is configured.
235
+
236
+ ```typescript
237
+ function requireProvider(): ImageCropProvider
238
+ ```
239
+
240
+ **Returns:** The active image crop provider.
241
+
242
+ #### `setProvider(provider)`
243
+
244
+ Registers an image crop provider as the active singleton.
245
+
246
+ ```typescript
247
+ function setProvider(provider: ImageCropProvider): void
248
+ ```
249
+
250
+ - `provider` — The image crop provider implementation to bond.
251
+
252
+ ## Available Providers
253
+
254
+ | Provider | Package |
255
+ | ---------- | ------------------------------------ |
256
+ | Image Crop | `@molecule/app-image-crop-cropperjs` |
257
+
258
+ ## Injection Notes
259
+
260
+ ### Requirements
261
+
262
+ Peer dependencies:
263
+
264
+ - `@molecule/app-bond` ^1.0.1
265
+
266
+ ### Runtime Dependencies
267
+
268
+ - `@molecule/app-bond`
269
+
270
+ - **The instance is headless — no crop UI mounts anywhere.** Render the image,
271
+ crop-box overlay, and handles yourself (styled via `getClassMap()`/`cm.*`) and
272
+ translate drag/resize gestures into `setCropData()` / `rotate()` / `zoom()`;
273
+ read `getCropData()` to draw the overlay.
274
+ - **Verify the cropped output end-to-end before shipping** — draw the returned
275
+ canvas into the page or upload-and-render it; an empty/blank result means the
276
+ bonded provider does not implement canvas output, which is an integration bug
277
+ to fix, not to skip.
278
+ - **Wire with THIS package's `setProvider()` or `bond('image-crop', …)`** —
279
+ `setProvider()` delegates into the shared `@molecule/app-bond` registry, so both
280
+ write the same slot; `requireProvider()` throws until one has run.
281
+ - Upload the result as a Blob (`canvas.toBlob`) through your upload path; the
282
+ server must re-validate the file (type/size) — client cropping is UX, not a
283
+ boundary.
284
+
285
+ ## E2E Tests
286
+
287
+ Integration checklist — drive the real UI (live preview, no mocks), adapt each
288
+ item to this app's actual crop screen (avatar/photo upload, etc.), and check
289
+ every box off one by one. A box you can't check is an integration bug to fix —
290
+ not a skip. The cropper is headless, so verify the UI YOU rendered:
291
+
292
+ - [ ] Opening the crop screen loads the source image (`createCropper({ src })`)
293
+ and renders it with a visible crop-box overlay + drag handles drawn from
294
+ `getCropData()` — not a blank or full-bleed image with no selection.
295
+ - [ ] Dragging/resizing the crop box feeds the gesture into `setCropData()`,
296
+ `getCropData()` reflects the new x/y/width/height, and the on-page preview
297
+ (the `getCroppedCanvas()` output drawn into the page) updates to show ONLY
298
+ the selected area, not the whole image.
299
+ - [ ] With an aspect-ratio lock (e.g. `aspectRatio: 1` for an avatar) the crop
300
+ box stays that ratio while you resize — `getCropData()` width == height for
301
+ 1:1 — and `circular: true` clips the preview to a circle.
302
+ - [ ] `rotate()` / `zoom()` transform the source and the crop overlay follows:
303
+ `getCropData().rotate` / `scaleX` change and the preview re-renders the
304
+ transformed region — the selection isn't stranded on the old orientation.
305
+ - [ ] Applying the crop OUTPUTS the cropped image: `getCroppedCanvas()` pixels
306
+ match the selected region (not the full source), and downstream the SAVED
307
+ file is the cropped Blob (`canvas.toBlob` → upload) — re-fetch and render the
308
+ stored image and confirm it shows the crop, never the original.
309
+ - [ ] Min/max crop size is enforced — you cannot drag the box smaller than
310
+ `minWidth`/`minHeight` or larger than `maxWidth`/`maxHeight`.
311
+ - [ ] Cancel/close discards without mutating the source: the original image is
312
+ unchanged and no cropped result is saved.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@molecule/app-image-crop",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
+ "homepage": "https://www.molecule.dev/packages/app-image-crop",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
@@ -17,7 +18,8 @@
17
18
  }
18
19
  },
19
20
  "files": [
20
- "dist"
21
+ "dist",
22
+ "README.md"
21
23
  ],
22
24
  "keywords": [
23
25
  "molecule",
@@ -25,13 +27,18 @@
25
27
  "crop"
26
28
  ],
27
29
  "license": "Apache-2.0",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/molecule-dev/molecule.git",
33
+ "directory": "packages/app/core/image-crop"
34
+ },
28
35
  "peerDependencies": {
29
- "@molecule/app-bond": "^1.0.0"
36
+ "@molecule/app-bond": "^1.0.1"
30
37
  },
31
38
  "devDependencies": {
32
- "@molecule/app-bond": "1.0.0",
39
+ "@molecule/app-bond": "1.0.2",
33
40
  "@types/node": "26.1.2",
34
41
  "typescript": "6.0.3",
35
- "vitest": "4.1.10"
42
+ "vitest": "4.1.11"
36
43
  }
37
44
  }