@tft/crispr-forms 13.0.0 → 13.0.4

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.
@@ -1127,7 +1127,6 @@ function convertBytesToMb(bytes) {
1127
1127
 
1128
1128
  const ImageUploadFieldMixin = crisprControlMixin(CrisprFieldComponent);
1129
1129
  class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1130
- // ngControl: NgControl = null;
1131
1130
  constructor(ngControl, domSanitizer, cdr) {
1132
1131
  super();
1133
1132
  this.ngControl = ngControl;
@@ -1148,48 +1147,61 @@ class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1148
1147
  minCompressionThresholdMb: .6,
1149
1148
  useWebWorker: true,
1150
1149
  };
1150
+ /**
1151
+ * The url mapped from initial input value, consumers can us mapInputValueToUrl in config to transform their input value into a src url for the image element
1152
+ */
1153
+ // inputValueUrl: string | undefined;
1151
1154
  this.inputImageFileSubject = new Subject();
1152
1155
  this.inputImageFile$ = this.inputImageFileSubject.asObservable().pipe(shareReplay(1));
1153
1156
  this.compressedFile$ = this.inputImageFile$.pipe(switchMap(async (file) => {
1154
- if (!file)
1157
+ if (!file || !(file instanceof File))
1155
1158
  return null;
1156
- if (typeof file === 'string')
1157
- return file;
1158
1159
  const { compressImage, targetCompressedImageFileSizeMb, minCompressionThresholdMb, maxWidthOrHeight, useWebWorker } = this.config;
1159
1160
  const fileSizeMb = convertBytesToMb(file.size);
1160
- const shouldCompress = compressImage && targetCompressedImageFileSizeMb < fileSizeMb && minCompressionThresholdMb < fileSizeMb;
1161
+ const shouldCompress = compressImage
1162
+ && targetCompressedImageFileSizeMb < fileSizeMb
1163
+ && minCompressionThresholdMb < fileSizeMb;
1161
1164
  if (shouldCompress) {
1162
1165
  const options = {
1163
1166
  // Use maxWidthOrHeight if it was provided, else use target compressed size
1164
1167
  ...(maxWidthOrHeight ? { maxWidthOrHeight } : { maxSizeMB: targetCompressedImageFileSizeMb }),
1165
1168
  useWebWorker,
1166
1169
  maxIterations: 30,
1167
- onProgress: (p) => {
1168
- this.progress = p;
1170
+ onProgress: (progress) => {
1171
+ this.progress = progress;
1169
1172
  this.cdr.detectChanges();
1170
1173
  }
1171
1174
  };
1172
- const compressedFile = await imageCompression(file, options);
1173
- return compressedFile;
1175
+ return await imageCompression(file, options);
1174
1176
  }
1175
1177
  else
1176
1178
  return file;
1179
+ }), map((compressedFile) => {
1180
+ if (!compressedFile)
1181
+ return compressedFile;
1182
+ const { type, name, lastModified } = compressedFile;
1183
+ return new File([compressedFile], this.config.fileName || name, {
1184
+ type,
1185
+ lastModified
1186
+ });
1177
1187
  }), tap(file => {
1178
- this.control.setValue(file);
1188
+ // only set the value if there is a file because resetToInitialValue sets the control value
1189
+ // to initial value and clears the compressed image, so setting it to null here would undo that
1190
+ if (file) {
1191
+ this.control.setValue(file);
1192
+ }
1179
1193
  if (file || this.value) {
1180
1194
  this.control.markAsTouched();
1181
1195
  setTimeout(() => {
1182
1196
  this.cdr.detectChanges();
1183
1197
  }, 100);
1184
1198
  }
1185
- }), map(file => typeof file === 'string' ? null : file), shareReplay(1));
1199
+ }), shareReplay(1));
1186
1200
  this.compressedImageUrlString$ = this.compressedFile$.pipe(switchMap(async (file) => {
1187
1201
  if (!file)
1188
1202
  return null;
1189
1203
  const compressedStringImage = await imageCompression.getDataUrlFromFile(file);
1190
- return file.type.includes('svg')
1191
- ? this.domSanitizer.bypassSecurityTrustResourceUrl(compressedStringImage)
1192
- : compressedStringImage;
1204
+ return this.domSanitizer.bypassSecurityTrustResourceUrl(compressedStringImage);
1193
1205
  }), shareReplay(1));
