@bnsights/bbsf-controls 1.2.27 → 1.2.29

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/README.md CHANGED
@@ -327,19 +327,19 @@ import { BBSFFormsBasicModule, BBSFUploadsModule } from '@bnsights/bbsf-controls
327
327
 
328
328
  ---
329
329
 
330
- ## 🔧 Troubleshooting
331
-
332
- ### Markdown editor assets and behavior
333
-
334
- `BBSF-MarkdownEditor` is now powered by Tiptap and no longer depends on the previous third-party markdown editor, Ace, runtime markdown scripts, or copied markdown editor assets.
335
-
336
- The control keeps the BBSF API and form integration, but the old split preview panel, `customRender`, and built-in image upload behavior are no longer provided by the editor engine.
337
-
338
- ---
339
-
340
- ### Error: `export 'DOCUMENT' (imported as 'DOCUMENT') was not found in '@angular/core'`
341
-
342
- **Problem:** This error occurs when using Angular 19+ with the `BBSFEditorsModule` (which uses `@kolkov/angular-editor`). Angular 19's build system tries to process the `@kolkov/angular-editor` package and encounters compatibility issues.
330
+ ## 🔧 Troubleshooting
331
+
332
+ ### Markdown editor assets and behavior
333
+
334
+ `BBSF-MarkdownEditor` is now powered by Tiptap and no longer depends on the previous third-party markdown editor, Ace, runtime markdown scripts, or copied markdown editor assets.
335
+
336
+ The control keeps the BBSF API and form integration, but the old split preview panel, `customRender`, and built-in image upload behavior are no longer provided by the editor engine.
337
+
338
+ ---
339
+
340
+ ### Error: `export 'DOCUMENT' (imported as 'DOCUMENT') was not found in '@angular/core'`
341
+
342
+ **Problem:** This error occurs when using Angular 19+ with the `BBSFEditorsModule` (which uses `@kolkov/angular-editor`). Angular 19's build system tries to process the `@kolkov/angular-editor` package and encounters compatibility issues.
343
343
 
344
344
  **Solution 1: Install @kolkov/angular-editor in your consuming app (Recommended)**
345
345
 
@@ -398,38 +398,61 @@ If the issue persists, consider using a different HTML editor component that's c
398
398
 
399
399
  ---
400
400
 
