@streamscloud/kit 0.62.0 → 0.64.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 (42) hide show
  1. package/dist/core/utils/color-helper.d.ts +4 -0
  2. package/dist/core/utils/color-helper.js +10 -0
  3. package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte +17 -51
  4. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.d.ts +4 -16
  5. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.js +1 -14
  6. package/dist/ui/cropper/image-editor-dialog/index.d.ts +8 -7
  7. package/dist/ui/cropper/image-editor-dialog/index.js +8 -7
  8. package/dist/ui/cropper/image-editor-dialog/types.d.ts +1 -1
  9. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte +149 -181
  10. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte.d.ts +4 -13
  11. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +389 -147
  12. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +13 -39
  13. package/dist/ui/cropper/img-cropper/frame-geometry.d.ts +82 -0
  14. package/dist/ui/cropper/img-cropper/frame-geometry.js +165 -0
  15. package/dist/ui/cropper/img-cropper/img-cropper-localization.d.ts +2 -3
  16. package/dist/ui/cropper/img-cropper/img-cropper-localization.js +12 -19
  17. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.d.ts +14 -5
  18. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.js +127 -33
  19. package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +4 -51
  20. package/dist/ui/cropper/img-cropper/img-cropper-utils.js +35 -190
  21. package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +99 -34
  22. package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +405 -104
  23. package/dist/ui/cropper/img-cropper/index.d.ts +1 -2
  24. package/dist/ui/cropper/img-cropper/index.js +0 -1
  25. package/dist/ui/table/cmp.table-draggable.svelte +2 -2
  26. package/dist/ui/table/cmp.table-draggable.svelte.d.ts +5 -7
  27. package/dist/ui/table/cmp.table.svelte +2 -2
  28. package/dist/ui/table/cmp.table.svelte.d.ts +5 -7
  29. package/dist/ui/table/table-view.svelte.d.ts +2 -2
  30. package/dist/ui/table/table-view.svelte.js +11 -7
  31. package/package.json +1 -3
  32. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte +0 -68
  33. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte.d.ts +0 -21
  34. package/dist/ui/cropper/img-cropper/cropperjs-elements.d.ts +0 -35
  35. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +0 -40
  36. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +0 -193
  37. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.d.ts +0 -7
  38. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +0 -51
  39. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.d.ts +0 -7
  40. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +0 -89
  41. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +0 -42
  42. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.js +0 -1
@@ -8,8 +8,8 @@ type SelectionCallbacks<T> = {
8
8
  };
9
9
  /** Inline width declarations for a column's `th` / `td`: the preferred width plus, when set, the floor the browser must honour. */
10
10
  export declare const columnWidthStyle: <T extends WithId>(column: ResolvedColumn<T>) => string;
