@streamscloud/kit 0.63.0 → 0.65.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 (46) hide show
  1. package/dist/core/local-storage/index.d.ts +1 -1
  2. package/dist/core/local-storage/local-storage-item.d.ts +14 -2
  3. package/dist/core/local-storage/local-storage-item.js +35 -10
  4. package/dist/core/utils/color-helper.d.ts +4 -0
  5. package/dist/core/utils/color-helper.js +10 -0
  6. package/dist/core/utils/price-helper.d.ts +2 -0
  7. package/dist/core/utils/price-helper.js +12 -1
  8. package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte +17 -51
  9. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.d.ts +4 -16
  10. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.js +1 -14
  11. package/dist/ui/cropper/image-editor-dialog/index.d.ts +8 -7
  12. package/dist/ui/cropper/image-editor-dialog/index.js +8 -7
  13. package/dist/ui/cropper/image-editor-dialog/types.d.ts +1 -1
  14. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte +149 -181
  15. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte.d.ts +4 -13
  16. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +389 -147
  17. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +13 -39
  18. package/dist/ui/cropper/img-cropper/frame-geometry.d.ts +82 -0
  19. package/dist/ui/cropper/img-cropper/frame-geometry.js +165 -0
  20. package/dist/ui/cropper/img-cropper/img-cropper-localization.d.ts +2 -3
  21. package/dist/ui/cropper/img-cropper/img-cropper-localization.js +12 -19
  22. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.d.ts +14 -5
  23. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.js +127 -33
  24. package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +4 -51
  25. package/dist/ui/cropper/img-cropper/img-cropper-utils.js +35 -190
  26. package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +99 -34
  27. package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +405 -104
  28. package/dist/ui/cropper/img-cropper/index.d.ts +1 -2
  29. package/dist/ui/cropper/img-cropper/index.js +0 -1
  30. package/dist/ui/image/cmp.image.svelte +23 -5
  31. package/dist/ui/image/cmp.image.svelte.d.ts +1 -1
  32. package/dist/ui/page-toolbar/cmp.toolbar-segmented-control.svelte +2 -2
  33. package/dist/ui/page-toolbar/cmp.toolbar-view-controls.svelte +4 -2
  34. package/dist/ui/table/table-columns-manager/cmp.table-columns-manager.svelte +4 -2
  35. package/package.json +1 -3
  36. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte +0 -68
  37. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte.d.ts +0 -21
  38. package/dist/ui/cropper/img-cropper/cropperjs-elements.d.ts +0 -35
  39. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +0 -40
  40. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +0 -193
  41. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.d.ts +0 -7
  42. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +0 -51
  43. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.d.ts +0 -7
  44. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +0 -89
  45. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +0 -42
  46. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.js +0 -1
@@ -1,9 +1,5 @@
1
1
  import { Base64Helper } from '../../../core/files/base64-helper';
2
2
  import { default as mime } from 'mime';
