{"_id":"@all-the.rest/optimus-image-cropper","_rev":"3-38c3a1b65019a7fd896f4d4b43e54e09","name":"@all-the.rest/optimus-image-cropper","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@all-the.rest/optimus-image-cropper","version":"0.1.0","keywords":["angular","optimus-ui","image-cropper","cropper","standalone","signals"],"author":{"name":"Florian Reisinger"},"license":"MIT","_id":"@all-the.rest/optimus-image-cropper@0.1.0","maintainers":[{"name":"florian.reisinger.at","email":"florianreisingerat@gmail.com"}],"homepage":"https://optimus-image-cropper.all-the.rest/","bugs":{"url":"https://github.com/reisi007/optimus-image-cropper/issues"},"dist":{"shasum":"34deebc2f5f7676075a54a71da720f3128a1fc3e","tarball":"https://registry.npmjs.org/@all-the.rest/optimus-image-cropper/-/optimus-image-cropper-0.1.0.tgz","fileCount":6,"integrity":"sha512-hD07BhgcarqGESLmqjNxtBhMVNNvfCkA3G7vdWTVU42BPZoBJmFZTOvE0lLRaXV4FYHjCRqlmCCLtvCCRyZbYw==","signatures":[{"sig":"MEUCIQDVJK4iy7/ccUzwlZo2O7OtMuKP3hQ9q007Zrd6F5tyyQIgY+sU3pa8JCcul8vba+jRfZoeo+Ig1CJwUeeqJKfzPws=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":222141},"type":"module","module":"fesm2022/all-the.rest-optimus-image-cropper.mjs","exports":{".":{"types":"./types/all-the.rest-optimus-image-cropper.d.ts","default":"./fesm2022/all-the.rest-optimus-image-cropper.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"404f38483496ed5e1aefb3d43fa492f827d1dd1c","typings":"types/all-the.rest-optimus-image-cropper.d.ts","_npmUser":{"name":"florian.reisinger.at","email":"florianreisingerat@gmail.com"},"repository":{"url":"git+https://github.com/reisi007/optimus-image-cropper.git","type":"git","directory":"packages/optimus-image-cropper"},"_npmVersion":"11.17.0","description":"Optimus UI Image Cropper — Canvas-based image cropping component for Angular","directories":{},"sideEffects":false,"_nodeVersion":"26.5.0","dependencies":{"tslib":"^2.8.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"peerDependencies":{"primelocale":"^2.4.0","@angular/core":"^21.0.0","@angular/forms":"^21.0.0","@angular/common":"^21.0.0","@openng/optimus-ui":"^1.0.0-rc.1"},"peerDependenciesMeta":{"primelocale":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/optimus-image-cropper_0.1.0_1785167220401_0.07096870478938189","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@all-the.rest/optimus-image-cropper","version":"0.1.1","keywords":["angular","optimus-ui","image-cropper","cropper","standalone","signals"],"author":{"name":"Florian Reisinger"},"license":"MIT","_id":"@all-the.rest/optimus-image-cropper@0.1.1","maintainers":[{"name":"florian.reisinger.at","email":"florianreisingerat@gmail.com"}],"homepage":"https://optimus-image-cropper.all-the.rest/","bugs":{"url":"https://github.com/reisi007/optimus-image-cropper/issues"},"dist":{"shasum":"0346306c2832794cd25a35a9afe125de88eafcf8","tarball":"https://registry.npmjs.org/@all-the.rest/optimus-image-cropper/-/optimus-image-cropper-0.1.1.tgz","fileCount":6,"integrity":"sha512-xLtMjqaSjxRcjfDQM7D2cJMXQFqAvbpW27LrhvXgIZi56d8XQI/+HxG+6W9As0W6aObalSB3gnn1KFZ8Tok9VQ==","signatures":[{"sig":"MEUCIQDqZTU/jrgPiq58G2azFuRZCNJk5Lc9twRHyPBNU8wdVgIgFSU0pXIrnzY+UVvOzEXk66iGCQh2nMWvr3ydc/1CiYw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@all-the.rest%2foptimus-image-cropper@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":222724},"type":"module","module":"fesm2022/all-the.rest-optimus-image-cropper.mjs","exports":{".":{"types":"./types/all-the.rest-optimus-image-cropper.d.ts","default":"./fesm2022/all-the.rest-optimus-image-cropper.mjs"},"./package.json":{"default":"./package.json"}},"typings":"types/all-the.rest-optimus-image-cropper.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:90a26c22-37a9-4b81-bdd9-3d5984012f2f"}},"repository":{"url":"git+https://github.com/reisi007/optimus-image-cropper.git","type":"git","directory":"packages/optimus-image-cropper"},"_npmVersion":"11.17.0","description":"Optimus UI Image Cropper — Canvas-based image cropping component for Angular","directories":{},"sideEffects":false,"_nodeVersion":"26.5.0","dependencies":{"tslib":"^2.8.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"peerDependencies":{"primelocale":"^2.4.0","@angular/core":"^21.0.0","@angular/forms":"^21.0.0","@angular/common":"^21.0.0","@openng/optimus-ui":"^1.0.0-rc.1"},"peerDependenciesMeta":{"primelocale":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/optimus-image-cropper_0.1.1_1785168800695_0.2311999101862814","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@all-the.rest/optimus-image-cropper","version":"0.1.2","description":"Optimus UI Image Cropper — Canvas-based image cropping component for Angular","license":"MIT","author":{"name":"Florian Reisinger"},"repository":{"type":"git","url":"git+https://github.com/reisi007/optimus-image-cropper.git","directory":"packages/optimus-image-cropper"},"homepage":"https://optimus-image-cropper.all-the.rest/","bugs":{"url":"https://github.com/reisi007/optimus-image-cropper/issues"},"keywords":["angular","optimus-ui","image-cropper","cropper","standalone","signals"],"peerDependencies":{"@angular/common":"^21.0.0","@angular/core":"^21.0.0","@angular/forms":"^21.0.0","@openng/optimus-ui":"^1.0.0-rc.0","@openng/optimus-ui-locale":"^1.0.0-rc.0"},"peerDependenciesMeta":{"@openng/optimus-ui-locale":{"optional":true}},"dependencies":{"tslib":"^2.8.1"},"sideEffects":false,"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"module":"fesm2022/all-the.rest-optimus-image-cropper.mjs","typings":"types/all-the.rest-optimus-image-cropper.d.ts","exports":{"./package.json":{"default":"./package.json"},".":{"types":"./types/all-the.rest-optimus-image-cropper.d.ts","default":"./fesm2022/all-the.rest-optimus-image-cropper.mjs"}},"type":"module","_id":"@all-the.rest/optimus-image-cropper@0.1.2","_nodeVersion":"26.5.1","_npmVersion":"11.17.0","dist":{"integrity":"sha512-L1LLOPYhSrQGz8MZD54SpTtx+O55SEZ8tzEwMM5I4reFZb4NLOnbNSIVXhgcVkYAKYIpMKgdUCxLvAQohQXMjw==","shasum":"8fea0322b1afd0a06df57690595a86d11f916e1f","tarball":"https://registry.npmjs.org/@all-the.rest/optimus-image-cropper/-/optimus-image-cropper-0.1.2.tgz","fileCount":6,"unpackedSize":225695,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@all-the.rest%2foptimus-image-cropper@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDNw4Q0uTpz8A1H32Qhwe7UebFCyGvaRWUWBC3nOJjODAiEA7qhdDZjJQuEhYkBmSWBfr6W+JuDxvQg90kN/x0kDHx4="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:90a26c22-37a9-4b81-bdd9-3d5984012f2f"}},"directories":{},"maintainers":[{"name":"florian.reisinger.at","email":"florianreisingerat@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/optimus-image-cropper_0.1.2_1785579007148_0.6491473739438931"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-27T15:47:00.198Z","modified":"2026-08-01T10:10:07.586Z","0.1.0":"2026-07-27T15:47:00.528Z","0.1.1":"2026-07-27T16:13:20.865Z","0.1.2":"2026-08-01T10:10:07.269Z"},"bugs":{"url":"https://github.com/reisi007/optimus-image-cropper/issues"},"author":{"name":"Florian Reisinger"},"license":"MIT","homepage":"https://optimus-image-cropper.all-the.rest/","keywords":["angular","optimus-ui","image-cropper","cropper","standalone","signals"],"repository":{"type":"git","url":"git+https://github.com/reisi007/optimus-image-cropper.git","directory":"packages/optimus-image-cropper"},"description":"Optimus UI Image Cropper — Canvas-based image cropping component for Angular","maintainers":[{"name":"florian.reisinger.at","email":"florianreisingerat@gmail.com"}],"readme":"# @all-the.rest/optimus-image-cropper\n\nCanvas-based Angular Image Cropper built on Optimus UI\n\n[![npm](https://img.shields.io/npm/v/@all-the.rest/optimus-image-cropper)](https://www.npmjs.com/package/@all-the.rest/optimus-image-cropper)\n[![CI](https://img.shields.io/github/actions/workflow/status/reisi007/optimus-image-cropper/ci.yml)](https://github.com/reisi007/optimus-image-cropper/actions/workflows/ci.yml)\n[![License](https://img.shields.io/github/license/reisi007/optimus-image-cropper)](LICENSE)\n\n> **[Live Demo](https://optimus-image-cropper.all-the.rest)** — try it now\n\n---\n\n## Features\n\n- **Canvas-based** crop, zoom, rotate, and pan with real-time preview\n- **Touch + keyboard** support — pinch-to-zoom, arrow keys, keyboard shortcuts\n- **Angular 21** — standalone, signals-based, zoneless (`provideZonelessChangeDetection`)\n- **Optimus UI / PrimeNG theming** — Aura-compatible `--p-*` design tokens\n- **SSR-safe** — DOM/Canvas access behind `ensureBrowser()` guard\n- **ControlValueAccessor** — works with template-driven and reactive forms\n- **i18n** via `@openng/optimus-ui-locale` (optional) with built-in en-US fallback\n\n---\n\n## Install\n\n```sh\npnpm add @all-the.rest/optimus-image-cropper @openng/optimus-ui @openng/optimus-ui-themes\n```\n\n**Peer dependencies:**\n- `@angular/core`, `@angular/common`, `@angular/forms` — `^21.0.0`\n- `@openng/optimus-ui` — `^1.0.0-rc.1`\n- `@openng/optimus-ui-locale` — `^1.0.0-rc.0` (optional, for localized ARIA labels)\n\n---\n\n## Quickstart\n\n```typescript\nimport { Component } from '@angular/core';\nimport { provideOptimus } from '@openng/optimus-ui/core';\nimport { Aura } from '@openng/optimus-ui-themes';\nimport { provideZonelessChangeDetection } from '@angular/core';\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { OicCropper } from '@all-the.rest/optimus-image-cropper';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideZonelessChangeDetection(),\n    provideOptimus({ theme: { preset: Aura } }),\n  ],\n});\n\n@Component({\n  standalone: true,\n  imports: [OicCropper],\n  template: `\n    <oic-cropper\n      src=\"https://picsum.photos/800/600\"\n      aspectRatio=\"16:9\"\n      [(croppedImage)]=\"cropped\"\n    />\n  `,\n})\nexport class AppComponent {\n  cropped = '';\n}\n```\n\nThe library is **zoneless-safe** — use `provideZonelessChangeDetection()` in your app (no `zone.js` required).\n\n---\n\n## API\n\n### Inputs\n\n| Input | Type | Default | Description |\n|---|---|---|---|\n| `src` | `string` | `''` | Image URL (absolute, relative, or data-URL) |\n| `aspectRatio` | `'free' \\| '1:1' \\| '4:3' \\| '16:9'` | `'16:9'` | When not `'free'` the aspect ratio selector is hidden and the crop is locked to the given ratio |\n| `overlayTemplate` | `string` | `''` | Reserved for future use — SVG template string for custom overlay |\n| `outputFormat` | `'image/png' \\| 'image/jpeg' \\| 'image/webp'` | `'image/png'` | Output format |\n| `outputQuality` | `number` | `0.92` | Quality for JPEG/WebP (`0`–`1`) |\n| `minCropWidth` | `number` | `20` | Minimum crop selection width in CSS pixels |\n| `minCropHeight` | `number` | `20` | Minimum crop selection height in CSS pixels |\n| `outputWidth` | `number` | `0` | Fixed output width in pixels (`0` = auto from crop selection) |\n| `outputHeight` | `number` | `0` | Fixed output height in pixels (`0` = auto from crop selection) |\n| `rotationMin` | `number` | `-45` | Minimum fine-rotation slider value (degrees) |\n| `rotationMax` | `number` | `45` | Maximum fine-rotation slider value (degrees) |\n| `rotationStepInput` | `number` | `1` | Fine-rotation slider step (degrees) |\n| `constrainToImage` | `boolean` | `true` | When `true`, the crop selection cannot leave the visible image bounds |\n| `toolbarPosition` | `'top' \\| 'bottom' \\| 'left' \\| 'right'` | `'bottom'` | Toolbar placement relative to the viewport |\n| `width` | `string \\| number` | `'100%'` | Viewport width (CSS value or pixel number) |\n\n### Outputs\n\n| Output | Type | Description |\n|---|---|---|\n| `cropChange` | `OicCropperResult` | Emitted on every crop change |\n| `loadError` | `string` | Emitted when image loading fails (the URL that failed) |\n\n### Model (two-way binding)\n\n```typescript\nreadonly croppedImage = model<string>('');\n```\n\nUse `[(croppedImage)]` for two-way binding of the base64 data-URL result.\n\n### `OicCropperResult`\n\n```typescript\ninterface OicCropperResult {\n  dataUrl: string;     // base64 data-URL of the cropped output\n  blob: Blob | null;   // Blob (null if unavailable in the current environment)\n  width: number;        // output image width in pixels\n  height: number;       // output image height in pixels\n  format: OicOutputFormat;\n}\n```\n\n### ControlValueAccessor (ngModel / formControl)\n\nThe component implements `ControlValueAccessor` for Angular forms — it writes the `src` value:\n\n```html\n<oic-cropper [formControl]=\"ctrl\" />\n```\n\n```typescript\nctrl = new FormControl<string>('https://example.com/image.jpg');\n```\n\nWhen using CVA the `src` input is set via the form control value. The cropped image data-URL is propagated back through `onChange`.\n\n### Keyboard shortcuts\n\n| Key | Action |\n|---|---|\n| Arrow keys | Move the crop selection by 5% steps |\n| `+` / `=` | Zoom in |\n| `-` / `_` | Zoom out |\n| `r` | Rotate 90° counter-clockwise |\n| `R` | Rotate 90° clockwise |\n\nFocus the component (`tabindex=\"0\"`) for keyboard control.\n\n### CSS custom properties\n\n| Token | Fallback | Usage |\n|---|---|---|\n| `--p-content-background` | `--p-surface-100` / `#f5f5f5` | Toolbar and viewport background |\n| `--p-content-border-color` | `--p-surface-200` / `#e5e7eb` | Toolbar separators / borders |\n| `--p-text-muted-color` | `#6b7280` | Toolbar labels and values |\n\nThe crop overlay receives percentage-based inline styles for positioning (not CSS custom properties).\n\n### Overlay / content projection\n\nProject custom SVG or HTML into the crop area using the `oicCropperOverlay` attribute selector:\n\n```html\n<oic-cropper src=\"...\">\n  <svg oicCropperOverlay class=\"pointer-events-none\">\n    <!-- custom grid, crosshair, etc. -->\n  </svg>\n</oic-cropper>\n```\n\nThe default overlay (`OicCropperGridOverlay`) provides a grid with rule-of-thirds guide lines.\n\n### Exported classes and tokens\n\n| Export | Kind | Description |\n|---|---|---|\n| `OicCropperCanvas` | class | Framework-agnostic canvas engine (image rendering, zoom, rotation, crop output) |\n| `OicCropperInteraction` | class | Framework-agnostic pointer/touch interaction state machine (move, resize, pinch) |\n| `OIC_CROPPER_DEFAULT_OPTIONS` | `InjectionToken<OicCropperOptions>` | Override global defaults (zoomStep, rotateStep, outputQuality, etc.) |\n| `OIC_CROPPER_INTL` | `InjectionToken<OicCropperIntl>` | Internationalized ARIA labels and UI strings |\n| `OIC_CROPPER_INTL_DEFAULTS` | `OicCropperIntl` | Default en-US string constants |\n| `provideOicCropperIntl(...)` | provider factory | Merge partial `OicCropperIntl` over defaults |\n| `provideOicCropperIntlFromLocale(...)` | provider factory | Derive strings from an `@openng/optimus-ui-locale` locale object |\n| `OicCropperOptions` | interface | Config object for `OIC_CROPPER_DEFAULT_OPTIONS` |\n| `OicCropperResult` | interface | Crop result metadata + data-URL + blob |\n| `OicCropRect` | interface | Normalized crop rectangle (`0`–`1`) |\n| `OicOutputFormat` | type | `'image/png' \\| 'image/jpeg' \\| 'image/webp'` |\n| `OicAspectRatioPreset` | type | `'free' \\| '1:1' \\| '4:3' \\| '16:9'` |\n| `OicCropperDragMode` | type | Interaction mode discriminator |\n\n---\n\n## i18n\n\nThe library ships with **en-US built-in** defaults. Override individual strings with `provideOicCropperIntl`:\n\n```typescript\nimport { provideOicCropperIntl } from '@all-the.rest/optimus-image-cropper';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideOicCropperIntl({\n      zoomIn: 'Vergrößern',\n      zoomOut: 'Verkleinern',\n      rotateLeft: 'Links drehen',\n      rotateRight: 'Rechts drehen',\n      fineRotation: 'Feinrotation',\n      aspectRatio: 'Seitenverhältnis',\n      aspectFree: 'Frei',\n    }),\n  ],\n});\n```\n\n### `@openng/optimus-ui-locale` integration (optional)\n\nInstall `@openng/optimus-ui-locale` and pass any locale object:\n\n```typescript\nimport { provideOicCropperIntlFromLocale } from '@all-the.rest/optimus-image-cropper';\nimport { de } from '@openng/optimus-ui-locale/js/de.js';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideOicCropperIntlFromLocale(de),\n  ],\n});\n```\n\nThe function reads `aria.zoomIn`, `aria.zoomOut`, `aria.rotateLeft`, `aria.rotateRight` from the locale object and falls back to English defaults for the remaining keys (`fineRotation`, `aspectRatio`, `aspectFree`).\n\n---\n\n## Theming\n\nThe component is styled with Optimus design tokens (Aura-compatible `--p-*` CSS custom properties). See the [CSS custom properties](#css-custom-properties) table above for the tokens used.\n\nApply your theme via Optimus UI's theme preset (e.g., Aura, Nora, Lara). The crop overlay uses percentage-based inline styles for positioning.\n\n---\n\n## Using with PrimeNG instead of Optimus UI\n\n**Optimus UI** (`@openng/optimus-ui`) is an API-compatible fork of PrimeNG. If your project uses PrimeNG directly, you can still use this library with a package alias/override — no code changes required.\n\n### Import mapping\n\n| Optimus UI | PrimeNG equivalent |\n|---|---|\n| `@openng/optimus-ui` | `primeng` |\n| `@openng/optimus-ui/button` | `primeng/button` |\n| `@openng/optimus-ui/slider` | `primeng/slider` |\n| `@openng/optimus-ui/select` | `primeng/select` |\n| `@openng/optimus-ui/icons/*` | `primeng/icons/*` |\n| `@openng/optimus-ui-themes` | `@primeuix/themes` |\n| `provideOptimus(...)` | `providePrimeNG(...)` |\n| Design tokens | Identical (`--p-*`) |\n| `@openng/optimus-ui-locale` | Works for both |\n\n### Provider swap\n\n```typescript\n// Optimus UI\nimport { provideOptimus } from '@openng/optimus-ui/core';\nimport { Aura } from '@openng/optimus-ui-themes';\n\nprovideOptimus({ theme: { preset: Aura } });\n\n// PrimeNG\nimport { providePrimeNG } from 'primeng/config';\nimport Aura from '@primeuix/themes/aura';\n\nprovidePrimeNG({ theme: { preset: Aura } });\n```\n\n### pnpm alias / override (zero code change)\n\nAdd to `package.json` to use PrimeNG without changing any imports:\n\n```json\n{\n  \"pnpm\": {\n    \"overrides\": {\n      \"primeng\": \"npm:@openng/optimus-ui@^1.0.0-rc.1\"\n    }\n  }\n}\n```\n\nOr the reverse — if you are on PrimeNG but want to consume this package (which declares `@openng/optimus-ui` as a peer), alias it:\n\n```json\n{\n  \"pnpm\": {\n    \"overrides\": {\n      \"@openng/optimus-ui\": \"npm:primeng@^19.0.0\"\n    }\n  }\n}\n```\n\n> **Note:** This package declares `@openng/optimus-ui` as a **required peer dependency**. To use it with PrimeNG without installing Optimus UI, use the pnpm alias approach above.\n\n---\n\n## Development\n\n```bash\npnpm install\npnpm nx build optimus-image-cropper   # Library build\npnpm nx test optimus-image-cropper    # Unit tests (Vitest)\npnpm nx lint optimus-image-cropper    # ESLint\npnpm nx serve demo                    # Demo application\npnpm nx build demo                    # Demo build\npnpm nx e2e demo-e2e                  # Playwright E2E\n```\n\n---\n\n## Release\n\nSee [docs/RELEASING.md](docs/RELEASING.md) for the complete release process — first release manually, then CI-based via Tag-Push with Trusted Publishing (npm OIDC).\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","readmeFilename":"README.md"}