11
- /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (`number` = delay in ms, boolean = explicit). */
12
- export declare const createTableView: <T extends WithId>(model: () => TableModel<T>, on: () => SelectionCallbacks<T> | undefined, noItemsPlaceholder?: () => number | boolean | undefined) => {
11
+ /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (on/off gate + anti-flicker delay). */
12
+ export declare const createTableView: <T extends WithId>(model: () => TableModel<T>, on: () => SelectionCallbacks<T> | undefined, noItemsPlaceholderEnabled?: () => boolean | undefined, noItemsPlaceholderDelay?: () => number | undefined) => {
13
13
  readonly showNoItemsPlaceholder: boolean;
14
14
  };
15
15
  export {};
@@ -4,9 +4,8 @@ export const columnWidthStyle = (column) => {
4
4
  const width = column.cssWidth ? `width: ${column.cssWidth};` : 'width: auto;';
5
5
  return column.minCssWidth ? `${width} min-width: ${column.minCssWidth};` : width;
6
6
  };
7
- const NO_ITEMS_PLACEHOLDER_DELAY_MS = 700;
8
- /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (`number` = delay in ms, boolean = explicit). */
9
- export const createTableView = (model, on, noItemsPlaceholder = () => undefined) => {
7
+ /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (on/off gate + anti-flicker delay). */
8
+ export const createTableView = (model, on, noItemsPlaceholderEnabled = () => undefined, noItemsPlaceholderDelay = () => undefined) => {
10
9
  let showNoItemsPlaceholder = $state(false);
11
10
  let timeout = 0;
12
11
  $effect(() => untrack(() => {
@@ -19,20 +18,25 @@ export const createTableView = (model, on, noItemsPlaceholder = () => undefined)
19
18
  return () => subscriptions.forEach((unsubscribe) => unsubscribe());
20
19
  }));
21
20
  $effect(() => {
22
- const decision = noItemsPlaceholder() ?? NO_ITEMS_PLACEHOLDER_DELAY_MS;
23
21
  if (model().items.length) {
24
22
  clearTimeout(timeout);
25
23
  showNoItemsPlaceholder = false;
26
24
  return;
27
25
  }
28
- if (typeof decision === 'boolean') {
26
+ if (noItemsPlaceholderEnabled() === false) {
29
27
  clearTimeout(timeout);
30
- showNoItemsPlaceholder = decision;
28
+ showNoItemsPlaceholder = false;
29
+ return;
30
+ }
31
+ const delay = noItemsPlaceholderDelay() ?? 0;
32
+ if (delay <= 0) {
33
+ clearTimeout(timeout);
34
+ showNoItemsPlaceholder = true;
31
35
  return;
32
36
  }
33
37
  timeout = window.setTimeout(() => {
34
38
  showNoItemsPlaceholder = !model().items.length;
35
- }, decision);
39
+ }, delay);
36
40
  return () => clearTimeout(timeout);
37
41
  });
38
42
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -489,7 +489,6 @@
489
489
  "@urql/core": "^6.0.1",
490
490
  "cleave-zen": "^0.0.17",
491
491
  "colord": "^2.9.3",
492
- "cropperjs": "^2.1.1",
493
492
  "dequal": "^2.0.3",
494
493
  "dompurify": "^3.4.5",
495
494
  "fuse.js": "^7.3.0",
@@ -519,7 +518,6 @@
519
518
  "autoprefixer": "^10.6.1",
520
519
  "cleave-zen": "^0.0.17",
521
520
  "colord": "^2.9.3",
522
- "cropperjs": "^2.1.1",
523
521
  "dequal": "^2.0.3",
524
522
  "dompurify": "^3.4.5",
525
523
  "eslint": "^10.10.0",
@@ -1,68 +0,0 @@
1
- <script lang="ts">import { ImgCropperLocalization } from './img-cropper-localization';
2
- const { cropper } = $props();
3
- const loc = new ImgCropperLocalization();
4
- </script>
5
-
6
- {#if cropper.cropBoxVisible}
7
- <div class="img-cropper-controls">
8
- <button type="button" class="img-cropper-controls__button img-cropper-controls__button--apply" onclick={() => cropper.crop()}>{loc.apply}</button>
9
- <button type="button" class="img-cropper-controls__button img-cropper-controls__button--cancel" onclick={() => cropper.clearSelection()}>
10
- {loc.cancel}
11
- </button>
12
- </div>
13
- {/if}
14
-
15
- <!--
16
- @component
17
- Apply/Cancel controls for the image cropper. Renders conditionally when a crop box is active.
18
- Can be placed anywhere in the layout.
19
-
20
- ### Props
21
- | Prop | Type | Default | Description |
22
- |---|---|---|---|
23
- | `cropper` | `ImgCropper` | — | Cropper controller instance |
24
-
25
- ### CSS Custom Properties
26
- | Property | Description | Default |
27
- |---|---|---|
28
- | `--sc-kit--img-cropper-controls--background` | Panel background | `transparent` |
29
- -->
30
-
31
- <style>.img-cropper-controls {
32
- --_img-cropper-controls--background: var(--sc-kit--img-cropper-controls--background, transparent);
33
- width: 15rem;
34
- padding: var(--sc-kit--space--1);
35
- display: flex;
36
- gap: var(--sc-kit--space--3);
37
- background: var(--_img-cropper-controls--background);
38
- }
39
- .img-cropper-controls__button {
40
- font-size: var(--sc-kit--font-size--sm);
41
- padding: var(--sc-kit--space--1) var(--sc-kit--space--4);
42
- border-radius: var(--sc-kit--radius--sm);
43
- max-width: 8rem;
44
- height: var(--sc-kit--field--height--md);
45
- display: inline-flex;
46
- flex: 1;
47
- justify-content: center;
48
- align-items: center;
49
- cursor: pointer;
50
- transition: background-color var(--sc-kit--duration--base) linear, color var(--sc-kit--duration--base) linear;
51
- }
52
- .img-cropper-controls__button--apply {
53
- color: var(--sc-kit--color--text--on-accent);
54
- background: var(--sc-kit--color--success);
55
- border: 0;
56
- }
57
- .img-cropper-controls__button--apply:hover {
58
- background: var(--sc-kit--color--success--hover);
59
- }
60
- .img-cropper-controls__button--cancel {
61
- color: var(--sc-kit--color--text--primary);
62
- background: var(--sc-kit--color--bg--panel);
63
- border: 1px solid var(--sc-kit--color--border);
64
- box-shadow: var(--sc-kit--shadow--hairline);
65
- }
66
- .img-cropper-controls__button--cancel:hover {
67
- background: var(--sc-kit--color--bg--hover);
68
- }</style>
@@ -1,21 +0,0 @@
1
- import type { ImgCropper } from './img-cropper.svelte';
2
- type Props = {
3
- cropper: ImgCropper;
4
- };
5
- /**
6
- * Apply/Cancel controls for the image cropper. Renders conditionally when a crop box is active.
7
- * Can be placed anywhere in the layout.
8
- *
9
- * ### Props
10
- * | Prop | Type | Default | Description |
11
- * |---|---|---|---|
12
- * | `cropper` | `ImgCropper` | — | Cropper controller instance |
13
- *
14
- * ### CSS Custom Properties
15
- * | Property | Description | Default |
16
- * |---|---|---|
17
- * | `--sc-kit--img-cropper-controls--background` | Panel background | `transparent` |
18
- */
19
- declare const Cmp: import("svelte").Component<Props, {}, "">;
20
- type Cmp = ReturnType<typeof Cmp>;
21
- export default Cmp;
@@ -1,35 +0,0 @@
1
- // Type declarations for CropperJS v2 web components.
2
- // Extends svelteHTML.IntrinsicElements so the Svelte language server
3
- // recognises <cropper-*> tags and their attributes in templates.
4
-
5
- type CamelToKebab<S extends string> = S extends `${infer F}${infer R}`
6
- ? R extends Uncapitalize<R>
7
- ? `${Lowercase<F>}${CamelToKebab<R>}`
8
- : `${Lowercase<F>}-${CamelToKebab<R>}`
9
- : S;
10
-
11
- type CropperElementProps<T extends HTMLElement> = {
12
- [
13
- K in keyof T as K extends keyof HTMLElement
14
- ? never
15
- : K extends `$${string}`
16
- ? never
17
- : T[K] extends (...args: never[]) => unknown
18
- ? never
19
- : K extends string
20
- ? CamelToKebab<K>
21
- : never
22
- ]?: (T[K] extends boolean ? boolean : T[K] extends number ? number | string : string) | null;
23
- } & import('svelte/elements').HTMLAttributes<HTMLElement>;
24
-
25
- declare namespace svelteHTML {
26
- interface IntrinsicElements {
27
- 'cropper-canvas': CropperElementProps<import('cropperjs').CropperCanvas>;
28
- 'cropper-image': CropperElementProps<import('cropperjs').CropperImage>;
29
- 'cropper-shade': CropperElementProps<import('cropperjs').CropperShade>;
30
- 'cropper-handle': CropperElementProps<import('cropperjs').CropperHandle>;
31
- 'cropper-selection': CropperElementProps<import('cropperjs').CropperSelection>;
32
- 'cropper-grid': CropperElementProps<import('cropperjs').CropperGrid>;
33
- 'cropper-crosshair': CropperElementProps<import('cropperjs').CropperCrosshair>;
34
- }
35
- }
@@ -1,40 +0,0 @@
1
- import type { CanvasGeometry, DragMode, ImgCropperResult, ImgCropperWorker, ImgCropperWorkerParams } from './img-cropper-worker.svelte';
2
- import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
3
- export declare abstract class ImgCropperBaseWorker implements ImgCropperWorker {
4
- cropBoxVisible: boolean;
5
- dragMode: DragMode;
6
- canvasGeometry: CanvasGeometry;
7
- destroy: () => void;
8
- protected _image: CropperImage;
9
- protected _selection: CropperSelection;
10
- protected _canvas: CropperCanvas;
11
- protected _selectHandle: HTMLElement | null;
12
- protected _originalSrc: string;
13
- protected _sourceMime: string | null;
14
- protected _visualWidth: number;
15
- protected _visualHeight: number;
16
- protected abstract readonly _centerMode: 'contain' | 'cover';
17
- constructor(params: ImgCropperWorkerParams);
18
- ready: () => Promise<void>;
19
- rotate: () => Promise<void>;
20
- zoomIn: () => void;
21
- zoomOut: () => void;
22
- reset: () => Promise<void>;
23
- crop: (options?: {
24
- fillColor?: string;
25
- }) => Promise<ImgCropperResult | null>;
26
- save: (options?: {
27
- fillColor?: string;
28
- }) => Promise<ImgCropperResult>;
29
- clearSelection: () => void;
30
- refit: () => Promise<void>;
31
- enableCropMode: () => void;
32
- protected _wrapTransformOp: (fn: () => void) => void;
33
- protected _applyMoveMode: () => void;
34
- protected _resolveOutputFormat: (fillColor: string | undefined) => {
35
- mime: string;
36
- quality: number;
37
- };
38
- protected _fitImage: () => Promise<void>;
39
- protected abstract _computeCanvasSize(): void;
40
- }
@@ -1,193 +0,0 @@
1
- import { ColorHelper } from '../../../core/utils';
2
- import { computeImageScale, computeNaturalOutputSize, handleSelectionBoundary } from './img-cropper-utils';
3
- import { tick } from 'svelte';
4
- const OPAQUE_MIME_TYPES = new Set(['image/jpeg', 'image/bmp']);
5
- export class ImgCropperBaseWorker {
6
- cropBoxVisible = $state(false);
7
- dragMode = $state('move');
8
- canvasGeometry = $state.raw({ aspectRatio: null, width: 0, height: 0 });
9
- destroy;
10
- _image;
11
- _selection;
12
- _canvas;
13
- _selectHandle;
14
- _originalSrc;
15
- _sourceMime;
16
- _visualWidth = 0;
17
- _visualHeight = 0;
18
- constructor(params) {
19
- this._originalSrc = params.originalSrc;
20
- this._sourceMime = params.sourceMime;
21
- this._image = params.image;
22
- this._selection = params.selection;
23
- this._canvas = params.canvas;
24
- this._selectHandle = params.selectHandle;
25
- this.canvasGeometry = { aspectRatio: params.aspectRatio, width: 0, height: 0 };
26
- const onSelectionChange = (e) => {
27
- // CropperSelection emits before committing, so a change the boundary handler cancelled never reaches the selection.
28
- const { width, height } = e.defaultPrevented ? this._selection : e.detail;
29
- this.cropBoxVisible = width > 0 && height > 0;
30
- };
31
- let handlingBoundary = false;
32
- const onBoundaryCheck = (e) => {
33
- if (handlingBoundary) {
34
- return;
35
- }
36
- handlingBoundary = true;
37
- try {
38
- handleSelectionBoundary({ event: e, selection: this._selection, canvas: this._canvas });
39
- }
40
- finally {
41
- handlingBoundary = false;
42
- }
43
- };
44
- // Boundary check first — onSelectionChange reads defaultPrevented, which it must not set itself.
45
- this._selection.addEventListener('change', onBoundaryCheck);
46
- this._selection.addEventListener('change', onSelectionChange);
47
- this.destroy = () => {
48
- this._selection.removeEventListener('change', onSelectionChange);
49
- this._selection.removeEventListener('change', onBoundaryCheck);
50
- };
51
- this._applyMoveMode();
52
- }
53
- ready = async () => {
54
- this._image.src = this._originalSrc;
55
- await this._fitImage();
56
- this._selection.$clear();
57
- };
58
- rotate = async () => {
59
- [this._visualWidth, this._visualHeight] = [this._visualHeight, this._visualWidth];
60
- this._computeCanvasSize();
61
- await tick();
62
- this._wrapTransformOp(() => {
63
- this._image.$rotate('90deg');
64
- this._image.$center(this._centerMode);
65
- });
66
- };
67
- zoomIn = () => {
68
- this._image.$zoom(0.1);
69
- };
70
- zoomOut = () => {
71
- this._image.$zoom(-0.1);
72
- };
73
- reset = async () => {
74
- this._selection.$clear();
75
- this._applyMoveMode();
76
- this._image.src = this._originalSrc;
77
- await this._fitImage();
78
- };
79
- crop = async (options) => {
80
- // An empty selection exports a 0x0 canvas, and assigning its "data:," data URL leaves the cropper with an unloadable source.
81
- if (this._selection.width <= 0 || this._selection.height <= 0) {
82
- return null;
83
- }
84
- const fillColor = options?.fillColor;
85
- const imageScale = computeImageScale(this._image.$getTransform());
86
- const outputSize = computeNaturalOutputSize({ displayWidth: this._selection.width, displayHeight: this._selection.height, imageScale });
87
- const canvas = await this._selection.$toCanvas({
88
- width: outputSize.width,
89
- height: outputSize.height,
90
- beforeDraw: (ctx, cvs) => {
91
- if (fillColor && !ColorHelper.isTransparent(fillColor)) {
92
- ctx.fillStyle = fillColor;
93
- ctx.fillRect(0, 0, cvs.width, cvs.height);
94
- }
95
- }
96
- });
97
- const format = this._resolveOutputFormat(fillColor);
98
- const dataUrl = canvas.toDataURL(format.mime, format.quality);
99
- this._applyMoveMode();
100
- this._image.src = dataUrl;
101
- await this._fitImage();
102
- this._selection.$clear();
103
- return { dataUrl, width: canvas.width, height: canvas.height, mimeType: format.mime };
104
- };
105
- save = async (options) => {
106
- const fillColor = options?.fillColor;
107
- const beforeDraw = (ctx, cvs) => {
108
- if (fillColor && !ColorHelper.isTransparent(fillColor)) {
109
- ctx.fillStyle = fillColor;
110
- ctx.fillRect(0, 0, cvs.width, cvs.height);
111
- }
112
- };
113
- // Compute output dimensions before micro-zoom so the result matches
114
- // the source pixel density, not the display size.
115
- const imageScale = computeImageScale(this._image.$getTransform());
116
- // A zero-sized selection exports a 0x0 canvas, whose toDataURL is the unusable "data:," string.
117
- const hasCropBox = this._selection.width > 0 && this._selection.height > 0;
118
- const displayWidth = hasCropBox ? this._selection.width : this._canvas.offsetWidth;
119
- const displayHeight = hasCropBox ? this._selection.height : this._canvas.offsetHeight;
120
- const outputSize = computeNaturalOutputSize({ displayWidth, displayHeight, imageScale });
121
- const toCanvasOptions = { width: outputSize.width, height: outputSize.height, beforeDraw };
122
- // CropperJS $toCanvas exports only the visible portion of the canvas.
123
- // Sub-pixel rounding can leave a 1px transparent edge when the image
124
- // tightly fits the canvas. A micro-zoom nudges the image slightly past
125
- // the boundary so the export captures the full area; the zoom is
126
- // reversed immediately after.
127
- const needsMicroZoom = !hasCropBox;
128
- if (needsMicroZoom) {
129
- // Forward zoom increases coverage — safe without suspending cover check
130
- this._image.$zoom(0.003);
131
- }
132
- const canvas = hasCropBox ? await this._selection.$toCanvas(toCanvasOptions) : await this._canvas.$toCanvas(toCanvasOptions);
133
- if (needsMicroZoom) {
134
- // Reverse zoom shrinks image — must suspend cover check to avoid clamping
135
- this._wrapTransformOp(() => this._image.$zoom(-0.003));
136
- }
137
- const format = this._resolveOutputFormat(fillColor);
138
- const dataUrl = canvas.toDataURL(format.mime, format.quality);
139
- return { dataUrl, width: canvas.width, height: canvas.height, mimeType: format.mime };
140
- };
141
- clearSelection = () => {
142
- this._selection.$clear();
143
- this._applyMoveMode();
144
- };
145
- refit = async () => {
146
- this._selection.$clear();
147
- this._applyMoveMode();
148
- this._computeCanvasSize();
149
- await tick();
150
- this._wrapTransformOp(() => {
151
- this._image.$resetTransform();
152
- this._image.$center(this._centerMode);
153
- });
154
- };
155
- enableCropMode = () => {
156
- this._image.translatable = false;
157
- if (this._selectHandle) {
158
- this._selectHandle.setAttribute('action', 'select');
159
- }
160
- this.dragMode = 'crop';
161
- };
162
- _wrapTransformOp = (fn) => {
163
- fn();
164
- };
165
- _applyMoveMode = () => {
166
- this._image.translatable = true;
167
- if (this._selectHandle) {
168
- this._selectHandle.setAttribute('action', 'move');
169
- }
170
- this.dragMode = 'move';
171
- this.cropBoxVisible = false;
172
- };
173
- _resolveOutputFormat = (fillColor) => {
174
- if (this._sourceMime && OPAQUE_MIME_TYPES.has(this._sourceMime)) {
175
- return { mime: 'image/jpeg', quality: 0.92 };
176
- }
177
- if (fillColor && !ColorHelper.isTransparent(fillColor)) {
178
- return { mime: 'image/jpeg', quality: 0.92 };
179
- }
180
- return { mime: 'image/png', quality: 1 };
181
- };
182
- _fitImage = async () => {
183
- const img = await this._image.$ready();
184
- this._visualWidth = img.naturalWidth;
185
- this._visualHeight = img.naturalHeight;
186
- this._computeCanvasSize();
187
- await tick();
188
- this._wrapTransformOp(() => {
189
- this._image.$resetTransform();
190
- this._image.$center(this._centerMode);
191
- });
192
- };
193
- }
@@ -1,7 +0,0 @@
1
- import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
2
- import type { ImgCropperWorkerParams } from './img-cropper-worker.svelte';
3
- export declare class ImgCropperContainWorker extends ImgCropperBaseWorker {
4
- protected readonly _centerMode: "contain";
5
- constructor(params: ImgCropperWorkerParams);
6
- protected _computeCanvasSize: () => void;
7
- }
@@ -1,51 +0,0 @@
1
- import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
2
- import { fitInContainer, resolveCanvasGeometry } from './img-cropper-utils';
3
- export class ImgCropperContainWorker extends ImgCropperBaseWorker {
4
- _centerMode = 'contain';
5
- constructor(params) {
6
- super(params);
7
- }
8
- _computeCanvasSize = () => {
9
- const container = this._canvas.parentElement;
10
- if (!container) {
11
- return;
12
- }
13
- const containerWidth = container.clientWidth;
14
- const containerHeight = container.clientHeight;
15
- const visualWidth = this._visualWidth;
16
- const visualHeight = this._visualHeight;
17
- const aspectRatio = this.canvasGeometry.aspectRatio;
18
- if (containerWidth <= 0 || containerHeight <= 0 || visualWidth <= 0 || visualHeight <= 0) {
19
- this.canvasGeometry = { aspectRatio, width: 0, height: 0 };
20
- return;
21
- }
22
- if (aspectRatio !== null) {
23
- // Fixed ratio: fit in container, cap at natural image bounds
24
- const fitted = fitInContainer({ ratio: aspectRatio, containerWidth, containerHeight });
25
- const maxWidth = Math.max(visualWidth, visualHeight * aspectRatio);
26
- const width = Math.min(fitted.width, maxWidth);
27
- this.canvasGeometry = resolveCanvasGeometry({ aspectRatio, width, height: width / aspectRatio, maxWidth: fitted.width, maxHeight: containerHeight });
28
- }
29
- else if (visualWidth <= containerWidth && visualHeight <= containerHeight) {
30
- const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
31
- this.canvasGeometry = resolveCanvasGeometry({
32
- aspectRatio,
33
- width: visualWidth,
34
- height: visualHeight,
35
- maxWidth: fitted.width,
36
- maxHeight: containerHeight
37
- });
38
- }
39
- else {
40
- // Free ratio, image too large — scale down to fit
41
- const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
42
- this.canvasGeometry = resolveCanvasGeometry({
43
- aspectRatio,
44
- width: fitted.width,
45
- height: fitted.height,
46
- maxWidth: fitted.width,
47
- maxHeight: containerHeight
48
- });
49
- }
50
- };
51
- }
@@ -1,7 +0,0 @@
1
- import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
2
- import type { ImgCropperWorkerParams } from './img-cropper-worker.svelte';
3
- export declare class ImgCropperCoverWorker extends ImgCropperBaseWorker {
4
- protected readonly _centerMode: "cover";
5
- constructor(params: ImgCropperWorkerParams);
6
- protected _computeCanvasSize: () => void;
7
- }
@@ -1,89 +0,0 @@
1
- import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
2
- import { fitInContainer, handleCoverCheck, resolveCanvasGeometry } from './img-cropper-utils';
3
- export class ImgCropperCoverWorker extends ImgCropperBaseWorker {
4
- _centerMode = 'cover';
5
- constructor(params) {
6
- super(params);
7
- // Dedup: handleCoverCheck calls $setTransform(clamped) which synchronously
8
- // fires another transform event with a matrix that differs by ~0.0001px
9
- // due to floating point. Without this guard CropperJS re-fires the same
10
- // clamped transform endlessly, causing a stack overflow.
11
- let prevMatrix = null;
12
- const onCoverCheck = (e) => {
13
- const { matrix } = e.detail;
14
- if (prevMatrix && matrix.every((v, i) => Math.abs(v - prevMatrix[i]) < 0.01)) {
15
- return;
16
- }
17
- prevMatrix = matrix;
18
- handleCoverCheck({ event: e, image: this._image, canvas: this._canvas });
19
- prevMatrix = null;
20
- };
21
- this._image.addEventListener('transform', onCoverCheck);
22
- this._wrapTransformOp = (fn) => {
23
- this._image.removeEventListener('transform', onCoverCheck);
24
- try {
25
- fn();
26
- }
27
- finally {
28
- this._image.addEventListener('transform', onCoverCheck);
29
- }
30
- };
31
- const baseDestroy = this.destroy;
32
- this.destroy = () => {
33
- baseDestroy();
34
- this._image.removeEventListener('transform', onCoverCheck);
35
- };
36
- }
37
- _computeCanvasSize = () => {
38
- const container = this._canvas.parentElement;
39
- if (!container) {
40
- return;
41
- }
42
- const containerWidth = container.clientWidth;
43
- const containerHeight = container.clientHeight;
44
- const visualWidth = this._visualWidth;
45
- const visualHeight = this._visualHeight;
46
- const aspectRatio = this.canvasGeometry.aspectRatio;
47
- if (containerWidth <= 0 || containerHeight <= 0) {
48
- this.canvasGeometry = { aspectRatio, width: 0, height: 0 };
49
- return;
50
- }
51
- if (aspectRatio !== null) {
52
- const fitted = fitInContainer({ ratio: aspectRatio, containerWidth, containerHeight });
53
- // Cap so the image can cover the canvas without upscaling
54
- const maxWidth = visualWidth > 0 && visualHeight > 0 ? Math.min(visualWidth, visualHeight * aspectRatio) : fitted.width;
55
- const width = Math.min(fitted.width, maxWidth);
56
- this.canvasGeometry = resolveCanvasGeometry({ aspectRatio, width, height: width / aspectRatio, maxWidth: fitted.width, maxHeight: containerHeight });
57
- }
58
- else if (visualWidth <= 0 || visualHeight <= 0) {
59
- this.canvasGeometry = resolveCanvasGeometry({
60
- aspectRatio,
61
- width: containerWidth,
62
- height: containerHeight,
63
- maxWidth: containerWidth,
64
- maxHeight: containerHeight
65
- });
66
- }
67
- else if (visualWidth <= containerWidth && visualHeight <= containerHeight) {
68
- const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
69
- this.canvasGeometry = resolveCanvasGeometry({
70
- aspectRatio,
71
- width: visualWidth,
72
- height: visualHeight,
73
- maxWidth: fitted.width,
74
- maxHeight: containerHeight
75
- });
76
- }
77
- else {
78
- // Fit at image AR; per-axis capping collapses an oversized image to the container AR.
79
- const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
80
- this.canvasGeometry = resolveCanvasGeometry({
81
- aspectRatio,
82
- width: fitted.width,
83
- height: fitted.height,
84
- maxWidth: fitted.width,
85
- maxHeight: containerHeight
86
- });
87
- }
88
- };
89
- }
@@ -1,42 +0,0 @@
1
- import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
2
- export type DragMode = 'move' | 'crop';
3
- export type CanvasGeometry = {
4
- aspectRatio: number | null;
5
- width: number;
6
- height: number;
7
- };
8
- export type ImgCropperWorkerParams = {
9
- originalSrc: string;
10
- sourceMime: string | null;
11
- image: CropperImage;
12
- selection: CropperSelection;
13
- canvas: CropperCanvas;
14
- selectHandle: HTMLElement | null;
15
- aspectRatio: number | null;
16
- };
17
- export type ImgCropperResult = {
18
- dataUrl: string;
19
- width: number;
20
- height: number;
21
- mimeType: string;
22
- };
23
- export interface ImgCropperWorker {
24
- cropBoxVisible: boolean;
25
- dragMode: DragMode;
26
- canvasGeometry: CanvasGeometry;
27
- destroy: () => void;
28
- ready: () => Promise<void>;
29
- rotate: () => void | Promise<void>;
30
- zoomIn: () => void;
31
- zoomOut: () => void;
32
- reset: () => Promise<void>;
33
- crop: (options?: {
34
- fillColor?: string;
35
- }) => Promise<ImgCropperResult | null>;
36
- save: (options?: {
37
- fillColor?: string;
38
- }) => Promise<ImgCropperResult>;
39
- clearSelection: () => void;
40
- enableCropMode: () => void;
41
- refit: () => void | Promise<void>;
42
- }