@we-are-singular/svelte-chop-chop 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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/dist/components/.gitkeep +0 -0
  4. package/dist/components/CircleStencil.svelte +126 -0
  5. package/dist/components/CircleStencil.svelte.d.ts +10 -0
  6. package/dist/components/CropOverlay.svelte +51 -0
  7. package/dist/components/CropOverlay.svelte.d.ts +8 -0
  8. package/dist/components/CropStencil.svelte +84 -0
  9. package/dist/components/CropStencil.svelte.d.ts +10 -0
  10. package/dist/components/Cropper.svelte +242 -0
  11. package/dist/components/Cropper.svelte.d.ts +32 -0
  12. package/dist/components/DragHandle.svelte +129 -0
  13. package/dist/components/DragHandle.svelte.d.ts +13 -0
  14. package/dist/components/FilterStrip.svelte +58 -0
  15. package/dist/components/FilterStrip.svelte.d.ts +9 -0
  16. package/dist/components/GridOverlay.svelte +85 -0
  17. package/dist/components/GridOverlay.svelte.d.ts +9 -0
  18. package/dist/components/ImageEditor.svelte +1087 -0
  19. package/dist/components/ImageEditor.svelte.d.ts +16 -0
  20. package/dist/components/Toolbar.svelte +103 -0
  21. package/dist/components/Toolbar.svelte.d.ts +7 -0
  22. package/dist/composables/.gitkeep +0 -0
  23. package/dist/composables/create-cropper.svelte.d.ts +49 -0
  24. package/dist/composables/create-cropper.svelte.js +257 -0
  25. package/dist/composables/create-image-editor.svelte.d.ts +20 -0
  26. package/dist/composables/create-image-editor.svelte.js +596 -0
  27. package/dist/composables/create-transform.svelte.d.ts +28 -0
  28. package/dist/composables/create-transform.svelte.js +26 -0
  29. package/dist/core/.gitkeep +0 -0
  30. package/dist/core/color-matrix.d.ts +39 -0
  31. package/dist/core/color-matrix.js +137 -0
  32. package/dist/core/constraints.d.ts +46 -0
  33. package/dist/core/constraints.js +107 -0
  34. package/dist/core/coordinate-system.d.ts +65 -0
  35. package/dist/core/coordinate-system.js +185 -0
  36. package/dist/core/crop-engine.svelte.d.ts +33 -0
  37. package/dist/core/crop-engine.svelte.js +192 -0
  38. package/dist/core/export.d.ts +14 -0
  39. package/dist/core/export.js +99 -0
  40. package/dist/core/history-manager.svelte.d.ts +22 -0
  41. package/dist/core/history-manager.svelte.js +72 -0
  42. package/dist/core/image-loader.svelte.d.ts +17 -0
  43. package/dist/core/image-loader.svelte.js +126 -0
  44. package/dist/core/interactions.d.ts +52 -0
  45. package/dist/core/interactions.js +118 -0
  46. package/dist/core/keyboard.d.ts +11 -0
  47. package/dist/core/keyboard.js +23 -0
  48. package/dist/core/transform-engine.svelte.d.ts +27 -0
  49. package/dist/core/transform-engine.svelte.js +79 -0
  50. package/dist/core/types.d.ts +265 -0
  51. package/dist/core/types.js +5 -0
  52. package/dist/index.d.ts +30 -0
  53. package/dist/index.js +35 -0
  54. package/dist/plugins/.gitkeep +0 -0
  55. package/dist/plugins/index.d.ts +8 -0
  56. package/dist/plugins/index.js +8 -0
  57. package/dist/plugins/plugin-filters.d.ts +14 -0
  58. package/dist/plugins/plugin-filters.js +100 -0
  59. package/dist/plugins/plugin-finetune.d.ts +10 -0
  60. package/dist/plugins/plugin-finetune.js +23 -0
  61. package/dist/plugins/plugin-frame.d.ts +11 -0
  62. package/dist/plugins/plugin-frame.js +81 -0
  63. package/dist/plugins/plugin-resize.d.ts +10 -0
  64. package/dist/plugins/plugin-resize.js +23 -0
  65. package/dist/plugins/plugin-watermark.d.ts +10 -0
  66. package/dist/plugins/plugin-watermark.js +86 -0
  67. package/dist/presets/.gitkeep +0 -0
  68. package/dist/presets/cover-photo.d.ts +14 -0
  69. package/dist/presets/cover-photo.js +14 -0
  70. package/dist/presets/index.d.ts +6 -0
  71. package/dist/presets/index.js +6 -0
  72. package/dist/presets/product-image.d.ts +11 -0
  73. package/dist/presets/product-image.js +11 -0
  74. package/dist/presets/profile-picture.d.ts +17 -0
  75. package/dist/presets/profile-picture.js +17 -0
  76. package/dist/themes/.gitkeep +0 -0
  77. package/dist/themes/dark.css +17 -0
  78. package/dist/themes/default.css +23 -0
  79. package/dist/themes/minimal.css +17 -0
  80. package/package.json +118 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 We Are Singular
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # svelte-chop-chop
2
+
3
+ > Headless-first image cropping and editing SDK for Svelte 5.
4
+ > Zero dependencies beyond Svelte.
5
+
6
+ [![npm](https://img.shields.io/npm/v/@we-are-singular/svelte-chop-chop)](https://www.npmjs.com/package/@we-are-singular/svelte-chop-chop)
7
+ [![license](https://img.shields.io/github/license/we-are-singular/svelte-chop-chop)](LICENSE)
8
+
9
+ **[Documentation & Demo →](https://svelte-chop-chop.pages.dev)**
10
+
11
+ ---
12
+
13
+ ## Features
14
+
15
+ - **`<Cropper>`** — Lightweight crop component with handles, grid overlays, and transforms
16
+ - **`<ImageEditor>`** — Full-featured editor with plugin-driven toolbar
17
+ - **Headless composables** — `createCropper` and `createImageEditor` for fully custom UI
18
+ - **Plugin system** — filters, finetune, frame, watermark, resize (all opt-in)
19
+ - **Presets** — `profilePicture`, `coverPhoto`, `productImage`
20
+ - **Three themes** — default, dark, minimal (CSS custom properties)
21
+ - **Zero runtime dependencies** — peer dep: `svelte ^5.0.0` only
22
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @we-are-singular/svelte-chop-chop
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ### Cropper
36
+
37
+ ```svelte
38
+ <script lang="ts">
39
+ import { Cropper } from '@we-are-singular/svelte-chop-chop';
40
+ import '@we-are-singular/svelte-chop-chop/themes/default';
41
+ </script>
42
+
43
+ <Cropper src="/photo.jpg" aspectRatio={16 / 9} style="height: 400px;" />
44
+ ```
45
+
46
+ ### Image Editor
47
+
48
+ ```svelte
49
+ <script lang="ts">
50
+ import { ImageEditor } from '@we-are-singular/svelte-chop-chop';
51
+ import { pluginFilters } from '@we-are-singular/svelte-chop-chop/plugins/filters';
52
+ import { pluginFinetune } from '@we-are-singular/svelte-chop-chop/plugins/finetune';
53
+ import { pluginFrame } from '@we-are-singular/svelte-chop-chop/plugins/frame';
54
+ import { pluginWatermark } from '@we-are-singular/svelte-chop-chop/plugins/watermark';
55
+ import { pluginResize } from '@we-are-singular/svelte-chop-chop/plugins/resize';
56
+ import type { ExportResult } from '@we-are-singular/svelte-chop-chop';
57
+ import '@we-are-singular/svelte-chop-chop/themes/default';
58
+
59
+ function handleExport(result: ExportResult) {
60
+ const url = URL.createObjectURL(result.blob!);
61
+ const a = document.createElement('a');
62
+ a.href = url;
63
+ a.download = 'edited.jpg';
64
+ a.click();
65
+ URL.revokeObjectURL(url);
66
+ }
67
+ </script>
68
+
69
+ <ImageEditor
70
+ src="/photo.jpg"
71
+ plugins={[pluginFilters(), pluginFinetune(), pluginFrame(), pluginWatermark(), pluginResize()]}
72
+ onexport={handleExport}
73
+ style="height: 500px;"
74
+ />
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Cropper Props
80
+
81
+ | Prop | Type | Default | Description |
82
+ |------|------|---------|-------------|
83
+ | `src` | `ImageSource` | — | URL, data URL, `File`, `Blob`, or `HTMLImageElement` |
84
+ | `aspectRatio` | `number \| { min?: number; max?: number } \| null` | `null` | Aspect ratio constraint |
85
+ | `sizeConstraints` | `SizeConstraints` | — | Min/max width/height in pixels |
86
+ | `grid` | `"none" \| "rule-of-thirds" \| "grid" \| "golden-ratio"` | `"rule-of-thirds"` | Grid overlay |
87
+ | `transitions` | `boolean` | `true` | CSS transitions |
88
+ | `stencil` | `Component` | `CropStencil` | Custom stencil component |
89
+ | `readOnly` | `boolean` | `false` | Disable interaction |
90
+ | `class` / `style` | `string` | `''` | Root element styling |
91
+
92
+ **Bindable:** `coordinates` (`CropCoordinates`), `transforms` (`TransformState`)
93
+ **Events:** `onchange`, `onready`, `onerror`
94
+
95
+ ---
96
+
97
+ ## Headless Usage
98
+
99
+ ```typescript
100
+ import { createCropper } from '@we-are-singular/svelte-chop-chop/headless';
101
+ import { createImageEditor } from '@we-are-singular/svelte-chop-chop';
102
+
103
+ // Lightweight cropper
104
+ const cropper = createCropper({ aspectRatio: 16 / 9 });
105
+ await cropper.loadImage('/photo.jpg');
106
+ const result = await cropper.export({ format: 'image/webp', quality: 0.9 });
107
+
108
+ // Full editor
109
+ const editor = createImageEditor({ plugins: [pluginFilters()] });
110
+ await editor.loadImage('/photo.jpg');
111
+ editor.applyFilter('clarendon');
112
+ editor.rotate(90);
113
+ const result = await editor.export({ format: 'image/webp', quality: 0.9 });
114
+ // result.blob, result.dataURL, result.canvas, result.coordinates
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Plugins
120
+
121
+ | Import | Description |
122
+ |--------|-------------|
123
+ | `@we-are-singular/svelte-chop-chop/plugins/filters` | 16 Instagram-style color filter presets |
124
+ | `@we-are-singular/svelte-chop-chop/plugins/finetune` | Brightness, contrast, saturation, exposure, etc. |
125
+ | `@we-are-singular/svelte-chop-chop/plugins/frame` | Decorative frame at export (solid, line, hook) |
126
+ | `@we-are-singular/svelte-chop-chop/plugins/watermark` | Text watermark at export |
127
+ | `@we-are-singular/svelte-chop-chop/plugins/resize` | Output width/height controls |
128
+
129
+ ### Custom Plugin
130
+
131
+ ```typescript
132
+ import type { ChopPlugin } from '@we-are-singular/svelte-chop-chop';
133
+
134
+ const myPlugin: ChopPlugin = {
135
+ name: 'my-plugin',
136
+ setup(ctx) {
137
+ ctx.registerAction({
138
+ id: 'my-action',
139
+ label: 'My Action',
140
+ group: 'tabs',
141
+ execute: () => ctx.showPanel('my-panel'),
142
+ });
143
+
144
+ ctx.registerPostProcessor(async (drawCtx, canvas) => {
145
+ // draw on canvas at export time
146
+ });
147
+
148
+ return () => { /* cleanup */ };
149
+ },
150
+ };
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Presets
156
+
157
+ ```typescript
158
+ import { profilePicture, coverPhoto, productImage } from '@we-are-singular/svelte-chop-chop/presets';
159
+
160
+ // Spread into component props
161
+ <Cropper src="/photo.jpg" {...profilePicture} />
162
+ ```
163
+
164
+ | Preset | Aspect | Max Size | Format |
165
+ |--------|--------|----------|--------|
166
+ | `profilePicture` | 1:1 | 512px | JPEG |
167
+ | `coverPhoto` | 16:9 | min 1200px wide | JPEG |
168
+ | `productImage` | 1:1 | — | PNG |
169
+
170
+ ---
171
+
172
+ ## Theming
173
+
174
+ ```css
175
+ @import '@we-are-singular/svelte-chop-chop/themes/default'; /* or dark, minimal */
176
+ ```
177
+
178
+ Override any CSS custom property:
179
+
180
+ ```css
181
+ :root {
182
+ --chop-bg: #1a1a2e;
183
+ --chop-stencil-border: #e94560;
184
+ --chop-toolbar-active: #e94560;
185
+ }
186
+ ```
187
+
188
+ **Available variables:** `--chop-bg`, `--chop-canvas-bg`, `--chop-color`, `--chop-border-radius`, `--chop-stencil-border`, `--chop-stencil-border-active`, `--chop-grid-color`, `--chop-overlay`, `--chop-toolbar-bg`, `--chop-toolbar-color`, `--chop-toolbar-active`, `--chop-transition-duration`, `--chop-transition-easing`
189
+
190
+ ---
191
+
192
+ ## Package Exports
193
+
194
+ | Path | Description |
195
+ |------|-------------|
196
+ | `@we-are-singular/svelte-chop-chop` | Components, composables, types |
197
+ | `@we-are-singular/svelte-chop-chop/headless` | `createCropper` composable |
198
+ | `@we-are-singular/svelte-chop-chop/plugins` | All plugin factories |
199
+ | `@we-are-singular/svelte-chop-chop/presets` | Preset bundles |
200
+ | `@we-are-singular/svelte-chop-chop/themes/default` | Default CSS theme |
201
+ | `@we-are-singular/svelte-chop-chop/themes/dark` | Dark CSS theme |
202
+ | `@we-are-singular/svelte-chop-chop/themes/minimal` | Minimal CSS theme |
203
+
204
+ ---
205
+
206
+ ## Keyboard Shortcuts
207
+
208
+ | Key | Action |
209
+ |-----|--------|
210
+ | `R` / `Shift+R` | Rotate 90° CW / CCW |
211
+ | `H` / `V` | Flip horizontal / vertical |
212
+ | `Ctrl+Z` / `Ctrl+Shift+Z` | Undo / Redo |
213
+ | `+` / `-` | Zoom in / out |
214
+ | `0` | Reset crop to image bounds |
215
+ | `Escape` | Reset all transforms |
216
+ | `Ctrl+F` | Toggle filters panel |
217
+ | Arrow keys | Move crop 1px (10px with Shift) |
218
+
219
+ ---
220
+
221
+ ## License
222
+
223
+ MIT © [We Are Singular](https://github.com/we-are-singular)
File without changes
@@ -0,0 +1,126 @@
1
+ <!--
2
+ svelte-chop-chop — Circular crop stencil
3
+ Same contract as CropStencil, but with circular mask.
4
+ -->
5
+ <script lang="ts">
6
+ import type { HandlePosition, Point, StencilProps } from '../core/types.js';
7
+ import { createDragHandler } from '../core/interactions.js';
8
+ import DragHandle from './DragHandle.svelte';
9
+
10
+ let {
11
+ rect,
12
+ aspectRatio = 1,
13
+ active,
14
+ imageBounds,
15
+ grid,
16
+ transitions,
17
+ onmove,
18
+ onresize,
19
+ onresizestart,
20
+ onresizeend,
21
+ }: StencilProps & {
22
+ onmove: (delta: Point) => void;
23
+ onresize: (handle: HandlePosition, delta: Point) => void;
24
+ onresizestart?: () => void;
25
+ onresizeend?: () => void;
26
+ } = $props();
27
+
28
+ const drag = createDragHandler({
29
+ onMove: (delta) => onmove(delta),
30
+ });
31
+
32
+ // The circle is inscribed in a 1:1 square bounding rect.
33
+ // cx/cy are used for the circle border positioning only.
34
+ const size = $derived(Math.min(rect.width, rect.height));
35
+ const cx = $derived(rect.width / 2);
36
+ const cy = $derived(rect.height / 2);
37
+ </script>
38
+
39
+ <div
40
+ class="chop-stencil chop-stencil-circle"
41
+ class:chop-stencil-active={active}
42
+ style:left="{rect.x}px"
43
+ style:top="{rect.y}px"
44
+ style:width="{rect.width}px"
45
+ style:height="{rect.height}px"
46
+ role="group"
47
+ aria-label="Circular crop area"
48
+ onpointerdown={drag.onpointerdown}
49
+ onpointermove={drag.onpointermove}
50
+ onpointerup={drag.onpointerup}
51
+ >
52
+ <svg class="chop-circle-overlay" aria-hidden="true">
53
+ <defs>
54
+ <mask id="chop-circle-mask">
55
+ <rect x="0" y="0" width="100%" height="100%" fill="white" />
56
+ <circle
57
+ cx="{cx}"
58
+ cy="{cy}"
59
+ r="{size / 2}"
60
+ fill="black"
61
+ />
62
+ </mask>
63
+ </defs>
64
+ <rect
65
+ x="0"
66
+ y="0"
67
+ width="100%"
68
+ height="100%"
69
+ fill="var(--chop-overlay, rgba(0, 0, 0, 0.55))"
70
+ mask="url(#chop-circle-mask)"
71
+ />
72
+ </svg>
73
+
74
+ <div
75
+ class="chop-circle-border"
76
+ style:left="{cx}px"
77
+ style:top="{cy}px"
78
+ style:width="{size}px"
79
+ style:height="{size}px"
80
+ style:margin-left="-{size / 2}px"
81
+ style:margin-top="-{size / 2}px"
82
+ ></div>
83
+
84
+ <!-- Handles at the 4 bounding-box corners — correct for a 1:1 square crop with circular mask -->
85
+ <DragHandle position="nw" {onresize} {onresizestart} {onresizeend} size={16} />
86
+ <DragHandle position="ne" {onresize} {onresizestart} {onresizeend} size={16} />
87
+ <DragHandle position="sw" {onresize} {onresizestart} {onresizeend} size={16} />
88
+ <DragHandle position="se" {onresize} {onresizestart} {onresizeend} size={16} />
89
+ </div>
90
+
91
+ <style>
92
+ .chop-stencil-circle {
93
+ position: absolute;
94
+ border: none;
95
+ box-sizing: border-box;
96
+ z-index: 5;
97
+ touch-action: none;
98
+ }
99
+
100
+ .chop-circle-overlay {
101
+ position: absolute;
102
+ left: 0;
103
+ top: 0;
104
+ width: 100%;
105
+ height: 100%;
106
+ pointer-events: none;
107
+ }
108
+
109
+ .chop-circle-border {
110
+ position: absolute;
111
+ border: 2px solid var(--chop-stencil-border, rgba(255, 255, 255, 0.8));
112
+ border-radius: 50%;
113
+ box-sizing: border-box;
114
+ pointer-events: none;
115
+ }
116
+
117
+ .chop-stencil-active .chop-circle-border {
118
+ border-color: var(--chop-stencil-border-active, #fff);
119
+ }
120
+
121
+ /* Give circle handles a subtle dark outline so they're visible on light images */
122
+ :global(.chop-stencil-circle .chop-handle) {
123
+ border: 2px solid rgba(0, 0, 0, 0.35);
124
+ box-sizing: border-box;
125
+ }
126
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { HandlePosition, Point, StencilProps } from '../core/types.js';
2
+ type $$ComponentProps = StencilProps & {
3
+ onmove: (delta: Point) => void;
4
+ onresize: (handle: HandlePosition, delta: Point) => void;
5
+ onresizestart?: () => void;
6
+ onresizeend?: () => void;
7
+ };
8
+ declare const CircleStencil: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type CircleStencil = ReturnType<typeof CircleStencil>;
10
+ export default CircleStencil;
@@ -0,0 +1,51 @@
1
+ <!--
2
+ svelte-chop-chop — Dark overlay with crop cutout
3
+ Shows dimmed area outside the crop rectangle.
4
+ Positioned to cover imageBounds; cutout at rect.
5
+ -->
6
+ <script lang="ts">
7
+ import type { Rect } from '../core/types.js';
8
+
9
+ let { rect, imageBounds }: { rect: Rect; imageBounds: Rect } = $props();
10
+
11
+ const maskX = $derived(rect.x - imageBounds.x);
12
+ const maskY = $derived(rect.y - imageBounds.y);
13
+ </script>
14
+
15
+ <svg
16
+ class="chop-overlay"
17
+ aria-hidden="true"
18
+ style:left="{imageBounds.x}px"
19
+ style:top="{imageBounds.y}px"
20
+ style:width="{imageBounds.width}px"
21
+ style:height="{imageBounds.height}px"
22
+ >
23
+ <defs>
24
+ <mask id="chop-crop-mask">
25
+ <rect x="0" y="0" width="100%" height="100%" fill="white" />
26
+ <rect
27
+ x="{maskX}"
28
+ y="{maskY}"
29
+ width="{rect.width}"
30
+ height="{rect.height}"
31
+ fill="black"
32
+ />
33
+ </mask>
34
+ </defs>
35
+ <rect
36
+ x="0"
37
+ y="0"
38
+ width="100%"
39
+ height="100%"
40
+ fill="var(--chop-overlay, rgba(0, 0, 0, 0.55))"
41
+ mask="url(#chop-crop-mask)"
42
+ />
43
+ </svg>
44
+
45
+ <style>
46
+ .chop-overlay {
47
+ position: absolute;
48
+ pointer-events: none;
49
+ z-index: 2;
50
+ }
51
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { Rect } from '../core/types.js';
2
+ type $$ComponentProps = {
3
+ rect: Rect;
4
+ imageBounds: Rect;
5
+ };
6
+ declare const CropOverlay: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type CropOverlay = ReturnType<typeof CropOverlay>;
8
+ export default CropOverlay;
@@ -0,0 +1,84 @@
1
+ <!--
2
+ svelte-chop-chop — Default rectangular stencil
3
+ Uses DragHandle, CropOverlay, GridOverlay.
4
+ -->
5
+ <script lang="ts">
6
+ import type { HandlePosition, Point, StencilProps } from '../core/types.js';
7
+ import { createDragHandler } from '../core/interactions.js';
8
+ import DragHandle from './DragHandle.svelte';
9
+ import GridOverlay from './GridOverlay.svelte';
10
+
11
+ let {
12
+ rect,
13
+ aspectRatio,
14
+ active,
15
+ imageBounds,
16
+ grid,
17
+ transitions,
18
+ onmove,
19
+ onresize,
20
+ onresizestart,
21
+ onresizeend,
22
+ }: StencilProps & {
23
+ onmove: (delta: Point) => void;
24
+ onresize: (handle: HandlePosition, delta: Point) => void;
25
+ onresizestart?: () => void;
26
+ onresizeend?: () => void;
27
+ } = $props();
28
+
29
+ const drag = createDragHandler({
30
+ onMove: (delta) => onmove(delta),
31
+ });
32
+ </script>
33
+
34
+ <div
35
+ class="chop-stencil chop-stencil-rect"
36
+ class:chop-stencil-active={active}
37
+ class:chop-stencil-transitions={transitions}
38
+ style:left="{rect.x}px"
39
+ style:top="{rect.y}px"
40
+ style:width="{rect.width}px"
41
+ style:height="{rect.height}px"
42
+ role="group"
43
+ aria-label="Crop area"
44
+ onpointerdown={drag.onpointerdown}
45
+ onpointermove={drag.onpointermove}
46
+ onpointerup={drag.onpointerup}
47
+ >
48
+ {#if !aspectRatio || aspectRatio === null}
49
+ <DragHandle position="nw" {onresize} {onresizestart} {onresizeend} />
50
+ <DragHandle position="ne" {onresize} {onresizestart} {onresizeend} />
51
+ <DragHandle position="sw" {onresize} {onresizestart} {onresizeend} />
52
+ <DragHandle position="se" {onresize} {onresizestart} {onresizeend} />
53
+ <DragHandle position="n" {onresize} {onresizestart} {onresizeend} />
54
+ <DragHandle position="s" {onresize} {onresizestart} {onresizeend} />
55
+ <DragHandle position="e" {onresize} {onresizestart} {onresizeend} />
56
+ <DragHandle position="w" {onresize} {onresizestart} {onresizeend} />
57
+ {:else}
58
+ <DragHandle position="nw" {onresize} {onresizestart} {onresizeend} />
59
+ <DragHandle position="ne" {onresize} {onresizestart} {onresizeend} />
60
+ <DragHandle position="sw" {onresize} {onresizestart} {onresizeend} />
61
+ <DragHandle position="se" {onresize} {onresizestart} {onresizeend} />
62
+ {/if}
63
+
64
+ <GridOverlay {rect} {grid} {active} />
65
+ </div>
66
+
67
+ <style>
68
+ .chop-stencil {
69
+ position: absolute;
70
+ border: 2px solid var(--chop-stencil-border, rgba(255, 255, 255, 0.8));
71
+ box-sizing: border-box;
72
+ z-index: 5;
73
+ touch-action: none;
74
+ }
75
+
76
+ .chop-stencil-active {
77
+ border-color: var(--chop-stencil-border-active, #fff);
78
+ }
79
+
80
+ .chop-stencil-transitions {
81
+ transition: border-color var(--chop-transition-duration, 300ms)
82
+ var(--chop-transition-easing, cubic-bezier(0.4, 0, 0.2, 1));
83
+ }
84
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { HandlePosition, Point, StencilProps } from '../core/types.js';
2
+ type $$ComponentProps = StencilProps & {
3
+ onmove: (delta: Point) => void;
4
+ onresize: (handle: HandlePosition, delta: Point) => void;
5
+ onresizestart?: () => void;
6
+ onresizeend?: () => void;
7
+ };
8
+ declare const CropStencil: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type CropStencil = ReturnType<typeof CropStencil>;
10
+ export default CropStencil;