@streamscloud/kit 0.1.9 → 0.1.11
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.
- package/dist/core/utils/color-helper.d.ts +13 -0
- package/dist/core/utils/color-helper.js +39 -0
- package/dist/core/utils/index.d.ts +1 -0
- package/dist/core/utils/index.js +1 -0
- package/dist/ui/color-picker/cmp.color-picker.svelte +150 -0
- package/dist/ui/color-picker/cmp.color-picker.svelte.d.ts +33 -0
- package/dist/ui/color-picker/cmp.input-stub.svelte +98 -0
- package/dist/ui/color-picker/cmp.input-stub.svelte.d.ts +40 -0
- package/dist/ui/color-picker/color-picker-localization.d.ts +3 -0
- package/dist/ui/color-picker/color-picker-localization.js +12 -0
- package/dist/ui/color-picker/index.d.ts +1 -0
- package/dist/ui/color-picker/index.js +1 -0
- package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte +109 -0
- package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte.d.ts +9 -0
- package/dist/ui/cropper/image-editor-dialog/image-editor-dialog-localization.d.ts +6 -0
- package/dist/ui/cropper/image-editor-dialog/image-editor-dialog-localization.js +33 -0
- package/dist/ui/cropper/image-editor-dialog/index.d.ts +21 -0
- package/dist/ui/cropper/image-editor-dialog/index.js +25 -0
- package/dist/ui/cropper/image-editor-dialog/types.d.ts +17 -0
- package/dist/ui/cropper/image-editor-dialog/types.js +1 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte +67 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte.d.ts +21 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte +228 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte.d.ts +28 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +189 -0
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +56 -0
- package/dist/ui/cropper/img-cropper/cropperjs-elements.d.ts +33 -0
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +43 -0
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +163 -0
- package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.d.ts +7 -0
- package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +39 -0
- package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.d.ts +7 -0
- package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +65 -0
- package/dist/ui/cropper/img-cropper/img-cropper-localization.d.ts +6 -0
- package/dist/ui/cropper/img-cropper/img-cropper-localization.js +33 -0
- package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.d.ts +11 -0
- package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.js +68 -0
- package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +41 -0
- package/dist/ui/cropper/img-cropper/img-cropper-utils.js +145 -0
- package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +43 -0
- package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.js +1 -0
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +75 -0
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +156 -0
- package/dist/ui/cropper/img-cropper/index.d.ts +4 -0
- package/dist/ui/cropper/img-cropper/index.js +4 -0
- package/dist/ui/icon-text/cmp.icon-text.svelte +90 -0
- package/dist/ui/icon-text/cmp.icon-text.svelte.d.ts +39 -0
- package/dist/ui/icon-text/index.d.ts +1 -0
- package/dist/ui/icon-text/index.js +1 -0
- package/package.json +27 -5
|
@@ -0,0 +1,67 @@
|
|
|
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: 0.3125rem;
|
|
35
|
+
display: flex;
|
|
36
|
+
gap: 0.625rem;
|
|
37
|
+
background: var(--_img-cropper-controls--background);
|
|
38
|
+
}
|
|
39
|
+
.img-cropper-controls__button {
|
|
40
|
+
font-size: 0.75rem;
|
|
41
|
+
padding: 0.25rem 1rem;
|
|
42
|
+
border-radius: 0.25rem;
|
|
43
|
+
max-width: 8rem;
|
|
44
|
+
height: 2rem;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
flex: 1;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
align-items: center;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
transition: background-color linear 0.2s, color linear 0.2s;
|
|
51
|
+
}
|
|
52
|
+
.img-cropper-controls__button--apply {
|
|
53
|
+
color: #ffffff;
|
|
54
|
+
background: #0cce6b;
|
|
55
|
+
}
|
|
56
|
+
.img-cropper-controls__button--apply:hover {
|
|
57
|
+
background: #0bbc60;
|
|
58
|
+
}
|
|
59
|
+
.img-cropper-controls__button--cancel {
|
|
60
|
+
color: light-dark(#2e2e2e, #ffffff);
|
|
61
|
+
background: light-dark(#ffffff, #222222);
|
|
62
|
+
border: 1px solid light-dark(#e5e7eb, #242424);
|
|
63
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
64
|
+
}
|
|
65
|
+
.img-cropper-controls__button--cancel:hover {
|
|
66
|
+
background: light-dark(#f9fafb, #242424);
|
|
67
|
+
}</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
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;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<script lang="ts">import { ColorPicker } from '../../color-picker';
|
|
2
|
+
import { Dropdown } from '../../dropdown';
|
|
3
|
+
import { Icon } from '../../icon';
|
|
4
|
+
import { IconText } from '../../icon-text';
|
|
5
|
+
import { ImgCropperToolbarLocalization } from './img-cropper-toolbar-localization';
|
|
6
|
+
import IconArrowMove from '@fluentui/svg-icons/icons/arrow_move_20_regular.svg?raw';
|
|
7
|
+
import IconReset from '@fluentui/svg-icons/icons/arrow_reset_20_regular.svg?raw';
|
|
8
|
+
import IconArrowRotateClockwise from '@fluentui/svg-icons/icons/arrow_rotate_clockwise_20_regular.svg?raw';
|
|
9
|
+
import IconCrop from '@fluentui/svg-icons/icons/crop_20_regular.svg?raw';
|
|
10
|
+
import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
|
|
11
|
+
import IconZoomIn from '@fluentui/svg-icons/icons/zoom_in_20_regular.svg?raw';
|
|
12
|
+
import IconZoomOut from '@fluentui/svg-icons/icons/zoom_out_20_regular.svg?raw';
|
|
13
|
+
const { cropper, children } = $props();
|
|
14
|
+
const loc = new ImgCropperToolbarLocalization();
|
|
15
|
+
const ratioLabel = (option) => (option.value === null ? loc.free : option.label);
|
|
16
|
+
const selectedRatioLabel = $derived.by(() => {
|
|
17
|
+
const found = cropper.ratioOptions?.find((r) => r.value === cropper.aspectRatio);
|
|
18
|
+
return found ? ratioLabel(found) : loc.free;
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="img-cropper-toolbar">
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
class="img-cropper-toolbar__button"
|
|
26
|
+
class:img-cropper-toolbar__button--active={cropper.dragMode === 'move'}
|
|
27
|
+
onclick={() => cropper.enableMoveMode()}>
|
|
28
|
+
<IconText icon={IconArrowMove}>{loc.move}</IconText>
|
|
29
|
+
</button>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="img-cropper-toolbar__button"
|
|
33
|
+
class:img-cropper-toolbar__button--active={cropper.dragMode === 'crop'}
|
|
34
|
+
onclick={() => cropper.enableCropMode()}>
|
|
35
|
+
<IconText icon={IconCrop}>{loc.crop}</IconText>
|
|
36
|
+
</button>
|
|
37
|
+
<button type="button" class="img-cropper-toolbar__button" onclick={() => cropper.zoomIn()}>
|
|
38
|
+
<IconText icon={IconZoomIn}>{loc.zoomIn}</IconText>
|
|
39
|
+
</button>
|
|
40
|
+
<button type="button" class="img-cropper-toolbar__button" onclick={() => cropper.zoomOut()}>
|
|
41
|
+
<IconText icon={IconZoomOut}>{loc.zoomOut}</IconText>
|
|
42
|
+
</button>
|
|
43
|
+
<button type="button" class="img-cropper-toolbar__button" onclick={() => cropper.rotate()}>
|
|
44
|
+
<IconText icon={IconArrowRotateClockwise}>{loc.rotate}</IconText>
|
|
45
|
+
</button>
|
|
46
|
+
<button type="button" class="img-cropper-toolbar__button img-cropper-toolbar__button--reset" onclick={() => cropper.reset()}>
|
|
47
|
+
<IconText icon={IconReset}>{loc.reset}</IconText>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
{#if cropper.ratioOptions}
|
|
51
|
+
<div class="img-cropper-toolbar__ratio">
|
|
52
|
+
<span class="img-cropper-toolbar__label">{loc.ratio}</span>
|
|
53
|
+
<Dropdown position="top">
|
|
54
|
+
{#snippet trigger()}
|
|
55
|
+
<div class="img-cropper-toolbar__ratio-trigger">{selectedRatioLabel}</div>
|
|
56
|
+
{/snippet}
|
|
57
|
+
<div class="img-cropper-toolbar__ratio-menu">
|
|
58
|
+
{#each cropper.ratioOptions as option (String(option.value))}
|
|
59
|
+
<button type="button" class="img-cropper-toolbar__ratio-item" onclick={() => cropper.changeAspectRatio(option.value)}>
|
|
60
|
+
{ratioLabel(option)}
|
|
61
|
+
</button>
|
|
62
|
+
{/each}
|
|
63
|
+
</div>
|
|
64
|
+
</Dropdown>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
{#if cropper.showFillColor}
|
|
69
|
+
<div class="img-cropper-toolbar__fill">
|
|
70
|
+
<span class="img-cropper-toolbar__label">{loc.fill}</span>
|
|
71
|
+
<ColorPicker value={cropper.fillColor} on={{ change: (v) => (cropper.fillColor = v) }}>
|
|
72
|
+
<div
|
|
73
|
+
class="img-cropper-toolbar__fill-swatch"
|
|
74
|
+
class:img-cropper-toolbar__fill-swatch--empty={cropper.isTransparentFill}
|
|
75
|
+
style:background-color={cropper.isTransparentFill ? undefined : cropper.fillColor}>
|
|
76
|
+
</div>
|
|
77
|
+
</ColorPicker>
|
|
78
|
+
{#if !cropper.isTransparentFill}
|
|
79
|
+
<button type="button" class="img-cropper-toolbar__fill-reset" onclick={() => (cropper.fillColor = '')}>
|
|
80
|
+
<Icon src={IconDismiss} />
|
|
81
|
+
</button>
|
|
82
|
+
{/if}
|
|
83
|
+
</div>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
{#if children}
|
|
87
|
+
{@render children()}
|
|
88
|
+
{/if}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!--
|
|
92
|
+
@component
|
|
93
|
+
Toolbar with image manipulation buttons: Move, Crop, Zoom In/Out, Rotate, Reset.
|
|
94
|
+
Shows aspect ratio dropdown when `cropper.ratioOptions` is set, and fill color
|
|
95
|
+
picker when `cropper.showFillColor` is true (derived from `mode === 'contain'`).
|
|
96
|
+
|
|
97
|
+
### Props
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
101
|
+
| `children` | `Snippet` | — | Extra controls rendered at the end |
|
|
102
|
+
|
|
103
|
+
### CSS Custom Properties
|
|
104
|
+
| Property | Description | Default |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `--sc-kit--img-cropper-toolbar--color` | Button text/icon color | `light-dark(gray-800, white)` |
|
|
107
|
+
| `--sc-kit--img-cropper-toolbar--background` | Button background | `light-dark(white, dark-700)` |
|
|
108
|
+
| `--sc-kit--img-cropper-toolbar--border-color` | Button border color | `light-dark(neutral-200, dark-600)` |
|
|
109
|
+
| `--sc-kit--img-cropper-toolbar--active-color` | Active button accent color | `light-dark(primary-500, primary-400)` |
|
|
110
|
+
-->
|
|
111
|
+
|
|
112
|
+
<style>.img-cropper-toolbar {
|
|
113
|
+
--_img-cropper-toolbar--color: var(--sc-kit--img-cropper-toolbar--color, light-dark(#2e2e2e, #ffffff));
|
|
114
|
+
--_img-cropper-toolbar--background: var(--sc-kit--img-cropper-toolbar--background, light-dark(#ffffff, #222222));
|
|
115
|
+
--_img-cropper-toolbar--border-color: var(--sc-kit--img-cropper-toolbar--border-color, light-dark(#e5e7eb, #242424));
|
|
116
|
+
--_img-cropper-toolbar--active-color: var(
|
|
117
|
+
--sc-kit--img-cropper-toolbar--active-color,
|
|
118
|
+
light-dark(#144ab0, #5a8dec)
|
|
119
|
+
);
|
|
120
|
+
container-type: inline-size;
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
min-width: 0;
|
|
124
|
+
gap: 0.5rem;
|
|
125
|
+
--sc-kit--icon-text--icon--size: 1rem;
|
|
126
|
+
}
|
|
127
|
+
.img-cropper-toolbar__button {
|
|
128
|
+
font-size: 0.75rem;
|
|
129
|
+
border-radius: 0.25rem;
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
border: 1px solid var(--_img-cropper-toolbar--border-color);
|
|
132
|
+
color: var(--_img-cropper-toolbar--color);
|
|
133
|
+
background: var(--_img-cropper-toolbar--background);
|
|
134
|
+
min-width: 5rem;
|
|
135
|
+
padding: 0.25rem 0.5rem;
|
|
136
|
+
display: flex;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
transition: background-color linear 0.2s, color linear 0.2s, border-color linear 0.2s;
|
|
140
|
+
}
|
|
141
|
+
.img-cropper-toolbar__button:hover, .img-cropper-toolbar__button--active {
|
|
142
|
+
color: var(--_img-cropper-toolbar--active-color);
|
|
143
|
+
border-color: var(--_img-cropper-toolbar--active-color);
|
|
144
|
+
}
|
|
145
|
+
.img-cropper-toolbar__button--reset {
|
|
146
|
+
margin-left: 0.5rem;
|
|
147
|
+
}
|
|
148
|
+
.img-cropper-toolbar__button {
|
|
149
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
150
|
+
}
|
|
151
|
+
@container (width < 53.125rem) {
|
|
152
|
+
.img-cropper-toolbar__button {
|
|
153
|
+
min-width: auto;
|
|
154
|
+
padding: 0.25rem;
|
|
155
|
+
--sc-kit--icon-text--text--display: none;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
.img-cropper-toolbar__label {
|
|
159
|
+
font-size: 0.625rem;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
margin-right: 0.5rem;
|
|
162
|
+
}
|
|
163
|
+
.img-cropper-toolbar__ratio {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
margin-left: 1rem;
|
|
167
|
+
}
|
|
168
|
+
.img-cropper-toolbar__ratio-trigger {
|
|
169
|
+
padding: 0.25rem;
|
|
170
|
+
border-radius: 0.125rem;
|
|
171
|
+
border: 1px solid var(--_img-cropper-toolbar--border-color);
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
font-size: 0.75rem;
|
|
174
|
+
white-space: nowrap;
|
|
175
|
+
}
|
|
176
|
+
.img-cropper-toolbar__ratio-menu {
|
|
177
|
+
background: var(--_img-cropper-toolbar--background);
|
|
178
|
+
border: 1px solid var(--_img-cropper-toolbar--border-color);
|
|
179
|
+
border-radius: 0.25rem;
|
|
180
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
181
|
+
padding: 0.25rem 0;
|
|
182
|
+
}
|
|
183
|
+
.img-cropper-toolbar__ratio-item {
|
|
184
|
+
display: block;
|
|
185
|
+
width: 100%;
|
|
186
|
+
padding: 0.375rem 0.75rem;
|
|
187
|
+
border: none;
|
|
188
|
+
background: none;
|
|
189
|
+
text-align: left;
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
font-size: 0.75rem;
|
|
192
|
+
color: var(--_img-cropper-toolbar--color);
|
|
193
|
+
}
|
|
194
|
+
.img-cropper-toolbar__ratio-item:hover {
|
|
195
|
+
background: light-dark(#f9fafb, #242424);
|
|
196
|
+
}
|
|
197
|
+
.img-cropper-toolbar__fill {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 0.5rem;
|
|
201
|
+
margin-left: 1rem;
|
|
202
|
+
}
|
|
203
|
+
.img-cropper-toolbar__fill-swatch {
|
|
204
|
+
width: 3.125rem;
|
|
205
|
+
height: 1.625rem;
|
|
206
|
+
border-radius: 0.25rem;
|
|
207
|
+
border: 1px solid var(--_img-cropper-toolbar--border-color);
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
}
|
|
210
|
+
.img-cropper-toolbar__fill-swatch--empty {
|
|
211
|
+
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%);
|
|
212
|
+
background-size: 8px 8px;
|
|
213
|
+
background-position: 0 0, 0 4px, 4px -4px, -4px 0;
|
|
214
|
+
}
|
|
215
|
+
.img-cropper-toolbar__fill-reset {
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
color: var(--_img-cropper-toolbar--color);
|
|
221
|
+
background: none;
|
|
222
|
+
border: none;
|
|
223
|
+
padding: 0;
|
|
224
|
+
--sc-kit--icon--size: 1rem;
|
|
225
|
+
}
|
|
226
|
+
.img-cropper-toolbar__fill-reset:hover {
|
|
227
|
+
color: var(--_img-cropper-toolbar--active-color);
|
|
228
|
+
}</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ImgCropper } from './img-cropper.svelte';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
cropper: ImgCropper;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Toolbar with image manipulation buttons: Move, Crop, Zoom In/Out, Rotate, Reset.
|
|
9
|
+
* Shows aspect ratio dropdown when `cropper.ratioOptions` is set, and fill color
|
|
10
|
+
* picker when `cropper.showFillColor` is true (derived from `mode === 'contain'`).
|
|
11
|
+
*
|
|
12
|
+
* ### Props
|
|
13
|
+
* | Prop | Type | Default | Description |
|
|
14
|
+
* |---|---|---|---|
|
|
15
|
+
* | `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
16
|
+
* | `children` | `Snippet` | — | Extra controls rendered at the end |
|
|
17
|
+
*
|
|
18
|
+
* ### CSS Custom Properties
|
|
19
|
+
* | Property | Description | Default |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | `--sc-kit--img-cropper-toolbar--color` | Button text/icon color | `light-dark(gray-800, white)` |
|
|
22
|
+
* | `--sc-kit--img-cropper-toolbar--background` | Button background | `light-dark(white, dark-700)` |
|
|
23
|
+
* | `--sc-kit--img-cropper-toolbar--border-color` | Button border color | `light-dark(neutral-200, dark-600)` |
|
|
24
|
+
* | `--sc-kit--img-cropper-toolbar--active-color` | Active button accent color | `light-dark(primary-500, primary-400)` |
|
|
25
|
+
*/
|
|
26
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
28
|
+
export default Cmp;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">import { Toastr } from '../../../core/toastr';
|
|
2
|
+
import { Loading } from '../../loading';
|
|
3
|
+
import { default as ImgCropperControls } from './cmp.img-cropper-controls.svelte';
|
|
4
|
+
import { ImgCropperLocalization } from './img-cropper-localization';
|
|
5
|
+
const { src, cropper, showControls = true } = $props();
|
|
6
|
+
const loc = new ImgCropperLocalization();
|
|
7
|
+
let cropperLoaded = $state(false);
|
|
8
|
+
$effect(() => {
|
|
9
|
+
const loadCropper = async () => {
|
|
10
|
+
try {
|
|
11
|
+
await import('cropperjs');
|
|
12
|
+
cropperLoaded = true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
Toastr.error(loc.initCropperError);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
void loadCropper();
|
|
19
|
+
});
|
|
20
|
+
const initCanvas = (canvasEl) => {
|
|
21
|
+
const imageEl = canvasEl.querySelector('cropper-image');
|
|
22
|
+
const selectionEl = canvasEl.querySelector('cropper-selection');
|
|
23
|
+
const selectHandle = canvasEl.querySelector('cropper-handle[action="select"]');
|
|
24
|
+
if (imageEl && selectionEl) {
|
|
25
|
+
const initCropper = async () => {
|
|
26
|
+
try {
|
|
27
|
+
await cropper.init({
|
|
28
|
+
src,
|
|
29
|
+
cropperImage: imageEl,
|
|
30
|
+
cropperSelection: selectionEl,
|
|
31
|
+
cropperCanvas: canvasEl,
|
|
32
|
+
selectHandle
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
Toastr.error(loc.imageLoadError);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
void initCropper();
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
destroy: () => {
|
|
43
|
+
cropper.destroy();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const observeResize = (el) => {
|
|
48
|
+
const observer = new ResizeObserver(() => {
|
|
49
|
+
if (cropper.ready) {
|
|
50
|
+
cropper.refit();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
observer.observe(el);
|
|
54
|
+
return { destroy: () => observer.disconnect() };
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class="img-cropper" use:observeResize style:--img-cropper--fill-color={cropper.fillColor || undefined}>
|
|
59
|
+
{#if cropper.loading || !cropper.ready}
|
|
60
|
+
<Loading positionAbsoluteCenter={true} />
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
{#if cropperLoaded}
|
|
64
|
+
<cropper-canvas
|
|
65
|
+
class="img-cropper__canvas"
|
|
66
|
+
class:img-cropper__canvas--visible={cropper.ready}
|
|
67
|
+
class:img-cropper__canvas--shadow={cropper.showImageShadow}
|
|
68
|
+
style:width={cropper.canvasGeometry.width ? `${cropper.canvasGeometry.width}px` : undefined}
|
|
69
|
+
style:height={cropper.canvasGeometry.height ? `${cropper.canvasGeometry.height}px` : undefined}
|
|
70
|
+
use:initCanvas
|
|
71
|
+
background={(cropper.showFillColor && cropper.isTransparentFill) || undefined}>
|
|
72
|
+
<cropper-image crossorigin={cropper.corsMode === 'native' ? 'anonymous' : null} initial-center-size={cropper.mode} scalable translatable rotatable>
|
|
73
|
+
</cropper-image>
|
|
74
|
+
<cropper-shade hidden></cropper-shade>
|
|
75
|
+
<cropper-handle action="select" plain></cropper-handle>
|
|
76
|
+
<cropper-selection movable resizable aspect-ratio={cropper.aspectRatio ?? undefined}>
|
|
77
|
+
<cropper-grid role="grid" bordered covered></cropper-grid>
|
|
78
|
+
<cropper-crosshair centered></cropper-crosshair>
|
|
79
|
+
<cropper-handle action="move" theme-color="rgba(255,255,255,0.35)"></cropper-handle>
|
|
80
|
+
<cropper-handle action="n-resize"></cropper-handle>
|
|
81
|
+
<cropper-handle action="e-resize"></cropper-handle>
|
|
82
|
+
<cropper-handle action="s-resize"></cropper-handle>
|
|
83
|
+
<cropper-handle action="w-resize"></cropper-handle>
|
|
84
|
+
<cropper-handle action="ne-resize"></cropper-handle>
|
|
85
|
+
<cropper-handle action="nw-resize"></cropper-handle>
|
|
86
|
+
<cropper-handle action="se-resize"></cropper-handle>
|
|
87
|
+
<cropper-handle action="sw-resize"></cropper-handle>
|
|
88
|
+
</cropper-selection>
|
|
89
|
+
</cropper-canvas>
|
|
90
|
+
|
|
91
|
+
{#if cropper.showImageShadow && cropper.ready && cropper.canvasGeometry.width > 0}
|
|
92
|
+
<div class="img-cropper__shadow" style:width="{cropper.canvasGeometry.width}px" style:height="{cropper.canvasGeometry.height}px"></div>
|
|
93
|
+
{/if}
|
|
94
|
+
{/if}
|
|
95
|
+
|
|
96
|
+
{#if showControls}
|
|
97
|
+
<div class="img-cropper__controls">
|
|
98
|
+
<ImgCropperControls cropper={cropper} />
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!--
|
|
104
|
+
@component
|
|
105
|
+
Image cropper view based on CropperJS v2 web components.
|
|
106
|
+
|
|
107
|
+
Renders a canvas with crop selection, rotation, zoom, and reset capabilities.
|
|
108
|
+
The container fills 100% width and height of its parent; the image is centered inside.
|
|
109
|
+
The component itself uses `overflow: visible` — the parent element should set
|
|
110
|
+
`overflow: hidden` to clip the shadow overlay at its boundaries.
|
|
111
|
+
|
|
112
|
+
#### Modes
|
|
113
|
+
|
|
114
|
+
The behavior is determined by the `ImgCropper` instance passed via the `cropper` prop:
|
|
115
|
+
|
|
116
|
+
- **`cover`** — the image always fills the canvas completely. The crop selection
|
|
117
|
+
defines the visible area. After save, the result is the selected region at
|
|
118
|
+
natural image resolution.
|
|
119
|
+
- **`contain`** — the image is fitted inside the canvas with optional fill color
|
|
120
|
+
for empty space. After crop, the canvas shrinks to match the cropped area's
|
|
121
|
+
aspect ratio. After save, the result includes the fill color background.
|
|
122
|
+
|
|
123
|
+
#### Aspect ratio
|
|
124
|
+
|
|
125
|
+
Aspect ratio is configured on the `ImgCropper` instance via the `aspectRatio` option:
|
|
126
|
+
|
|
127
|
+
- **Fixed number** (e.g. `16/9`) — locks the crop selection to this ratio.
|
|
128
|
+
- **Dynamic object** (`ImgCropperDynamicAspectRatio`) — provides a list of `supported`
|
|
129
|
+
ratios and an optional `initial` value. The toolbar renders a ratio dropdown when
|
|
130
|
+
multiple options are available. `allowFreeCrop: true` adds a free-ratio option
|
|
131
|
+
that lets the user draw an unconstrained selection.
|
|
132
|
+
- **Omitted** — defaults to free crop (no ratio constraint on the selection).
|
|
133
|
+
|
|
134
|
+
#### Shadow overlay
|
|
135
|
+
|
|
136
|
+
When `showImageShadow` is enabled on the cropper instance, a dark semi-transparent
|
|
137
|
+
`box-shadow` is applied directly to the `<cropper-canvas>` element, extending beyond
|
|
138
|
+
the canvas bounds so the surrounding area is dimmed. The parent element should set
|
|
139
|
+
`overflow: hidden` to clip the shadow at its boundaries.
|
|
140
|
+
|
|
141
|
+
### Props
|
|
142
|
+
| Prop | Type | Default | Description |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| `src` | `string` | — | Image source URL |
|
|
145
|
+
| `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
146
|
+
| `showControls` | `boolean` | `true` | Show built-in Apply/Cancel crop controls |
|
|
147
|
+
|
|
148
|
+
### CSS Custom Properties
|
|
149
|
+
None — canvas background, shadow, and sizing are controlled by the `ImgCropper` instance.
|
|
150
|
+
-->
|
|
151
|
+
|
|
152
|
+
<style>.img-cropper {
|
|
153
|
+
--_fill-color: var(--img-cropper--fill-color);
|
|
154
|
+
position: relative;
|
|
155
|
+
width: 100%;
|
|
156
|
+
height: 100%;
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
align-items: center;
|
|
160
|
+
overflow: visible;
|
|
161
|
+
}
|
|
162
|
+
.img-cropper__canvas {
|
|
163
|
+
width: 100%;
|
|
164
|
+
height: 100%;
|
|
165
|
+
background-color: var(--_fill-color);
|
|
166
|
+
visibility: hidden;
|
|
167
|
+
}
|
|
168
|
+
.img-cropper__canvas--visible {
|
|
169
|
+
visibility: visible;
|
|
170
|
+
}
|
|
171
|
+
.img-cropper__canvas--shadow {
|
|
172
|
+
overflow: visible;
|
|
173
|
+
}
|
|
174
|
+
.img-cropper__shadow {
|
|
175
|
+
position: absolute;
|
|
176
|
+
top: 50%;
|
|
177
|
+
left: 50%;
|
|
178
|
+
transform: translate(-50%, -50%);
|
|
179
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.8);
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
z-index: 2;
|
|
182
|
+
}
|
|
183
|
+
.img-cropper__controls {
|
|
184
|
+
position: absolute;
|
|
185
|
+
bottom: 0;
|
|
186
|
+
left: 50%;
|
|
187
|
+
transform: translateX(-50%);
|
|
188
|
+
z-index: 100;
|
|
189
|
+
}</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ImgCropper } from './img-cropper.svelte';
|
|
2
|
+
type Props = {
|
|
3
|
+
src: string;
|
|
4
|
+
cropper: ImgCropper;
|
|
5
|
+
showControls?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Image cropper view based on CropperJS v2 web components.
|
|
9
|
+
*
|
|
10
|
+
* Renders a canvas with crop selection, rotation, zoom, and reset capabilities.
|
|
11
|
+
* The container fills 100% width and height of its parent; the image is centered inside.
|
|
12
|
+
* The component itself uses `overflow: visible` — the parent element should set
|
|
13
|
+
* `overflow: hidden` to clip the shadow overlay at its boundaries.
|
|
14
|
+
*
|
|
15
|
+
* #### Modes
|
|
16
|
+
*
|
|
17
|
+
* The behavior is determined by the `ImgCropper` instance passed via the `cropper` prop:
|
|
18
|
+
*
|
|
19
|
+
* - **`cover`** — the image always fills the canvas completely. The crop selection
|
|
20
|
+
* defines the visible area. After save, the result is the selected region at
|
|
21
|
+
* natural image resolution.
|
|
22
|
+
* - **`contain`** — the image is fitted inside the canvas with optional fill color
|
|
23
|
+
* for empty space. After crop, the canvas shrinks to match the cropped area's
|
|
24
|
+
* aspect ratio. After save, the result includes the fill color background.
|
|
25
|
+
*
|
|
26
|
+
* #### Aspect ratio
|
|
27
|
+
*
|
|
28
|
+
* Aspect ratio is configured on the `ImgCropper` instance via the `aspectRatio` option:
|
|
29
|
+
*
|
|
30
|
+
* - **Fixed number** (e.g. `16/9`) — locks the crop selection to this ratio.
|
|
31
|
+
* - **Dynamic object** (`ImgCropperDynamicAspectRatio`) — provides a list of `supported`
|
|
32
|
+
* ratios and an optional `initial` value. The toolbar renders a ratio dropdown when
|
|
33
|
+
* multiple options are available. `allowFreeCrop: true` adds a free-ratio option
|
|
34
|
+
* that lets the user draw an unconstrained selection.
|
|
35
|
+
* - **Omitted** — defaults to free crop (no ratio constraint on the selection).
|
|
36
|
+
*
|
|
37
|
+
* #### Shadow overlay
|
|
38
|
+
*
|
|
39
|
+
* When `showImageShadow` is enabled on the cropper instance, a dark semi-transparent
|
|
40
|
+
* `box-shadow` is applied directly to the `<cropper-canvas>` element, extending beyond
|
|
41
|
+
* the canvas bounds so the surrounding area is dimmed. The parent element should set
|
|
42
|
+
* `overflow: hidden` to clip the shadow at its boundaries.
|
|
43
|
+
*
|
|
44
|
+
* ### Props
|
|
45
|
+
* | Prop | Type | Default | Description |
|
|
46
|
+
* |---|---|---|---|
|
|
47
|
+
* | `src` | `string` | — | Image source URL |
|
|
48
|
+
* | `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
49
|
+
* | `showControls` | `boolean` | `true` | Show built-in Apply/Cancel crop controls |
|
|
50
|
+
*
|
|
51
|
+
* ### CSS Custom Properties
|
|
52
|
+
* None — canvas background, shadow, and sizing are controlled by the `ImgCropper` instance.
|
|
53
|
+
*/
|
|
54
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
55
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
56
|
+
export default Cmp;
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
[K in keyof T as K extends keyof HTMLElement
|
|
13
|
+
? never
|
|
14
|
+
: K extends `$${string}`
|
|
15
|
+
? never
|
|
16
|
+
: T[K] extends (...args: never[]) => unknown
|
|
17
|
+
? never
|
|
18
|
+
: K extends string
|
|
19
|
+
? CamelToKebab<K>
|
|
20
|
+
: never]?: (T[K] extends boolean ? boolean : T[K] extends number ? number | string : string) | null;
|
|
21
|
+
} & import('svelte/elements').HTMLAttributes<HTMLElement>;
|
|
22
|
+
|
|
23
|
+
declare namespace svelteHTML {
|
|
24
|
+
interface IntrinsicElements {
|
|
25
|
+
'cropper-canvas': CropperElementProps<import('cropperjs').CropperCanvas>;
|
|
26
|
+
'cropper-image': CropperElementProps<import('cropperjs').CropperImage>;
|
|
27
|
+
'cropper-shade': CropperElementProps<import('cropperjs').CropperShade>;
|
|
28
|
+
'cropper-handle': CropperElementProps<import('cropperjs').CropperHandle>;
|
|
29
|
+
'cropper-selection': CropperElementProps<import('cropperjs').CropperSelection>;
|
|
30
|
+
'cropper-grid': CropperElementProps<import('cropperjs').CropperGrid>;
|
|
31
|
+
'cropper-crosshair': CropperElementProps<import('cropperjs').CropperCrosshair>;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CanvasGeometry, DragMode, 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 _visualWidth: number;
|
|
14
|
+
protected _visualHeight: number;
|
|
15
|
+
protected abstract readonly _centerMode: 'contain' | 'cover';
|
|
16
|
+
constructor(params: ImgCropperWorkerParams);
|
|
17
|
+
ready: () => Promise<void>;
|
|
18
|
+
rotate: () => Promise<void>;
|
|
19
|
+
zoomIn: () => void;
|
|
20
|
+
zoomOut: () => void;
|
|
21
|
+
reset: () => Promise<void>;
|
|
22
|
+
crop: (options?: {
|
|
23
|
+
fillColor?: string;
|
|
24
|
+
}) => Promise<{
|
|
25
|
+
dataUrl: string;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
}>;
|
|
29
|
+
save: (options?: {
|
|
30
|
+
fillColor?: string;
|
|
31
|
+
}) => Promise<{
|
|
32
|
+
dataUrl: string;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}>;
|
|
36
|
+
clearSelection: () => void;
|
|
37
|
+
refit: () => Promise<void>;
|
|
38
|
+
enableCropMode: () => void;
|
|
39
|
+
protected _wrapTransformOp: (fn: () => void) => void;
|
|
40
|
+
protected _applyMoveMode: () => void;
|
|
41
|
+
protected _fitImage: () => Promise<void>;
|
|
42
|
+
protected abstract _computeCanvasSize(): void;
|
|
43
|
+
}
|