401
- ## Change Log
402
-
403
- ## 1.2.27 / 22-06-2026
404
-
405
- =====================
406
-
407
- - Added optional image cropper support to `BBSF-FileUpload` through `FileUploadOptions.cropperOptions`.
408
- - Added exported cropper option models for configuring modal behavior, orientation presets, and cancel behavior.
409
- - Preserved existing upload behavior unless cropper options are explicitly enabled.
410
-
411
- ## 1.2.26 / 15-06-2026
412
-
413
- =====================
414
-
415
- - Added explicit package exports for controls SCSS assets so Angular browser and application builders can resolve deep style imports from consuming packages.
416
- - Kept backward compatibility for extensionless Sass imports such as `@bnsights/bbsf-controls/src/lib/assets/sass/variables`.
417
- - Marked CSS, SCSS, and packaged assets as side-effectful so published global styles are preserved by consumers.
418
- - Added `LogoCropperComponent` as a standalone upload modal component exported from `@bnsights/bbsf-controls`.
419
- - Re-exported `LogoCropperComponent` through `BBSFUploadsModule` and the package public API.
420
- - Added `ngx-image-cropper` dependency for logo cropping support with Angular 19-compatible peer dependencies.
421
-
422
- ## 1.2.25 / 15-06-2026
423
-
424
- =====================
425
-
426
- - Replaced the BBSF MarkdownEditor engine with a Tiptap-powered markdown editor while keeping the existing `BBSF-MarkdownEditor` selector, options model, form integration, validation, and `BBSFEditorsModule` export.
427
- - Removed the old markdown editor runtime asset dependency path, including Ace and copied markdown editor assets.
428
- - Added Tiptap and PrimeIcons dependencies for the new MarkdownEditor toolbar and markdown serialization.
429
-
430
- ## 1.2.24 / 14-06-2026
431
-
432
- =====================
401
+ ## Change Log
402
+
403
+ ## 1.2.29 / 05-07-2026
404
+
405
+ =====================
406
+
407
+ - Added `allowMoveImage` support to `LogoCropperComponent` to pan the source image under a static crop box, with the CSS pointer-events fix (`.ngx-ic-cropper { pointer-events: none; }`) required to let drags reach the image instead of the crop-box overlay.
408
+ - Added `containWithinAspectRatio` support; padding is now computed from an `effectiveAspectRatio` getter so it matches the static crop box shape when one is set, instead of a mismatched `aspectRatio` input.
409
+ - Added 90° rotate left/right buttons using `canvasRotation` (does not clip corners like `transform.rotate`).
410
+ - Added a full reset button (zoom, pan, and rotation), replacing the previous zoom-only reset.
411
+ - Added mouse-wheel zoom on the cropper.
412
+ - Added `loadImageFailed` handling: logs the error, shows a toast, and dismisses the modal so the caller can clear the upload.
413
+ - Added optional display scaling (`maxDisplayWidth`/`maxDisplayHeight`, default `0` = disabled) so small target crop sizes can be shown enlarged on screen while the exported file is resized back to the true target dimensions.
414
+ - Extended `FileUploadCropperOptions` with `allowMoveImage`, `containWithinAspectRatio`, `maxDisplayWidth`, and `maxDisplayHeight`, threaded through `BBSF-FileUpload`'s cropper modal.
415
+ - Added `ResetZoom` and `ImageLoadFailed` resource keys.
416
+ - All additions are backward compatible; existing consumers are unaffected until they opt in.
417
+
418
+ ## 1.2.28 / 23-06-2026
419
+
420
+ =====================
421
+
422
+ - Added optional `FileUploadOptions` overrides for validation error message labels.
423
+ - Supported host-provided localized labels for max file count, min file count, total size, file size, type tooltip, duplicate file, and required validation messages.
424
+ - Threaded file upload options through validation helpers and applied the duplicate-file override message.
425
+
426
+ ## 1.2.27 / 22-06-2026
427
+
428
+ =====================
429
+
430
+ - Added optional image cropper support to `BBSF-FileUpload` through `FileUploadOptions.cropperOptions`.
431
+ - Added exported cropper option models for configuring modal behavior, orientation presets, and cancel behavior.
432
+ - Preserved existing upload behavior unless cropper options are explicitly enabled.
433
+
434
+ ## 1.2.26 / 15-06-2026
435
+
436
+ =====================
437
+
438
+ - Added explicit package exports for controls SCSS assets so Angular browser and application builders can resolve deep style imports from consuming packages.
439
+ - Kept backward compatibility for extensionless Sass imports such as `@bnsights/bbsf-controls/src/lib/assets/sass/variables`.
440
+ - Marked CSS, SCSS, and packaged assets as side-effectful so published global styles are preserved by consumers.
441
+ - Added `LogoCropperComponent` as a standalone upload modal component exported from `@bnsights/bbsf-controls`.
442
+ - Re-exported `LogoCropperComponent` through `BBSFUploadsModule` and the package public API.
443
+ - Added `ngx-image-cropper` dependency for logo cropping support with Angular 19-compatible peer dependencies.
444
+
445
+ ## 1.2.25 / 15-06-2026
446
+
447
+ =====================
448
+
449
+ - Replaced the BBSF MarkdownEditor engine with a Tiptap-powered markdown editor while keeping the existing `BBSF-MarkdownEditor` selector, options model, form integration, validation, and `BBSFEditorsModule` export.
450
+ - Removed the old markdown editor runtime asset dependency path, including Ace and copied markdown editor assets.
451
+ - Added Tiptap and PrimeIcons dependencies for the new MarkdownEditor toolbar and markdown serialization.
452
+
453
+ ## 1.2.24 / 14-06-2026
454
+
455
+ =====================
433
456
 
434
457
  - Add new file-upload props for override labels