3
- const COVERAGE_TOLERANCE = 0.5;
4
- const SHRINK_THRESHOLD = 0.5;
5
- // Detect MIME type from a URL without fetching the resource.
6
- // Returns a MIME string for data: URLs, extension-based URLs, or null for blob: / unknown URLs.
7
3
  export const detectMimeFromUrl = (url) => {
8
4
  if (url.startsWith('data:')) {
9
5
  return Base64Helper.getMimeType(url);
@@ -12,198 +8,47 @@ export const detectMimeFromUrl = (url) => {
12
8
  return null;
13
9
  }
14
10
  try {
15
- const pathname = url.includes('://') ? new URL(url).pathname : url.split('?')[0].split('#')[0];
16
- const lastDot = pathname.lastIndexOf('.');
17
- if (lastDot === -1) {
18
- return null;
19
- }
20
- return mime.getType(pathname.slice(lastDot + 1).toLowerCase()) ?? null;
11
+ const path = url.includes('://') ? new URL(url).pathname : url.split('?')[0].split('#')[0];
12
+ const dot = path.lastIndexOf('.');
13
+ return dot === -1 ? null : (mime.getType(path.slice(dot + 1).toLowerCase()) ?? null);
21
14
  }
22
15
  catch {
23
16
  return null;
24
17
  }
25
18
  };
26
- // Extract the uniform scale factor from a CSS transform matrix [a, b, c, d, e, f].
27
- // Works for any combination of scale + rotation: scale = sqrt(a² + b²).
28
- export const computeImageScale = (transform) => {
29
- return Math.sqrt(transform[0] ** 2 + transform[1] ** 2) || 1;
30
- };
31
- // Compute the natural-resolution output size for a displayed region.
32
- // Divides the display dimensions by the image's transform scale so that
33
- // the exported image matches the source pixel density.
34
- export const computeNaturalOutputSize = (params) => {
35
- const { displayWidth, displayHeight, imageScale } = params;
36
- return {
37
- width: Math.round(displayWidth / imageScale),
38
- height: Math.round(displayHeight / imageScale)
39
- };
40
- };
41
- // Compute proposed bounding rect mathematically (no DOM style manipulation).
42
- // CSS default transform-origin is 50% 50%, so AABB of a w×h element under matrix(a,b,c,d,e,f):
43
- // left = e + w/2 - |a|·w/2 - |c|·h/2
44
- // right = e + w/2 + |a|·w/2 + |c|·h/2 (and similarly for top/bottom with b,d,f)
45
- // We only need the DIFFERENCE between old and new AABB to predict the proposed viewport rect.
46
- export const computeLocalAABB = (params) => {
47
- const { matrix, width, height } = params;
48
- const halfWidth = width / 2;
49
- const halfHeight = height / 2;
50
- return {
51
- left: matrix[4] + halfWidth - Math.abs(matrix[0]) * halfWidth - Math.abs(matrix[2]) * halfHeight,
52
- top: matrix[5] + halfHeight - Math.abs(matrix[1]) * halfWidth - Math.abs(matrix[3]) * halfHeight,
53
- right: matrix[4] + halfWidth + Math.abs(matrix[0]) * halfWidth + Math.abs(matrix[2]) * halfHeight,
54
- bottom: matrix[5] + halfHeight + Math.abs(matrix[1]) * halfWidth + Math.abs(matrix[3]) * halfHeight
55
- };
56
- };
57
- export const rectCoversRect = (inner, outer, tolerance = 1) => inner.left <= outer.left + tolerance &&
58
- inner.top <= outer.top + tolerance &&
59
- inner.right >= outer.right - tolerance &&
60
- inner.bottom >= outer.bottom - tolerance;
61
- export const handleCoverCheck = (params) => {
62
- const { event, image, canvas } = params;
63
- const canvasRect = canvas.getBoundingClientRect();
64
- const currentRect = image.getBoundingClientRect();
65
- // Only restrict when image currently covers canvas.
66
- // During internal ops ($resetTransform, $center, $rotate) it doesn't — handler skips.
67
- if (!rectCoversRect(currentRect, canvasRect)) {
68
- return;
69
- }
70
- const { matrix, oldMatrix } = event.detail;
71
- // Allow rotations — always followed by $center() which restores coverage.
72
- // Compare angles (atan2(b,a)) instead of raw b/c values because zoom also
73
- // changes b and c when the image is already rotated.
74
- const oldAngle = Math.atan2(oldMatrix[1], oldMatrix[0]);
75
- const newAngle = Math.atan2(matrix[1], matrix[0]);
76
- if (Math.abs(newAngle - oldAngle) > 0.001) {
77
- return;
78
- }
79
- const imageWidth = image.offsetWidth;
80
- const imageHeight = image.offsetHeight;
81
- const oldAABB = computeLocalAABB({ matrix: oldMatrix, width: imageWidth, height: imageHeight });
82
- const newAABB = computeLocalAABB({ matrix, width: imageWidth, height: imageHeight });
83
- const proposedRect = {
84
- left: currentRect.left + (newAABB.left - oldAABB.left),
85
- top: currentRect.top + (newAABB.top - oldAABB.top),
86
- right: currentRect.right + (newAABB.right - oldAABB.right),
87
- bottom: currentRect.bottom + (newAABB.bottom - oldAABB.bottom)
88
- };
89
- // Sub-pixel tolerance: a stricter check re-enters on the clamped transform this handler issues itself.
90
- if (rectCoversRect(proposedRect, canvasRect, COVERAGE_TOLERANCE)) {
91
- return;
92
- }
93
- event.preventDefault();
94
- const proposedWidth = proposedRect.right - proposedRect.left;
95
- const proposedHeight = proposedRect.bottom - proposedRect.top;
96
- const canvasWidth = canvasRect.right - canvasRect.left;
97
- const canvasHeight = canvasRect.bottom - canvasRect.top;
98
- const oldAABBWidth = oldAABB.right - oldAABB.left;
99
- const oldAABBHeight = oldAABB.bottom - oldAABB.top;
100
- const newAABBWidth = newAABB.right - newAABB.left;
101
- const newAABBHeight = newAABB.bottom - newAABB.top;
102
- const shrinksImage = newAABBWidth < oldAABBWidth - SHRINK_THRESHOLD || newAABBHeight < oldAABBHeight - SHRINK_THRESHOLD;
103
- let [scaleX, skewY, skewX, scaleY] = matrix;
104
- let clampedRect = proposedRect;
105
- if (proposedWidth < canvasWidth - 0.5 || proposedHeight < canvasHeight - 0.5) {
106
- // Rescaling anything but a shrink would turn a pan, whose proposal is the same size, into an upscale.
107
- if (!shrinksImage) {
108
- return;
109
- }
110
- const coverScale = Math.max(canvasWidth / proposedWidth, canvasHeight / proposedHeight);
111
- if (!Number.isFinite(coverScale)) {
112
- return;
113
- }
114
- scaleX *= coverScale;
115
- skewY *= coverScale;
116
- skewX *= coverScale;
117
- scaleY *= coverScale;
118
- const halfWidth = (proposedWidth * coverScale) / 2;
119
- const halfHeight = (proposedHeight * coverScale) / 2;
120
- const centerX = (proposedRect.left + proposedRect.right) / 2;
121
- const centerY = (proposedRect.top + proposedRect.bottom) / 2;
122
- clampedRect = { left: centerX - halfWidth, top: centerY - halfHeight, right: centerX + halfWidth, bottom: centerY + halfHeight };
123
- }
124
- let clampedTx = matrix[4];
125
- let clampedTy = matrix[5];
126
- if (clampedRect.left > canvasRect.left) {
127
- clampedTx -= clampedRect.left - canvasRect.left;
128
- }
129
- else if (clampedRect.right < canvasRect.right) {
130
- clampedTx += canvasRect.right - clampedRect.right;
131
- }
132
- if (clampedRect.top > canvasRect.top) {
133
- clampedTy -= clampedRect.top - canvasRect.top;
134
- }
135
- else if (clampedRect.bottom < canvasRect.bottom) {
136
- clampedTy += canvasRect.bottom - clampedRect.bottom;
137
- }
138
- image.$setTransform(scaleX, skewY, skewX, scaleY, clampedTx, clampedTy);
139
- };
140
- export const fitInContainer = (params) => {
141
- const { ratio, containerWidth, containerHeight } = params;
142
- if (ratio > containerWidth / containerHeight) {
143
- return { width: containerWidth, height: containerWidth / ratio };
144
- }
145
- return { width: containerHeight * ratio, height: containerHeight };
146
- };
147
- // Same limits cropper-canvas declares in its shadow style, where they clamp one axis at a time and break the canvas ratio.
148
- const MIN_CANVAS_WIDTH = 200;
149
- const MIN_CANVAS_HEIGHT = 100;
19
+ const ALPHA_PROBE_PX = 512;
150
20
  /**
151
- * Applies the minimum canvas size by scaling the whole rect, bounded by `maxWidth` / `maxHeight`, and rounds to whole pixels.
152
- * The minimums are soft: a container smaller than them wins, and the returned canvas is smaller than `MIN_CANVAS_WIDTH` / `MIN_CANVAS_HEIGHT`.
21
+ * Whether the picture has any non-opaque pixel. Probed on a copy downscaled to `ALPHA_PROBE_PX` — downscaling averages
22
+ * alpha, so even thin transparent details still leave a sub-255 pixel, at a fraction of a full-resolution read.
153
23
  */
154
- export const resolveCanvasGeometry = (params) => {
155
- const { aspectRatio, width, height, maxWidth, maxHeight } = params;
156
- if (width <= 0 || height <= 0) {
157
- return { aspectRatio, width: 0, height: 0 };
158
- }
159
- const ratio = width / height;
160
- const floorScale = Math.max(MIN_CANVAS_WIDTH / width, MIN_CANVAS_HEIGHT / height, 1);
161
- const boundedWidth = Math.min(width * floorScale, maxWidth, maxHeight * ratio);
162
- // A zero side reads as "unset" in the template, which falls the canvas back to its CSS size and desyncs it from this geometry.
163
- return { aspectRatio, width: Math.max(1, Math.round(boundedWidth)), height: Math.max(1, Math.round(boundedWidth / ratio)) };
164
- };
165
- export const handleSelectionBoundary = (params) => {
166
- const { event, selection, canvas } = params;
167
- const { x, y, width, height } = event.detail;
168
- const { width: canvasWidth, height: canvasHeight } = canvas.getBoundingClientRect();
169
- if (x >= 0 && y >= 0 && x + width <= canvasWidth && y + height <= canvasHeight) {
170
- return;
171
- }
172
- event.preventDefault();
173
- const isMove = Math.abs(width - selection.width) < 1 && Math.abs(height - selection.height) < 1;
174
- if (isMove) {
175
- selection.$change(Math.max(0, Math.min(x, canvasWidth - width)), Math.max(0, Math.min(y, canvasHeight - height)), width, height);
176
- return;
177
- }
178
- if (selection.aspectRatio) {
179
- return;
180
- }
181
- const overLeft = x < 0;
182
- const overTop = y < 0;
183
- const overRight = x + width > canvasWidth;
184
- const overBottom = y + height > canvasHeight;
185
- if (+overLeft + +overTop + +overRight + +overBottom !== 1) {
186
- return;
187
- }
188
- let clampedX = x;
189
- let clampedY = y;
190
- let clampedWidth = width;
191
- let clampedHeight = height;
192
- if (overLeft) {
193
- clampedX = 0;
194
- clampedWidth = x + width;
195
- }
196
- else if (overTop) {
197
- clampedY = 0;
198
- clampedHeight = y + height;
199
- }
200
- else if (overRight) {
201
- clampedWidth = canvasWidth - x;
202
- }
203
- else if (overBottom) {
204
- clampedHeight = canvasHeight - y;
205
- }
206
- if (clampedWidth > 0 && clampedHeight > 0) {
207
- selection.$change(clampedX, clampedY, clampedWidth, clampedHeight);
24
+ export const detectAlpha = (image, mimeType) => {
25
+ if (mimeType === 'image/jpeg') {
26
+ return false;
27
+ }
28
+ const scale = Math.min(1, ALPHA_PROBE_PX / Math.max(image.naturalWidth, image.naturalHeight));
29
+ const width = Math.max(1, Math.round(image.naturalWidth * scale));
30
+ const height = Math.max(1, Math.round(image.naturalHeight * scale));
31
+ const canvas = document.createElement('canvas');
32
+ canvas.width = width;
33
+ canvas.height = height;
34
+ const context = canvas.getContext('2d');
35
+ if (!context) {
36
+ return false;
37
+ }
38
+ context.imageSmoothingQuality = 'high';
39
+ context.drawImage(image, 0, 0, width, height);
40
+ const { data } = context.getImageData(0, 0, width, height);
41
+ for (let i = 3; i < data.length; i += 4) {
42
+ if (data[i] < 255) {
43
+ return true;
44
+ }
208
45
  }
46
+ return false;
209
47
  };
48
+ export const loadImage = (src) => new Promise((resolve, reject) => {
49
+ const image = new Image();
50
+ image.crossOrigin = 'anonymous';
51
+ image.onload = () => resolve(image);
52
+ image.onerror = () => reject(new Error(`ImgCropper: failed to load "${src}"`));
53
+ image.src = src;
54
+ });
@@ -1,8 +1,6 @@
1
- import type { CanvasGeometry, DragMode, ImgCropperResult } from './img-cropper-worker.svelte';
2
- import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
3
- export type { CanvasGeometry };
4
- export type CropperMode = 'contain' | 'cover';
5
- export type { DragMode };
1
+ import { type FrameFitMode, type Point, type Rect, type Size } from './frame-geometry';
2
+ export type CropperMode = FrameFitMode;
3
+ export type { Point, Rect };
6
4
  export type ImgCropperRatioOption = {
7
5
  label: string;
8
6
  value: number | null;
@@ -16,48 +14,115 @@ export type ImgCropperDynamicAspectRatio = {
16
14
  supported: number[];
17
15
  allowFreeCrop: true;
18
16
  };
19
- export type { ImgCropperResult };
20
- export type ImgCropperSaveResult = ImgCropperResult;
17
+ export type ImgCropperMargin = {
18
+ block?: number;
19
+ inline?: number;
20
+ };
21
21
  export type ImgCropperOptions = {
22
+ /** The placement rule the picture starts with and `reset()` returns to; Fill / Fit in the toolbar switch it. */
22
23
  mode: CropperMode;
23
24
  aspectRatio?: number | ImgCropperDynamicAspectRatio;
24
25
  fillColor?: string;
25
- showImageShadow?: boolean;
26
+ /** Breathing room between the frame and the stage's own edges. @default `{ block: 64, inline: 64 }` */
27
+ margin?: ImgCropperMargin;
28
+ };
29
+ export type ImgCropperSaveResult = {
30
+ dataUrl: string;
31
+ width: number;
32
+ height: number;
33
+ mimeType: string;
26
34
  };
27
35
  export declare class ImgCropper {
36
+ readonly ratioOptions: ImgCropperRatioOption[] | null;
37
+ readonly margin: Required<ImgCropperMargin>;
28
38
  loading: boolean;
29
- ready: boolean;
39
+ failed: boolean;
40
+ mode: CropperMode;
30
41
  fillColor: string;
31
- readonly mode: CropperMode;
32
- readonly ratioOptions: ImgCropperRatioOption[] | null;
33
- readonly showImageShadow: boolean;
34
- private _worker;
35
- private _originalSrc;
36
- private _defaultGeometry;
42
+ natural: Size | null;
43
+ rotation: 0 | 90 | 180 | 270;
44
+ /** The part of the source (turned by `rotation`) the picture is cut to, in natural pixels; `null` for the whole source. */
45
+ crop: Rect | null;
46
+ source: string;
47
+ free: boolean;
48
+ cropMode: boolean;
49
+ selection: Rect | null;
50
+ aspectRatio: number | null;
51
+ stage: Size;
52
+ box: Rect | null;
53
+ private readonly _initialMode;
54
+ private readonly _initialAspectRatio;
55
+ private _image;
56
+ private _blobUrl;
57
+ private _sourceMime;
58
+ private _sourceHasAlpha;
37
59
  constructor(options: ImgCropperOptions);
38
60
  get showFillColor(): boolean;
61
+ get hasSelection(): boolean;
62
+ /** Whether the picture has transparent pixels — the source's own, or empty space a crop took in past the source's edges. */
63
+ get hasAlpha(): boolean;
64
+ /** The current aspect ratio's label — a preset's own, or `1.78:1` for a custom one; `null` for Original. */
65
+ get ratioLabel(): string | null;
39
66
  get isTransparentFill(): boolean;
40
- get canvasGeometry(): CanvasGeometry;
41
- get aspectRatio(): number | null;
42
- get cropBoxVisible(): boolean;
43
- get dragMode(): DragMode;
44
- init: (params: {
45
- src: string;
46
- cropperImage: CropperImage;
47
- cropperSelection: CropperSelection;
48
- cropperCanvas: CropperCanvas;
49
- selectHandle: HTMLElement | null;
50
- }) => Promise<void>;
67
+ /** The picture's natural size — the crop's, or the whole source's turned by `rotation`. */
68
+ get shownNatural(): Size | null;
69
+ /** The picture's own aspect ratio (post-rotation) — the shape `box` must always keep. */
70
+ get imageAspect(): number | null;
71
+ /** The frame's own aspect ratio — the chosen ratio, or the picture's own when none is chosen. */
72
+ get frameAspect(): number | null;
73
+ get frame(): Rect | null;
74
+ /** Zoom as shown to the user: 1 is the current mode's rest state — filling the frame in `cover`, fitting it in `contain`. */
75
+ get zoom(): number;
76
+ get minZoom(): number;
77
+ get maxZoom(): number;
78
+ /** The mode whose rest state the picture is in right now — filling the frame for `cover`, fitting it for `contain`; `null` once zoomed or free. */
79
+ get restingMode(): CropperMode | null;
80
+ get ready(): boolean;
81
+ private get _fullNatural();
82
+ private get _zoomBase();
83
+ init: (src: string) => Promise<void>;
51
84
  destroy: () => void;
52
- rotate: () => Promise<void>;
85
+ setStage: (size: Size) => void;
86
+ layout: () => void;
87
+ changeAspectRatio: (ratio: number | null) => void;
88
+ setFree: (free: boolean) => void;
89
+ toggleFree: () => void;
90
+ setCropMode: (active: boolean) => void;
91
+ updateSelection: (anchor: Point, point: Point) => void;
92
+ /** Drops a freshly drawn selection that ended up below the minimum size. */
93
+ finishSelection: () => void;
94
+ moveSelection: (delta: Point) => void;
95
+ clearSelection: () => void;
96
+ /**
97
+ * Cuts the picture to the selection — whatever it covers, empty space past the picture included (that turns
98
+ * transparent). The cut piece becomes the picture: the rest of it is gone until `reset()`, and it's laid out afresh,
99
+ * filling the frame — with the Original aspect the frame takes the piece's shape, a fixed aspect already matches it.
100
+ */
101
+ applyCrop: () => void;
102
+ pan: (delta: Point) => void;
103
+ scaleAbout: (factor: number, anchor: Point) => void;
104
+ /** Sets the zoom in the shown units — see `zoom`. */
105
+ setZoom: (zoom: number) => void;
53
106
  zoomIn: () => void;
54
107
  zoomOut: () => void;
55
- reset: () => Promise<void>;
56
- changeAspectRatio: (ratio: number | null) => Promise<void>;
57
- crop: () => Promise<ImgCropperSaveResult | null>;
108
+ /** Switches to `cover` (leaving free position) and fills the frame. */
109
+ fill: () => void;
110
+ /** Switches to `contain` (leaving free position) and fits the picture inside the frame, centered. */
111
+ fit: () => void;
112
+ align: (fx: number, fy: number) => void;
113
+ /** Turns the picture a quarter clockwise (`1`) or counter-clockwise (`-1`). With the Original aspect the frame turns
114
+ * along and keeps what's inside it; a fixed-aspect frame can't turn, so the picture is laid out afresh instead. */
115
+ rotate: (direction: 1 | -1) => void;
116
+ reset: () => void;
117
+ /** Renders the frame to an image, applying a pending selection first; `null` when there's nothing to render or the browser can't. */
58
118
  save: () => Promise<ImgCropperSaveResult | null>;
59
- clearSelection: () => void;
60
- enableCropMode: () => void;
61
- enableMoveMode: () => void;
62
- refit: () => void;
119
+ /**
120
+ * Where the whole turned source lands when the picture is drawn into `picture` — its center and its size on each axis,
121
+ * before `rotation` swaps them. The view and `save()` both place the source through this, so they can't drift apart.
122
+ */
123
+ sourcePlacement: (picture: Rect) => {
124
+ center: Point;
125
+ size: Size;
126
+ } | null;
127
+ private _update;
63
128
  }