1194
1206
  this.isUploaded = false;
1195
1207
  if (this.ngControl != null) {
@@ -1200,11 +1212,18 @@ class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1200
1212
  }
1201
1213
  ngOnInit() {
1202
1214
  super.ngOnInit();
1215
+ // this.inputValueUrl = this.value && this.config.mapInputValueToUrl?.(this.value) || this.value;
1203
1216
  this.imageSrcUrl$ = combineLatest([
1204
1217
  this.compressedImageUrlString$.pipe(startWith(null)),
1205
- this.control.valueChanges.pipe(startWith(this.value))
1218
+ this.control.valueChanges.pipe(startWith(this.value), map((controlValue) => {
1219
+ if (!controlValue)
1220
+ return null;
1221
+ if (controlValue instanceof File)
1222
+ return null;
1223
+ return this.config.mapInputValueToUrl?.(controlValue) || controlValue;
1224
+ }))
1206
1225
  ]).pipe(map(([compressedImageUrlString, controlUrl]) => {
1207
- return compressedImageUrlString || typeof controlUrl === 'string' && controlUrl || null;
1226
+ return compressedImageUrlString || controlUrl || null;
1208
1227
  }));
1209
1228
  this.disabled$ = this.config.disabledCallback
1210
1229
  ? this.config.disabledCallback(this.group, this.config)
@@ -1213,6 +1232,10 @@ class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1213
1232
  filesChanged(files) {
1214
1233
  if (files && files.length > 0) {
1215
1234
  const imageFile = files[0];
1235
+ // We clear the control here so that it is invalid while compressing
1236
+ // the input pipeline will take care of resetting it
1237
+ this.control.patchValue(null);
1238
+ // this.control.setErrors({compressing: {loadingMessage: 'Please wait while file compresses'}});
1216
1239
  this.control.markAsTouched();
1217
1240
  this.inputImageFileSubject.next(imageFile);
1218
1241
  }
@@ -1225,8 +1248,8 @@ class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1225
1248
  this.isUploaded = false;
1226
1249
  }
1227
1250
  resetToInitialValue(initialValue) {
1228
- this.inputImageFileSubject.next(initialValue);
1229
- // this.fileInputRef.nativeElement.value = initialValue
1251
+ this.inputImageFileSubject.next(null);
1252
+ this.control.patchValue(initialValue);
1230
1253
  this.isUploaded = false;
1231
1254
  }
1232
1255
  writeValue(value) { }
@@ -1236,10 +1259,10 @@ class ImageUploadFieldComponent extends ImageUploadFieldMixin {
1236
1259
  registerOnTouched(fn) { }
1237
1260
  }
1238
1261
  ImageUploadFieldComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageUploadFieldComponent, deps: [{ token: i4$1.NgControl, optional: true, self: true }, { token: i2$3.DomSanitizer }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1239
- ImageUploadFieldComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.1", type: ImageUploadFieldComponent, selector: "crispr-image-upload-field", viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config\"\n [group]=\"group\">\n\n <mat-form-field floatLabel=\"always\"\n [color]=\"config.color\"\n [appearance]=\"config.appearance\"\n [formGroup]=\"group\">\n <mat-label>{{config.label}}</mat-label>\n <input [formControlName]=\"config.controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config.acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config.imagePreviewHeight\">\n <crispr-selected-file\n *ngIf=\"(progress < 100) && inputImageFile$ | async as file; else dropzoneOrPreview\"\n [color]=\"config.color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"'Compressing: ' + file.name\"\n [showProgress]=\"true\"\n [progress]=\"progress\">\n </crispr-selected-file>\n\n <ng-template #dropzoneOrPreview>\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n <img *ngIf=\"imageSrcUrl$ | async as srcUrl; else noImage\" [src]=\"srcUrl\" alt=\"\">\n <ng-template #noImage>\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config.dropZoneText}}</h2>\n </div>\n </ng-template>\n\n </ng-template>\n </div>\n\n\n\n <!-- </ng-template> -->\n <div class=\"button-container\" *ngIf=\"true\">\n\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config.clearFilesButtonText}}\n </button>\n\n <button\n *ngIf=\"value\"\n mat-button\n (click)=\"resetToInitialValue(value)\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config.resetFilesButtonText}}\n </button>\n\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config.controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n </div>\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-form-field .mat-form-field-infix{padding-top:0}:host ::ng-deep .mat-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"], components: [{ type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { type: SelectedFileComponent, selector: "crispr-selected-file", inputs: ["fileName", "progress", "showProgress", "color"] }, { type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: i1.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }], directives: [{ type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i4$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i5.ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { type: i2$1.MatLabel, selector: "mat-label" }, { type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i4$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { type: i5.ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: FileDropzoneDirective, selector: "[crisprFileDropzone]", inputs: ["crisprFileDropzone"], outputs: ["fileDrop"] }], pipes: { "async": i2.AsyncPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true });
1262
+ ImageUploadFieldComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.1", type: ImageUploadFieldComponent, selector: "crispr-image-upload-field", viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config\"\n [group]=\"group\">\n\n <mat-form-field floatLabel=\"always\"\n [color]=\"config.color\"\n [appearance]=\"config.appearance\"\n [formGroup]=\"group\">\n <mat-label>{{config.label}}</mat-label>\n <input [formControlName]=\"config.controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config.acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config.imagePreviewHeight\">\n <crispr-selected-file\n *ngIf=\"(progress < 100) && inputImageFile$ | async as file; else dropzoneOrPreview\"\n [color]=\"config.color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"'Compressing: ' + file.name\"\n [showProgress]=\"true\"\n [progress]=\"progress\">\n </crispr-selected-file>\n\n <ng-template #dropzoneOrPreview>\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n <img *ngIf=\"imageSrcUrl$ | async as srcUrl; else noImage\" [src]=\"srcUrl\" alt=\"\">\n <ng-template #noImage>\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config.dropZoneText}}</h2>\n </div>\n </ng-template>\n\n </ng-template>\n </div>\n\n\n\n <!-- </ng-template> -->\n <div class=\"button-container\" *ngIf=\"true\">\n\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config.clearFilesButtonText}}\n </button>\n\n <button\n *ngIf=\"value\"\n mat-button\n (click)=\"resetToInitialValue(value)\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config.resetFilesButtonText}}\n </button>\n\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config.controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n </div>\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-form-field .mat-form-field-infix{padding-top:0}:host ::ng-deep .mat-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;box-sizing:border-box;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"], components: [{ type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { type: SelectedFileComponent, selector: "crispr-selected-file", inputs: ["fileName", "progress", "showProgress", "color"] }, { type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: i1.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }], directives: [{ type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i4$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i5.ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { type: i2$1.MatLabel, selector: "mat-label" }, { type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i4$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { type: i5.ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: FileDropzoneDirective, selector: "[crisprFileDropzone]", inputs: ["crisprFileDropzone"], outputs: ["fileDrop"] }], pipes: { "async": i2.AsyncPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true });
1240
1263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageUploadFieldComponent, decorators: [{
1241
1264
  type: Component,
1242
- args: [{ selector: 'crispr-image-upload-field', changeDetection: ChangeDetectionStrategy.OnPush, template: "<crispr-field-container\n [config]=\"config\"\n [group]=\"group\">\n\n <mat-form-field floatLabel=\"always\"\n [color]=\"config.color\"\n [appearance]=\"config.appearance\"\n [formGroup]=\"group\">\n <mat-label>{{config.label}}</mat-label>\n <input [formControlName]=\"config.controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config.acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config.imagePreviewHeight\">\n <crispr-selected-file\n *ngIf=\"(progress < 100) && inputImageFile$ | async as file; else dropzoneOrPreview\"\n [color]=\"config.color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"'Compressing: ' + file.name\"\n [showProgress]=\"true\"\n [progress]=\"progress\">\n </crispr-selected-file>\n\n <ng-template #dropzoneOrPreview>\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n <img *ngIf=\"imageSrcUrl$ | async as srcUrl; else noImage\" [src]=\"srcUrl\" alt=\"\">\n <ng-template #noImage>\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config.dropZoneText}}</h2>\n </div>\n </ng-template>\n\n </ng-template>\n </div>\n\n\n\n <!-- </ng-template> -->\n <div class=\"button-container\" *ngIf=\"true\">\n\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config.clearFilesButtonText}}\n </button>\n\n <button\n *ngIf=\"value\"\n mat-button\n (click)=\"resetToInitialValue(value)\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config.resetFilesButtonText}}\n </button>\n\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config.controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n </div>\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-form-field .mat-form-field-infix{padding-top:0}:host ::ng-deep .mat-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"] }]
1265
+ args: [{ selector: 'crispr-image-upload-field', changeDetection: ChangeDetectionStrategy.OnPush, template: "<crispr-field-container\n [config]=\"config\"\n [group]=\"group\">\n\n <mat-form-field floatLabel=\"always\"\n [color]=\"config.color\"\n [appearance]=\"config.appearance\"\n [formGroup]=\"group\">\n <mat-label>{{config.label}}</mat-label>\n <input [formControlName]=\"config.controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config.acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config.imagePreviewHeight\">\n <crispr-selected-file\n *ngIf=\"(progress < 100) && inputImageFile$ | async as file; else dropzoneOrPreview\"\n [color]=\"config.color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"'Compressing: ' + file.name\"\n [showProgress]=\"true\"\n [progress]=\"progress\">\n </crispr-selected-file>\n\n <ng-template #dropzoneOrPreview>\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n <img *ngIf=\"imageSrcUrl$ | async as srcUrl; else noImage\" [src]=\"srcUrl\" alt=\"\">\n <ng-template #noImage>\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config.dropZoneText}}</h2>\n </div>\n </ng-template>\n\n </ng-template>\n </div>\n\n\n\n <!-- </ng-template> -->\n <div class=\"button-container\" *ngIf=\"true\">\n\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config.clearFilesButtonText}}\n </button>\n\n <button\n *ngIf=\"value\"\n mat-button\n (click)=\"resetToInitialValue(value)\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config.resetFilesButtonText}}\n </button>\n\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config.controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n </div>\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-form-field .mat-form-field-infix{padding-top:0}:host ::ng-deep .mat-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;box-sizing:border-box;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"] }]
1243
1266
  }], ctorParameters: function () { return [{ type: i4$1.NgControl, decorators: [{
1244
1267
  type: Optional
1245
1268
  }, {
@@ -1299,14 +1322,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImpor
1299
1322
  function maxFileSizeValidator(maxFileSize) {
1300
1323
  return (control) => {
1301
1324
  const controlValue = control.value;
1302
- if (!(controlValue instanceof Blob))
1325
+ if (!(controlValue instanceof File)) {
1303
1326
  return null;
1327
+ }
1304
1328
  const actualFileSize = +convertBytesToMb(controlValue.size).toPrecision(3);
1305
1329
  if (maxFileSize > actualFileSize)
1306
1330
  return null;
1307
- control.setErrors({ maxFileSize: { maxFileSize, actualFileSize } });
1308
1331
  return { maxFileSize: { maxFileSize, actualFileSize } };
1309
1332
  };
1333
+ }
1334
+ function allowedFileExtValidator(allowedFileExtensions, onlyAllowFiles = true) {
1335
+ return (control) => {
1336
+ const controlValue = control.value;
1337
+ const isFile = controlValue instanceof File;
1338
+ if (!onlyAllowFiles && !isFile)
1339
+ return null;
1340
+ const actualExtension = isFile ? controlValue.name.split('.').pop().toLocaleLowerCase() : 'Not a file';
1341
+ const isAllowedExtension = allowedFileExtensions.includes(actualExtension);
1342
+ if (isAllowedExtension) {
1343
+ return null;
1344
+ }
1345
+ return { allowedExtension: { allowedFileExtensions, actualExtension } };
1346
+ };
1310
1347
  }
1311
1348
 
1312
1349
  // TODO: Should we import UI library?
@@ -1487,6 +1524,7 @@ var index$2 = /*#__PURE__*/Object.freeze({
1487
1524
  ImageUploadFieldComponent: ImageUploadFieldComponent,
1488
1525
  ImageUploadFieldModule: ImageUploadFieldModule,
1489
1526
  maxFileSizeValidator: maxFileSizeValidator,
1527
+ allowedFileExtValidator: allowedFileExtValidator,
1490
1528
  FileDropzoneDirective: FileDropzoneDirective,
1491
1529
  FileUploadFieldComponent: FileUploadFieldComponent,
1492
1530
  FileUploadFieldModule: FileUploadFieldModule,
@@ -1871,5 +1909,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImpor
1871
1909
  * Generated bundle index. Do not edit.
1872
1910
  */
1873
1911
 
1874
- export { AbstractAutocompleteComponent, AutocompleteChiplistFieldComponent, AutocompleteFieldComponent, ButtonComponent, CheckboxFieldComponent, ControlType, CrisprFieldComponent, CrisprFormComponent, CrisprFormsModule, DEFAULT_EMPTY_OPTIONS_MESSAGE, DatepickerFieldComponent, DividerComponent, FieldContainerComponent, FieldContainerModule, FileDropzoneDirective, FileUploadFieldComponent, FileUploadFieldModule, HeadingComponent, HeadingModule, ImageUploadFieldComponent, ImageUploadFieldModule, InputFieldComponent, MaterialModule, OptionComponent, RadioFieldComponent, SelectFieldComponent, SliderFieldComponent, TextareaFieldComponent, UnitConversionFieldComponent, buildFormGroupFromConfig, callOptionsIfFunction, checkControlForValues, checkControlsForValues, computeValueFromFields, createControlForType, crisprControlMixin, filterOptionsByLabel, isControlConfig, maxFileSizeValidator, observablifyOptions };
1912
+ export { AbstractAutocompleteComponent, AutocompleteChiplistFieldComponent, AutocompleteFieldComponent, ButtonComponent, CheckboxFieldComponent, ControlType, CrisprFieldComponent, CrisprFormComponent, CrisprFormsModule, DEFAULT_EMPTY_OPTIONS_MESSAGE, DatepickerFieldComponent, DividerComponent, FieldContainerComponent, FieldContainerModule, FileDropzoneDirective, FileUploadFieldComponent, FileUploadFieldModule, HeadingComponent, HeadingModule, ImageUploadFieldComponent, ImageUploadFieldModule, InputFieldComponent, MaterialModule, OptionComponent, RadioFieldComponent, SelectFieldComponent, SliderFieldComponent, TextareaFieldComponent, UnitConversionFieldComponent, allowedFileExtValidator, buildFormGroupFromConfig, callOptionsIfFunction, checkControlForValues, checkControlsForValues, computeValueFromFields, createControlForType, crisprControlMixin, filterOptionsByLabel, isControlConfig, maxFileSizeValidator, observablifyOptions };
1875
1913
  //# sourceMappingURL=tft-crispr-forms.mjs.map