435
458
  {
@@ -456,7 +479,7 @@ If the issue persists, consider using a different HTML editor component that's c
456
479
  - Fixed package stylesheet imports for Angular esbuild application builder compatibility.
457
480
  - Replaced webpack-era `/node_modules/...` SCSS imports in `src/lib/assets/Style.scss` with relative package imports.
458
481
  - Removed `.css` extensions from SCSS imports so Sass resolves and inlines vendor styles instead of leaving CSS `@import` statements for esbuild.
459
- - Fixed imports for Angular Material prebuilt theme, ngx-toastr, intl-tel-input, markdown editor styles, and angular-datetime-picker styles.
482
+ - Fixed imports for Angular Material prebuilt theme, ngx-toastr, intl-tel-input, markdown editor styles, and angular-datetime-picker styles.
460
483
 
461
484
  ## 1.2.22 / 06-05-2026
462
485
 
@@ -106,95 +106,95 @@ class ErrorMassageValidation {
106
106
  control.setErrors({ minlength: true });
107
107
  control.markAsTouched();
108
108
  }
109
- getErrorMassageValidation(error) {
109
+ getErrorMassageValidation(error, options) {
110
110
  let result = '';
111
111
  switch (error.key) {
112
112
  case 'required':
113
- result = this.utilityService.getResourceValue('RequiredValidationKey');
113
+ result = options?.requiredErrorMsg || this.utilityService.getResourceValue('RequiredValidationKey');
114
114
  break;
115
115
  case 'email':
116
- result = this.utilityService.getResourceValue('EmailValidationKey');
116
+ result = options?.emailErrorMsg || this.utilityService.getResourceValue('EmailValidationKey');
117
117
  break;
118
118
  case 'EmailValidationKey':
119
- result = this.utilityService.getResourceValue('EmailValidationKey');
119
+ result = options?.emailErrorMsg || this.utilityService.getResourceValue('EmailValidationKey');
120
120
  break;
121
121
  case 'URLValidationKey':
122
- result = this.utilityService.getResourceValue('urlValidationError');
122
+ result = options?.urlErrorMsg || this.utilityService.getResourceValue('urlValidationError');
123
123
  break;
124
124
  case 'pattern':
125
- result = this.utilityService.getResourceValue('NumberValidationKey');
125
+ result = options?.numberErrorMsg || this.utilityService.getResourceValue('NumberValidationKey');
126
126
  break;
127
127
  case 'maxlength':
128
- result = `${this.utilityService.getResourceValue('MaxLenghtValidationKey')} ${error.value.requiredLength}`;
128
+ result = `${options?.maxLengthErrorMsg || this.utilityService.getResourceValue('MaxLenghtValidationKey')} ${error.value.requiredLength}`;
129
129
  break;
130
130
  case 'minlength':
131
- result = `${this.utilityService.getResourceValue('MinLenghtValidationKey')} ${error.value.requiredLength}`;
131
+ result = `${options?.minLengthErrorMsg || this.utilityService.getResourceValue('MinLenghtValidationKey')} ${error.value.requiredLength}`;
132
132
  break;
133
133
  case 'min':
134
- result = `${this.utilityService.getResourceValue('MinValueValidationKey')} ${error.value.min}`;
134
+ result = `${options?.minValueErrorMsg || this.utilityService.getResourceValue('MinValueValidationKey')} ${error.value.min}`;
135
135
  break;
136
136
  case 'max':
137
- result = `${this.utilityService.getResourceValue('MaxValueValidationKey')} ${error.value.max}`;
137
+ result = `${options?.maxValueErrorMsg || this.utilityService.getResourceValue('MaxValueValidationKey')} ${error.value.max}`;
138
138
  break;
139
139
  case 'IntegerNumberValidationKey':
140
- result = this.utilityService.getResourceValue('IntegerNumberValidationKey');
140
+ result = options?.integerNumberErrorMsg || this.utilityService.getResourceValue('IntegerNumberValidationKey');
141
141
  break;
142
142
  case 'PositiveNumberValidationKey':
143
- result = this.utilityService.getResourceValue('PositiveNumberValidationKey');
143
+ result = options?.positiveNumberErrorMsg || this.utilityService.getResourceValue('PositiveNumberValidationKey');
144
144
  break;
145
145
  case 'validatePhoneNumber':
146
- result = this.utilityService.getResourceValue('PhoneNumberValidationKey');
146
+ result = options?.phoneNumberErrorMsg || this.utilityService.getResourceValue('PhoneNumberValidationKey');
147
147
  break;
148
148
  case 'mask':
149
- result = `${this.utilityService.getResourceValue('MaskValidationKey')} ${error.value.requiredMask}`;
149
+ result = `${options?.maskErrorMsg || this.utilityService.getResourceValue('MaskValidationKey')} ${error.value.requiredMask}`;
150
150
  break;
151
151
  case 'passwordComplexityHasNumber':
152
- result = this.utilityService.getResourceValue('PasswordComplexityHasNumber');
152
+ result = options?.passwordHasNumberErrorMsg || this.utilityService.getResourceValue('PasswordComplexityHasNumber');
153
153
  break;
154
154
  case 'ArabicLetterOnly':
155
- result = this.utilityService.getResourceValue('ArabicLetterOnly');
155
+ result = options?.arabicLetterOnlyErrorMsg || this.utilityService.getResourceValue('ArabicLetterOnly');
156
156
  break;
157
157
  case 'EnglishLetterOnly':
158
- result = this.utilityService.getResourceValue('EnglishLetterOnly');
158
+ result = options?.englishLetterOnlyErrorMsg || this.utilityService.getResourceValue('EnglishLetterOnly');
159
159
  break;
160
160
  case 'ArabicIsRequiredAndOnly50CharactersEnglish':
161
- result = this.utilityService.getResourceValue('VAL_ArabicIsRequiredAndOnly50CharactersEnglish');
161
+ result = options?.arabicRequiredAndEnglishMax50ErrorMsg || this.utilityService.getResourceValue('VAL_ArabicIsRequiredAndOnly50CharactersEnglish');
162
162
  break;
163
163
  case 'passwordComplexityHasCapitalLetter':
164
- result = this.utilityService.getResourceValue('PasswordComplexityHasCapitalLetter');
164
+ result = options?.passwordHasCapitalLetterErrorMsg || this.utilityService.getResourceValue('PasswordComplexityHasCapitalLetter');
165
165
  break;
166
166
  case 'passwordComplexityHasSmallLetter':
167
- result = this.utilityService.getResourceValue('PasswordComplexityHasSmallLetter');
167
+ result = options?.passwordHasSmallLetterErrorMsg || this.utilityService.getResourceValue('PasswordComplexityHasSmallLetter');
168
168
  break;
169
169
  case 'passwordComplexityHasSpecialLetter':
170
- result = this.utilityService.getResourceValue('PasswordComplexityHasSpecialLetter');
170
+ result = options?.passwordHasSpecialLetterErrorMsg || this.utilityService.getResourceValue('PasswordComplexityHasSpecialLetter');
171
171
  break;
172
172
  case 'MaxWordCountValidationKey':
173
- result = `${this.utilityService.getResourceValue('MaxWordCountValidationKey')} ${error.value}`;
173
+ result = `${options?.maxWordCountErrorMsg || this.utilityService.getResourceValue('MaxWordCountValidationKey')} ${error.value}`;
174
174
  break;
175
175
  case 'NewSelectionValidationKey':
176
- result = this.utilityService.getResourceValue('NewSelectionValidationKey');
176
+ result = options?.newSelectionErrorMsg || this.utilityService.getResourceValue('NewSelectionValidationKey');
177
177
  break;
178
178
  case 'MaxFileCountValidationKey':
179
- result = `${this.utilityService.getResourceValue('MaxFileCountValidationKey')} ${error.value}`;
179
+ result = `${options?.maxFileCountErrorMsg || this.utilityService.getResourceValue('MaxFileCountValidationKey')} ${error.value}`;
180
180
  break;
181
181
  case 'MinFileCountValidationKey':
182
- result = `${this.utilityService.getResourceValue('MinFileCountValidationKey')} ${error.value}`;
182
+ result = `${options?.minFileCountErrorMsg || this.utilityService.getResourceValue('MinFileCountValidationKey')} ${error.value}`;
183
183
  break;
184
184
  case 'MaxSizeForAllFilesInMB':
185
- result = `${this.utilityService.getResourceValue('MaxSizeForAllFilesInMB')} ${error.value}`;
185
+ result = `${options?.maxSizeTotalErrorMsg || this.utilityService.getResourceValue('MaxSizeForAllFilesInMB')} ${error.value}`;
186
186
  break;
187
187
  case 'FileMaxSizeInMB':
188
- result = `${this.utilityService.getResourceValue('FileMaxSizeInMB')} ${error.value}`;
188
+ result = `${options?.maxFileSizeErrorMsg || this.utilityService.getResourceValue('FileMaxSizeInMB')} ${error.value}`;
189
189
  break;
190
190
  case 'ToolTipTypeError':
191
- result = `${this.utilityService.getResourceValue('ToolTipTypeError')} ${error.value}`;
191
+ result = `${options?.toolTipTypeErrorMsg || this.utilityService.getResourceValue('ToolTipTypeError')} ${error.value}`;
192
192
  break;
193
193
  case 'InvalidEndDate':
194
- result = this.utilityService.getResourceValue('InvalidEndDate');
194
+ result = options?.invalidEndDateErrorMsg || this.utilityService.getResourceValue('InvalidEndDate');
195
195
  break;
196
196
  case 'InvalidStartDate':
197
- result = this.utilityService.getResourceValue('InvalidStartDate');
197
+ result = options?.invalidStartDateErrorMsg || this.utilityService.getResourceValue('InvalidStartDate');
198
198
  break;
199
199
  case 'DuplicateFileError':
200
200
  result = error.value;
@@ -219,7 +219,7 @@ class ControlUtility {
219
219
  this.controlValidationService = controlValidationService;
220
220
  this.utilityService = utilityService;
221
221
  }
222
- getErrorValidation(ErrorsItem, customValidation) {
222
+ getErrorValidation(ErrorsItem, customValidation, options) {
223
223
  let result = '';
224
224
  ErrorsItem.forEach(element => {
225
225
  if (element.key === 'customerError' && ErrorsItem.length === 1) {
@@ -235,7 +235,7 @@ class ControlUtility {
235
235
  result = element.value;
236
236
  }
237
237
  else {
238
- result = this.ErrorHandler.getErrorMassageValidation(element);
238
+ result = this.ErrorHandler.getErrorMassageValidation(element, options);
239
239
  }
240
240
  });
241
241
  return result;
@@ -305,7 +305,7 @@ class ControlUtility {
305
305
  // UtilityService.showGlobalError();
306
306
  // this.scrollToGlobalErrorMassage()
307
307
  // }
308
- return this.getErrorValidation(ErrorList, options.customValidation);
308
+ return this.getErrorValidation(ErrorList, options.customValidation, options);
309
309
  }
310
310
  scrollToGlobalErrorMassage() {
311
311
  let element = document.getElementById('errorId');
@@ -2568,6 +2568,14 @@ class FileUploadCropperOptions {
2568
2568
  this.backdrop = 'static';
2569
2569
  this.maintainAspectRatio = true;
2570
2570
  this.onlyScaleDown = true;
2571
+ this.allowMoveImage = false;
2572
+ this.containWithinAspectRatio = false;
2573
+ // Maximum on-screen size of the crop box; 0 keeps the crop box at its true
2574
+ // (output) size. When set, the output file is still resized back to the
2575
+ // preset's static dimensions. Pick values that fit the modal size (an
2576
+ // ng-bootstrap 'lg' modal body is ~770px wide).
2577
+ this.maxDisplayWidth = 0;
2578
+ this.maxDisplayHeight = 0;
2571
2579
  this.cancelBehavior = 'clear';
2572
2580
  this.horizontal = new FileUploadCropperPreset(3 / 1, 90, 45);
2573
2581
  this.vertical = new FileUploadCropperPreset(3 / 2, 200, 66);
@@ -7352,28 +7360,91 @@ class LogoCropperComponent {
7352
7360
  this.cropperStaticWidth = 0;
7353
7361
  this.cropperStaticHeight = 0;
7354
7362
  this.onlyScaleDown = true;
7355
- this.transform = { scale: 1 };
7363
+ this.allowMoveImage = false;
7364
+ this.containWithinAspectRatio = false;
7365
+ // Maximum on-screen size of the crop box; 0 disables display scaling and
7366
+ // keeps the crop box at its true (output) size.
7367
+ this.maxDisplayWidth = 0;
7368
+ this.maxDisplayHeight = 0;
7369
+ this.transform = { scale: 1, translateH: 0, translateV: 0, translateUnit: 'px' };
7370
+ this.canvasRotation = 0;
7356
7371
  this.croppedBlob = null;
7357
7372
  this.minScale = 1;
7358
7373
  this.maxScale = 4;
7359
7374
  this.scaleStep = 0.1;
7360
7375
  }
7361
7376
  ngOnInit() { }
7377
+ get displayScalingEnabled() {
7378
+ return this.maxDisplayWidth > 0 && this.maxDisplayHeight > 0 &&
7379
+ this.cropperStaticWidth > 0 && this.cropperStaticHeight > 0;
7380
+ }
7381
+ // Scale the crop box up so the user can see the image clearly while cropping,
7382
+ // while the uploaded file is resized back to the target dimensions.
7383
+ get displayScale() {
7384
+ if (!this.displayScalingEnabled)
7385
+ return 1;
7386
+ const scale = Math.min(this.maxDisplayWidth / this.cropperStaticWidth, this.maxDisplayHeight / this.cropperStaticHeight);
7387
+ return Math.max(1, scale);
7388
+ }
7389
+ get displayWidth() {
7390
+ return Math.round(this.cropperStaticWidth * this.displayScale);
7391
+ }
7392
+ get displayHeight() {
7393
+ return Math.round(this.cropperStaticHeight * this.displayScale);
7394
+ }
7395
+ // The contain-within-aspect-ratio padding is computed from the aspectRatio
7396
+ // option, so it must match the static crop box shape when one is set.
7397
+ get effectiveAspectRatio() {
7398
+ if (this.cropperStaticWidth && this.cropperStaticHeight) {
7399
+ return this.cropperStaticWidth / this.cropperStaticHeight;
7400
+ }
7401
+ return this.aspectRatio;
7402
+ }
7403
+ // Source-image size caps consumed by the component scss; null falls back to
7404
+ // the legacy fixed caps so display scaling stays opt-in.
7405
+ get sourceMaxHeight() {
7406
+ return this.displayScalingEnabled ? `${this.displayHeight}px` : null;
7407
+ }
7408
+ get sourceMaxWidth() {
7409
+ return this.displayScalingEnabled ? `${this.displayWidth}px` : null;
7410
+ }
7362
7411
  translate(key) {
7363
7412
  return this.utilityService.getResourceValue(key);
7364
7413
  }
7365
7414
  imageCropped(event) {
7366
7415
  this.croppedBlob = event.blob ?? null;
7367
7416
  }
7417
+ imageLoadFailed(error) {
7418
+ console.error('Logo cropper failed to load image', error);
7419
+ this.utilityService.notifyErrorMessage(this.translate('ImageLoadFailed'));
7420
+ this.activeModal.dismiss();
7421
+ }
7422
+ onWheel(event) {
7423
+ event.preventDefault();
7424
+ if (event.deltaY < 0)
7425
+ this.zoomIn();
7426
+ else if (event.deltaY > 0)
7427
+ this.zoomOut();
7428
+ }
7368
7429
  zoomIn() {
7369
7430
  this.setScale((this.transform.scale ?? 1) + this.scaleStep);
7370
7431
  }
7371
7432
  zoomOut() {
7372
7433
  this.setScale((this.transform.scale ?? 1) - this.scaleStep);
7373
7434
  }
7435
+ rotateLeft() {
7436
+ this.canvasRotation = (this.canvasRotation + 3) % 4;
7437
+ }
7438
+ rotateRight() {
7439
+ this.canvasRotation = (this.canvasRotation + 1) % 4;
7440
+ }
7374
7441
  resetZoom() {
7375
7442
  this.setScale(1);
7376
7443
  }
7444
+ reset() {
7445
+ this.canvasRotation = 0;
7446
+ this.transform = { scale: 1, translateH: 0, translateV: 0, translateUnit: 'px' };
7447
+ }
7377
7448
  confirm() {
7378
7449
  if (!this.croppedBlob)
7379
7450
  return;
@@ -7388,14 +7459,14 @@ class LogoCropperComponent {
7388
7459
  this.transform = { ...this.transform, scale: clamped };
7389
7460
  }
7390
7461
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: LogoCropperComponent, deps: [{ token: i4$1.NgbActiveModal }, { token: i3.UtilityService }], target: i0.ɵɵFactoryTarget.Component }); }
7391
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.16", type: LogoCropperComponent, isStandalone: true, selector: "BBSF-LogoCropper", inputs: { imageFile: "imageFile", isHorizontal: "isHorizontal", aspectRatio: "aspectRatio", maintainAspectRatio: "maintainAspectRatio", cropperStaticWidth: "cropperStaticWidth", cropperStaticHeight: "cropperStaticHeight", onlyScaleDown: "onlyScaleDown" }, ngImport: i0, template: "<div class=\"bbsf-logo-cropper\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">{{ translate(\"CropImage\") }}</h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\n </div>\n\n <div class=\"modal-body d-flex flex-column justify-content-center align-items-center py-0\" style=\"min-height: 200px;\">\n <image-cropper [class.crop-horizontal]=\"isHorizontal\" [class.crop-vertical]=\"!isHorizontal\" [imageFile]=\"imageFile\"\n [maintainAspectRatio]=\"maintainAspectRatio\" [aspectRatio]=\"aspectRatio\"\n [cropperStaticWidth]=\"cropperStaticWidth\" [cropperStaticHeight]=\"cropperStaticHeight\"\n [onlyScaleDown]=\"onlyScaleDown\" [transform]=\"transform\" format=\"png\" (imageCropped)=\"imageCropped($event)\">\n </image-cropper>\n\n <div class=\"d-flex justify-content-center align-items-center gap-2 mt-3\">\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomOut()\"\n [title]=\"translate('ZoomOut')\">\n <i class=\"fa fa-search-minus\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"resetZoom()\"\n [title]=\"translate('ResetZoom')\">\n <i class=\"fa fa-undo\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomIn()\" [title]=\"translate('ZoomIn')\">\n <i class=\"fa fa-search-plus\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"dismiss()\"> Cancel </button>\n <button type=\"button\" class=\"btn btn-sm btn-brand\" [disabled]=\"!croppedBlob\" (click)=\"confirm()\">\n {{ translate(\"Crop\") }}\n </button>\n </div>\n</div>\n", styles: [".bbsf-logo-cropper image-cropper.crop-horizontal .ngx-ic-source-image{max-height:45px!important;max-width:none!important}.bbsf-logo-cropper image-cropper.crop-vertical .ngx-ic-source-image{max-width:200px!important;max-height:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }], encapsulation: i0.ViewEncapsulation.None }); }
7462
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.16", type: LogoCropperComponent, isStandalone: true, selector: "BBSF-LogoCropper", inputs: { imageFile: "imageFile", isHorizontal: "isHorizontal", aspectRatio: "aspectRatio", maintainAspectRatio: "maintainAspectRatio", cropperStaticWidth: "cropperStaticWidth", cropperStaticHeight: "cropperStaticHeight", onlyScaleDown: "onlyScaleDown", allowMoveImage: "allowMoveImage", containWithinAspectRatio: "containWithinAspectRatio", maxDisplayWidth: "maxDisplayWidth", maxDisplayHeight: "maxDisplayHeight" }, ngImport: i0, template: "<div class=\"bbsf-logo-cropper\">\r\n <div class=\"modal-header\">\r\n <h5 class=\"modal-title\">{{ translate(\"CropImage\") }}</h5>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\r\n </div>\r\n\r\n <div class=\"modal-body d-flex flex-column justify-content-center align-items-center py-0\" style=\"min-height: 200px;\">\r\n <image-cropper [class.crop-horizontal]=\"isHorizontal\" [class.crop-vertical]=\"!isHorizontal\"\r\n [class.allow-move]=\"allowMoveImage\" [imageFile]=\"imageFile\"\r\n [style.--bbsf-cropper-source-max-height]=\"sourceMaxHeight\"\r\n [style.--bbsf-cropper-source-max-width]=\"sourceMaxWidth\"\r\n [maintainAspectRatio]=\"maintainAspectRatio\" [aspectRatio]=\"effectiveAspectRatio\"\r\n [containWithinAspectRatio]=\"containWithinAspectRatio\" [canvasRotation]=\"canvasRotation\"\r\n [cropperStaticWidth]=\"displayWidth\" [cropperStaticHeight]=\"displayHeight\"\r\n [resizeToWidth]=\"displayScalingEnabled ? cropperStaticWidth : 0\"\r\n [resizeToHeight]=\"displayScalingEnabled ? cropperStaticHeight : 0\"\r\n [onlyScaleDown]=\"onlyScaleDown\" [allowMoveImage]=\"allowMoveImage\" [(transform)]=\"transform\" format=\"png\"\r\n (imageCropped)=\"imageCropped($event)\" (loadImageFailed)=\"imageLoadFailed($event)\" (wheel)=\"onWheel($event)\">\r\n </image-cropper>\r\n\r\n <div class=\"d-flex justify-content-center align-items-center gap-2 mt-3\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomOut()\"\r\n [title]=\"translate('ZoomOut')\">\r\n <i class=\"fa fa-search-minus\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomIn()\" [title]=\"translate('ZoomIn')\">\r\n <i class=\"fa fa-search-plus\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"rotateLeft()\"\r\n [title]=\"translate('RotateLeft')\">\r\n <i class=\"fa fa-rotate-left\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"rotateRight()\"\r\n [title]=\"translate('RotateRight')\">\r\n <i class=\"fa fa-rotate-right\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"reset()\"\r\n [title]=\"translate('ResetZoom')\">\r\n <i class=\"fa fa-refresh\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"dismiss()\"> Cancel </button>\r\n <button type=\"button\" class=\"btn btn-sm btn-brand\" [disabled]=\"!croppedBlob\" (click)=\"confirm()\">\r\n {{ translate(\"Crop\") }}\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [".bbsf-logo-cropper image-cropper.crop-horizontal .ngx-ic-source-image{max-height:var(--bbsf-cropper-source-max-height, 45px)!important;max-width:none!important}.bbsf-logo-cropper image-cropper.crop-vertical .ngx-ic-source-image{max-width:var(--bbsf-cropper-source-max-width, 200px)!important;max-height:none!important}.bbsf-logo-cropper image-cropper.allow-move .ngx-ic-cropper{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }], encapsulation: i0.ViewEncapsulation.None }); }
7392
7463
  }
7393
7464
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: LogoCropperComponent, decorators: [{
7394
7465
  type: Component,
7395
7466
  args: [{ selector: 'BBSF-LogoCropper', encapsulation: ViewEncapsulation.None, standalone: true, imports: [
7396
7467
  CommonModule,
7397
7468
  ImageCropperComponent
7398
- ], template: "<div class=\"bbsf-logo-cropper\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">{{ translate(\"CropImage\") }}</h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\n </div>\n\n <div class=\"modal-body d-flex flex-column justify-content-center align-items-center py-0\" style=\"min-height: 200px;\">\n <image-cropper [class.crop-horizontal]=\"isHorizontal\" [class.crop-vertical]=\"!isHorizontal\" [imageFile]=\"imageFile\"\n [maintainAspectRatio]=\"maintainAspectRatio\" [aspectRatio]=\"aspectRatio\"\n [cropperStaticWidth]=\"cropperStaticWidth\" [cropperStaticHeight]=\"cropperStaticHeight\"\n [onlyScaleDown]=\"onlyScaleDown\" [transform]=\"transform\" format=\"png\" (imageCropped)=\"imageCropped($event)\">\n </image-cropper>\n\n <div class=\"d-flex justify-content-center align-items-center gap-2 mt-3\">\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomOut()\"\n [title]=\"translate('ZoomOut')\">\n <i class=\"fa fa-search-minus\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"resetZoom()\"\n [title]=\"translate('ResetZoom')\">\n <i class=\"fa fa-undo\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomIn()\" [title]=\"translate('ZoomIn')\">\n <i class=\"fa fa-search-plus\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"dismiss()\"> Cancel </button>\n <button type=\"button\" class=\"btn btn-sm btn-brand\" [disabled]=\"!croppedBlob\" (click)=\"confirm()\">\n {{ translate(\"Crop\") }}\n </button>\n </div>\n</div>\n", styles: [".bbsf-logo-cropper image-cropper.crop-horizontal .ngx-ic-source-image{max-height:45px!important;max-width:none!important}.bbsf-logo-cropper image-cropper.crop-vertical .ngx-ic-source-image{max-width:200px!important;max-height:none!important}\n"] }]
7469
+ ], template: "<div class=\"bbsf-logo-cropper\">\r\n <div class=\"modal-header\">\r\n <h5 class=\"modal-title\">{{ translate(\"CropImage\") }}</h5>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"dismiss()\"></button>\r\n </div>\r\n\r\n <div class=\"modal-body d-flex flex-column justify-content-center align-items-center py-0\" style=\"min-height: 200px;\">\r\n <image-cropper [class.crop-horizontal]=\"isHorizontal\" [class.crop-vertical]=\"!isHorizontal\"\r\n [class.allow-move]=\"allowMoveImage\" [imageFile]=\"imageFile\"\r\n [style.--bbsf-cropper-source-max-height]=\"sourceMaxHeight\"\r\n [style.--bbsf-cropper-source-max-width]=\"sourceMaxWidth\"\r\n [maintainAspectRatio]=\"maintainAspectRatio\" [aspectRatio]=\"effectiveAspectRatio\"\r\n [containWithinAspectRatio]=\"containWithinAspectRatio\" [canvasRotation]=\"canvasRotation\"\r\n [cropperStaticWidth]=\"displayWidth\" [cropperStaticHeight]=\"displayHeight\"\r\n [resizeToWidth]=\"displayScalingEnabled ? cropperStaticWidth : 0\"\r\n [resizeToHeight]=\"displayScalingEnabled ? cropperStaticHeight : 0\"\r\n [onlyScaleDown]=\"onlyScaleDown\" [allowMoveImage]=\"allowMoveImage\" [(transform)]=\"transform\" format=\"png\"\r\n (imageCropped)=\"imageCropped($event)\" (loadImageFailed)=\"imageLoadFailed($event)\" (wheel)=\"onWheel($event)\">\r\n </image-cropper>\r\n\r\n <div class=\"d-flex justify-content-center align-items-center gap-2 mt-3\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomOut()\"\r\n [title]=\"translate('ZoomOut')\">\r\n <i class=\"fa fa-search-minus\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"zoomIn()\" [title]=\"translate('ZoomIn')\">\r\n <i class=\"fa fa-search-plus\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"rotateLeft()\"\r\n [title]=\"translate('RotateLeft')\">\r\n <i class=\"fa fa-rotate-left\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"rotateRight()\"\r\n [title]=\"translate('RotateRight')\">\r\n <i class=\"fa fa-rotate-right\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" (click)=\"reset()\"\r\n [title]=\"translate('ResetZoom')\">\r\n <i class=\"fa fa-refresh\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"dismiss()\"> Cancel </button>\r\n <button type=\"button\" class=\"btn btn-sm btn-brand\" [disabled]=\"!croppedBlob\" (click)=\"confirm()\">\r\n {{ translate(\"Crop\") }}\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [".bbsf-logo-cropper image-cropper.crop-horizontal .ngx-ic-source-image{max-height:var(--bbsf-cropper-source-max-height, 45px)!important;max-width:none!important}.bbsf-logo-cropper image-cropper.crop-vertical .ngx-ic-source-image{max-width:var(--bbsf-cropper-source-max-width, 200px)!important;max-height:none!important}.bbsf-logo-cropper image-cropper.allow-move .ngx-ic-cropper{pointer-events:none}\n"] }]
7399
7470
  }], ctorParameters: () => [{ type: i4$1.NgbActiveModal }, { type: i3.UtilityService }], propDecorators: { imageFile: [{
7400
7471
  type: Input
7401
7472
  }], isHorizontal: [{
@@ -7410,6 +7481,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImpo
7410
7481
  type: Input
7411
7482
  }], onlyScaleDown: [{
7412
7483
  type: Input
7484
+ }], allowMoveImage: [{
7485
+ type: Input
7486
+ }], containWithinAspectRatio: [{
7487
+ type: Input
7488
+ }], maxDisplayWidth: [{
7489
+ type: Input
7490
+ }], maxDisplayHeight: [{
7491
+ type: Input
7413
7492
  }] } });
7414
7493
 
7415
7494
  class FileUploadComponent {
@@ -7755,7 +7834,7 @@ class FileUploadComponent {
7755
7834
  const formControl = this.fileUploadFormControl;
7756
7835
  const resourceValue = this.UtilityService.getResourceValue('DuplicateFileError');
7757
7836
  // If resource value is the same as the key, it means the resource wasn't found
7758
- const errorMessage = (resourceValue && resourceValue !== 'DuplicateFileError') ? resourceValue : 'File(s) already exist';
7837
+ const errorMessage = this.options?.duplicateFileErrorMsg || ((resourceValue && resourceValue !== 'DuplicateFileError') ? resourceValue : 'File(s) already exist');
7759
7838
  const errorObj = {
7760
7839
  DuplicateFileError: `${errorMessage}: ${duplicateFileNames}`
7761
7840
  };
@@ -7794,7 +7873,7 @@ class FileUploadComponent {
7794
7873
  //keep existing valid files, remove only extra ones
7795
7874
  this.uploader.queue = this.uploader.queue.slice(0, max);
7796
7875
  this.maxFilesReached = true;
7797
- this.maxFilesValidationMsg = this.UtilityService.getResourceValue('MaxFileCountValidationKey') + max;
7876
+ this.maxFilesValidationMsg = (this.options.maxFileCountErrorMsg || this.UtilityService.getResourceValue('MaxFileCountValidationKey')) + max;
7798
7877
  }
7799
7878
  else {
7800
7879
  this.maxFilesReached = false;
@@ -8002,6 +8081,10 @@ class FileUploadComponent {
8002
8081
  modalRef.componentInstance.cropperStaticHeight = preset.cropperStaticHeight;
8003
8082
  modalRef.componentInstance.maintainAspectRatio = cropperOptions.maintainAspectRatio;
8004
8083
  modalRef.componentInstance.onlyScaleDown = cropperOptions.onlyScaleDown;
8084
+ modalRef.componentInstance.allowMoveImage = cropperOptions.allowMoveImage;
8085
+ modalRef.componentInstance.containWithinAspectRatio = cropperOptions.containWithinAspectRatio;
8086
+ modalRef.componentInstance.maxDisplayWidth = cropperOptions.maxDisplayWidth;
8087
+ modalRef.componentInstance.maxDisplayHeight = cropperOptions.maxDisplayHeight;
8005
8088
  modalRef.result.then((croppedFile) => {
8006
8089
  this.readCroppedFile(addedFile, croppedFile);
8007
8090
  }).catch(() => {