@@ -69,31 +69,59 @@ type OutputFormatToOutputMap<T extends OutputFormat> = T extends 'canvas'
69
69
? string
70
70
: never ;
71
71
72
+ type EditorConfig = {
73
+ MIN_CROP_DIMENSION : number ;
74
+
75
+ ZOOM_WHEEL_FACTOR : number ;
76
+ ZOOM_BUTTON_FACTOR : number ;
77
+
78
+ CROP_HANDLE_SIZE : number ;
79
+ CROP_HANDLE_STROKE_WIDTH : number ;
80
+ CROP_HANDLE_FILL : string ;
81
+ CROP_HANDLE_STROKE : string ;
82
+
83
+ CROP_GUIDE_STROKE : string ;
84
+ CROP_GUIDE_STROKE_WIDTH : number ;
85
+
86
+ FIT_TO_CONTAINER_PADDING : number ;
87
+
88
+ DEFAULT_CROP_BOX_SCALE : number ;
89
+
90
+ ZOOM_MIN : number ;
91
+ ZOOM_MAX : number ;
92
+ } ;
93
+
94
+ const DEFAULT_CONFIG : EditorConfig = {
95
+ MIN_CROP_DIMENSION : 64 ,
96
+
97
+ ZOOM_WHEEL_FACTOR : 1.1 ,
98
+ ZOOM_BUTTON_FACTOR : 1.2 ,
99
+
100
+ CROP_HANDLE_SIZE : 8 ,
101
+ CROP_HANDLE_STROKE_WIDTH : 1 ,
102
+ CROP_HANDLE_FILL : 'white' ,
103
+ CROP_HANDLE_STROKE : 'black' ,
104
+
105
+ CROP_GUIDE_STROKE : 'rgba(255, 255, 255, 0.5)' ,
106
+ CROP_GUIDE_STROKE_WIDTH : 1 ,
107
+
108
+ FIT_TO_CONTAINER_PADDING : 0.9 ,
109
+
110
+ DEFAULT_CROP_BOX_SCALE : 0.8 ,
111
+
112
+ ZOOM_MIN : 0.1 ,
113
+ ZOOM_MAX : 10 ,
114
+ } ;
115
+
72
116
export class Editor {
73
117
private konva : KonvaObjects | null = null ;
74
118
private originalImage : HTMLImageElement | null = null ;
75
119
private isCropping = false ;
76
-
77
- // Constants
78
- private readonly MIN_CROP_DIMENSION = 64 ;
79
- private readonly ZOOM_WHEEL_FACTOR = 1.1 ;
80
- private readonly ZOOM_BUTTON_FACTOR = 1.2 ;
81
- private readonly CROP_HANDLE_SIZE = 8 ;
82
- private readonly CROP_HANDLE_STROKE_WIDTH = 1 ;
83
- private readonly CROP_GUIDE_STROKE = 'rgba(255, 255, 255, 0.5)' ;
84
- private readonly CROP_GUIDE_STROKE_WIDTH = 1 ;
85
- private readonly CROP_HANDLE_FILL = 'white' ;
86
- private readonly CROP_HANDLE_STROKE = 'black' ;
87
- private readonly FIT_TO_CONTAINER_PADDING = 0.9 ;
88
- private readonly DEFAULT_CROP_BOX_SCALE = 0.8 ;
89
-
90
- // Configuration
91
- private readonly ZOOM_MIN = 0.1 ;
92
- private readonly ZOOM_MAX = 10 ;
120
+ private config : EditorConfig = DEFAULT_CONFIG ;
93
121
94
122
private cropConstraints : CropConstraints = {
95
- minWidth : this . MIN_CROP_DIMENSION ,
96
- minHeight : this . MIN_CROP_DIMENSION ,
123
+ minWidth : this . config . MIN_CROP_DIMENSION ,
124
+ minHeight : this . config . MIN_CROP_DIMENSION ,
97
125
} ;
98
126
private callbacks : EditorCallbacks = { } ;
99
127
private cropBox : CropBox | null = null ;
@@ -105,7 +133,9 @@ export class Editor {
105
133
106
134
private subscriptions : Set < ( ) => void > = new Set ( ) ;
107
135
108
- init = ( container : HTMLDivElement ) => {
136
+ init = ( container : HTMLDivElement , config ?: Partial < EditorConfig > ) => {
137
+ this . config = { ...this . config , ...config } ;
138
+
109
139
const stage = new Konva . Stage ( {
110
140
container : container ,
111
141
width : container . clientWidth ,
@@ -245,24 +275,21 @@ export class Editor {
245
275
if ( ! this . konva ?. image . image || ! this . cropBox ) {
246
276
return ;
247
277
}
278
+
248
279
const imgWidth = this . konva . image . image . width ( ) ;
249
280
const imgHeight = this . konva . image . image . height ( ) ;
250
281
251
282
// Constrain to image bounds
252
283
const x = Math . max ( 0 , Math . min ( rect . x ( ) , imgWidth - rect . width ( ) ) ) ;
253
284
const y = Math . max ( 0 , Math . min ( rect . y ( ) , imgHeight - rect . height ( ) ) ) ;
285
+ const { width, height } = this . cropBox ;
254
286
255
287
rect . x ( x ) ;
256
288
rect . y ( y ) ;
257
289
258
- this . updateCropBox ( {
259
- ...this . cropBox ,
260
- x,
261
- y,
262
- } ) ;
290
+ this . updateCropBox ( { x, y, width, height } ) ;
263
291
} ) ;
264
292
265
- // Cursor styles
266
293
rect . on ( 'mouseenter' , ( ) => {
267
294
const stage = this . konva ?. stage ;
268
295
if ( ! stage ) {
@@ -289,8 +316,8 @@ export class Editor {
289
316
private createKonvaCropGuide = ( name : GuideName ) : Konva . Line => {
290
317
const line = new Konva . Line ( {
291
318
name,
292
- stroke : this . CROP_GUIDE_STROKE ,
293
- strokeWidth : this . CROP_GUIDE_STROKE_WIDTH ,
319
+ stroke : this . config . CROP_GUIDE_STROKE ,
320
+ strokeWidth : this . config . CROP_GUIDE_STROKE_WIDTH ,
294
321
strokeScaleEnabled : false ,
295
322
listening : false ,
296
323
} ) ;
@@ -303,11 +330,11 @@ export class Editor {
303
330
name,
304
331
x : 0 ,
305
332
y : 0 ,
306
- width : this . CROP_HANDLE_SIZE ,
307
- height : this . CROP_HANDLE_SIZE ,
308
- fill : this . CROP_HANDLE_FILL ,
309
- stroke : this . CROP_HANDLE_STROKE ,
310
- strokeWidth : this . CROP_HANDLE_STROKE_WIDTH ,
333
+ width : this . config . CROP_HANDLE_SIZE ,
334
+ height : this . config . CROP_HANDLE_SIZE ,
335
+ fill : this . config . CROP_HANDLE_FILL ,
336
+ stroke : this . config . CROP_HANDLE_STROKE ,
337
+ strokeWidth : this . config . CROP_HANDLE_STROKE_WIDTH ,
311
338
strokeScaleEnabled : true ,
312
339
draggable : true ,
313
340
hitStrokeWidth : 16 ,
@@ -466,8 +493,8 @@ export class Editor {
466
493
}
467
494
468
495
const scale = this . konva . stage . scaleX ( ) ;
469
- const handleSize = this . CROP_HANDLE_SIZE / scale ;
470
- const strokeWidth = this . CROP_HANDLE_STROKE_WIDTH / scale ;
496
+ const handleSize = this . config . CROP_HANDLE_SIZE / scale ;
497
+ const strokeWidth = this . config . CROP_HANDLE_STROKE_WIDTH / scale ;
471
498
472
499
for ( const handle of Object . values ( this . konva . crop . interaction . handles ) ) {
473
500
const currentX = handle . x ( ) ;
@@ -561,11 +588,11 @@ export class Editor {
561
588
} ;
562
589
563
590
const direction = e . deltaY > 0 ? - 1 : 1 ;
564
- const scaleBy = this . ZOOM_WHEEL_FACTOR ;
591
+ const scaleBy = this . config . ZOOM_WHEEL_FACTOR ;
565
592
let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy ;
566
593
567
594
// Apply zoom limits
568
- newScale = Math . max ( this . ZOOM_MIN , Math . min ( this . ZOOM_MAX , newScale ) ) ;
595
+ newScale = Math . max ( this . config . ZOOM_MIN , Math . min ( this . config . ZOOM_MAX , newScale ) ) ;
569
596
570
597
this . konva . stage . scale ( { x : newScale , y : newScale } ) ;
571
598
@@ -727,8 +754,8 @@ export class Editor {
727
754
const handleX = handleRect . x ( ) + handleRect . width ( ) / 2 ;
728
755
const handleY = handleRect . y ( ) + handleRect . height ( ) / 2 ;
729
756
730
- const minWidth = this . cropConstraints . minWidth ?? this . MIN_CROP_DIMENSION ;
731
- const minHeight = this . cropConstraints . minHeight ?? this . MIN_CROP_DIMENSION ;
757
+ const minWidth = this . cropConstraints . minWidth ?? this . config . MIN_CROP_DIMENSION ;
758
+ const minHeight = this . cropConstraints . minHeight ?? this . config . MIN_CROP_DIMENSION ;
732
759
733
760
// Update dimensions based on handle type
734
761
if ( handleName . includes ( 'left' ) ) {
@@ -762,8 +789,8 @@ export class Editor {
762
789
const handleX = handleRect . x ( ) + handleRect . width ( ) / 2 ;
763
790
const handleY = handleRect . y ( ) + handleRect . height ( ) / 2 ;
764
791
765
- const minWidth = this . cropConstraints . minWidth ?? this . MIN_CROP_DIMENSION ;
766
- const minHeight = this . cropConstraints . minHeight ?? this . MIN_CROP_DIMENSION ;
792
+ const minWidth = this . cropConstraints . minWidth ?? this . config . MIN_CROP_DIMENSION ;
793
+ const minHeight = this . cropConstraints . minHeight ?? this . config . MIN_CROP_DIMENSION ;
767
794
768
795
// Early boundary check for aspect ratio mode
769
796
const atLeftEdge = this . cropBox . x <= 0 ;
@@ -951,8 +978,8 @@ export class Editor {
951
978
// Create default crop box (centered, 80% of image)
952
979
const imgWidth = this . konva . image . image . width ( ) ;
953
980
const imgHeight = this . konva . image . image . height ( ) ;
954
- cropWidth = imgWidth * this . DEFAULT_CROP_BOX_SCALE ;
955
- cropHeight = imgHeight * this . DEFAULT_CROP_BOX_SCALE ;
981
+ cropWidth = imgWidth * this . config . DEFAULT_CROP_BOX_SCALE ;
982
+ cropHeight = imgHeight * this . config . DEFAULT_CROP_BOX_SCALE ;
956
983
cropX = ( imgWidth - cropWidth ) / 2 ;
957
984
cropY = ( imgHeight - cropHeight ) / 2 ;
958
985
}
@@ -1072,7 +1099,7 @@ export class Editor {
1072
1099
return ;
1073
1100
}
1074
1101
1075
- scale = Math . max ( this . ZOOM_MIN , Math . min ( this . ZOOM_MAX , scale ) ) ;
1102
+ scale = Math . max ( this . config . ZOOM_MIN , Math . min ( this . config . ZOOM_MAX , scale ) ) ;
1076
1103
1077
1104
// If no point provided, use center of viewport
1078
1105
if ( ! point && this . konva . image ) {
@@ -1116,12 +1143,12 @@ export class Editor {
1116
1143
1117
1144
zoomIn = ( point ?: { x : number ; y : number } ) => {
1118
1145
const currentZoom = this . getZoom ( ) ;
1119
- this . setZoom ( currentZoom * this . ZOOM_BUTTON_FACTOR , point ) ;
1146
+ this . setZoom ( currentZoom * this . config . ZOOM_BUTTON_FACTOR , point ) ;
1120
1147
} ;
1121
1148
1122
1149
zoomOut = ( point ?: { x : number ; y : number } ) => {
1123
1150
const currentZoom = this . getZoom ( ) ;
1124
- this . setZoom ( currentZoom / this . ZOOM_BUTTON_FACTOR , point ) ;
1151
+ this . setZoom ( currentZoom / this . config . ZOOM_BUTTON_FACTOR , point ) ;
1125
1152
} ;
1126
1153
1127
1154
resetView = ( ) => {
@@ -1160,7 +1187,8 @@ export class Editor {
1160
1187
const imageWidth = this . konva . image . image . width ( ) ;
1161
1188
const imageHeight = this . konva . image . image . height ( ) ;
1162
1189
1163
- const scale = Math . min ( containerWidth / imageWidth , containerHeight / imageHeight ) * this . FIT_TO_CONTAINER_PADDING ;
1190
+ const scale =
1191
+ Math . min ( containerWidth / imageWidth , containerHeight / imageHeight ) * this . config . FIT_TO_CONTAINER_PADDING ;
1164
1192
1165
1193
this . konva . stage . scale ( { x : scale , y : scale } ) ;
1166
1194
@@ -1230,8 +1258,8 @@ export class Editor {
1230
1258
}
1231
1259
1232
1260
// Apply minimum size constraints
1233
- const minWidth = this . cropConstraints . minWidth ?? this . MIN_CROP_DIMENSION ;
1234
- const minHeight = this . cropConstraints . minHeight ?? this . MIN_CROP_DIMENSION ;
1261
+ const minWidth = this . cropConstraints . minWidth ?? this . config . MIN_CROP_DIMENSION ;
1262
+ const minHeight = this . cropConstraints . minHeight ?? this . config . MIN_CROP_DIMENSION ;
1235
1263
1236
1264
if ( newWidth < minWidth ) {
1237
1265
newWidth = minWidth ;
0 commit comments