spiderly 19.5.3 → 19.5.4-preview.1

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.
@@ -209,7 +209,7 @@ class SpiderlyFormGroup extends FormGroup {
209
209
  constructor(controls) {
210
210
  super(controls);
211
211
  this.trackingId = crypto.randomUUID();
212
- this.initSaveBody = () => null;
212
+ // TODO: Delete controlNamesFromHtml and add UIDoNotGenerate flag into ng entity generator, we shouldn't even add those into parentFormGroup
213
213
  this.controlNamesFromHtml = [];
214
214
  this.getControl = (formControlName) => {
215
215
  if (this.controlNamesFromHtml.findIndex((x) => x === formControlName) === -1)
@@ -285,10 +285,10 @@ class SpiderlyFormArray extends FormArray {
285
285
  class ValidatorAbstractService {
286
286
  constructor(translocoService) {
287
287
  this.translocoService = translocoService;
288
- this.isArrayEmpty = (control) => {
288
+ this.notEmpty = (control) => {
289
289
  const validator = () => {
290
290
  const value = control.value;
291
- const notEmptyRule = typeof value !== 'undefined' && value !== null && value.length !== 0;
291
+ const notEmptyRule = typeof value !== 'undefined' && value !== null && value !== '';
292
292
  const arrayValid = notEmptyRule;
293
293
  return arrayValid
294
294
  ? null
@@ -296,25 +296,32 @@ class ValidatorAbstractService {
296
296
  };
297
297
  validator.hasNotEmptyRule = true;
298
298
  control.required = true;
299
- return validator;
299
+ control.validator = validator;
300
+ control.updateValueAndValidity();
300
301
  };
301
- this.notEmpty = (control) => {
302
+ /** Validates that a SpiderlyFormArray (collection of form controls/groups) is not empty. */
303
+ this.isFormArrayEmpty = (control) => {
302
304
  const validator = () => {
303
- const value = control.value;
304
- const notEmptyRule = typeof value !== 'undefined' && value !== null && value !== '';
305
+ const value = control;
306
+ const notEmptyRule = typeof value !== 'undefined' && value !== null && value.length !== 0;
305
307
  const arrayValid = notEmptyRule;
306
308
  return arrayValid
307
309
  ? null
308
- : { _: this.translocoService.translate('NotEmpty') };
310
+ : {
311
+ _: this.translocoService.translate('ListCanNotBeEmpty', {
312
+ value: control.labelForDisplay,
313
+ }),
314
+ };
309
315
  };
310
316
  validator.hasNotEmptyRule = true;
311
317
  control.required = true;
312
- control.validator = validator;
318
+ control.setValidators(validator);
313
319
  control.updateValueAndValidity();
314
320
  };
315
- this.isFormArrayEmpty = (control) => {
321
+ /** Validates that a SpiderlyFormControl holding an array value (e.g., multi-select dropdown) is not empty. */
322
+ this.isArrayEmpty = (control) => {
316
323
  const validator = () => {
317
- const value = control;
324
+ const value = control.value;
318
325
  const notEmptyRule = typeof value !== 'undefined' && value !== null && value.length !== 0;
319
326
  const arrayValid = notEmptyRule;
320
327
  return arrayValid
@@ -743,10 +750,36 @@ class SpiderlyEditorComponent extends BaseControl {
743
750
  constructor(translocoService) {
744
751
  super(translocoService);
745
752
  this.translocoService = translocoService;
753
+ this.objectId = 0;
746
754
  }
747
755
  ngOnInit() {
748
756
  super.ngOnInit();
749
757
  }
758
+ onEditorInit(event) {
759
+ const quill = event.editor;
760
+ if (this.uploadImageMethod) {
761
+ const toolbar = quill.getModule('toolbar');
762
+ toolbar.addHandler('image', () => this.imageHandler(quill));
763
+ }
764
+ }
765
+ imageHandler(quill) {
766
+ const input = document.createElement('input');
767
+ input.setAttribute('type', 'file');
768
+ input.setAttribute('accept', 'image/*');
769
+ input.click();
770
+ input.onchange = async () => {
771
+ const file = input.files[0];
772
+ if (file) {
773
+ const formData = new FormData();
774
+ formData.append('file', file, `${this.objectId}-${file.name}`);
775
+ this.uploadImageMethod(formData).subscribe((imageUrl) => {
776
+ const range = quill.getSelection(true);
777
+ quill.insertEmbed(range.index, 'image', imageUrl);
778
+ quill.setSelection(range.index + 1);
779
+ });
780
+ }
781
+ };
782
+ }
750
783
  onClick() {
751
784
  let editableArea = this.editor.el.nativeElement.querySelector('.ql-editor');
752
785
  editableArea.onblur = () => {
@@ -765,7 +798,7 @@ class SpiderlyEditorComponent extends BaseControl {
765
798
  };
766
799
  }
767
800
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyEditorComponent, deps: [{ token: i1.TranslocoService }], target: i0.ɵɵFactoryTarget.Component }); }
768
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyEditorComponent, isStandalone: true, selector: "spiderly-editor", viewQueries: [{ propertyName: "editor", first: true, predicate: Editor, descendants: true }, { propertyName: "tooltip", first: true, predicate: Tooltip, descendants: true }], usesInheritance: true, ngImport: i0, template: "<!-- Can't put (onBlur) in this control -->\n\n<div style=\"display: flex; flex-direction: column; gap: 0.5rem\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <!-- Disable doesn't work on this control -->\n <p-editor\n *ngIf=\"control\"\n [formControl]=\"control\"\n [pTooltip]=\"getValidationErrrorMessages()\"\n [tooltipEvent]=\"errorMessageTooltipEvent\"\n tooltipPosition=\"bottom\"\n [tooltipDisabled]=\"control.valid\"\n tooltipStyleClass=\"spiderly-tooltip-invalid\"\n [readonly]=\"control.disabled\"\n [class]=\"control.invalid && control.dirty ? 'control-error-border' : ''\"\n [id]=\"control.label\"\n [placeholder]=\"placeholder\"\n (click)=\"onClick()\"\n [style]=\"{ height: '320px' }\"\n ></p-editor>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: EditorModule }, { kind: "component", type: i4$8.Editor, selector: "p-editor", inputs: ["style", "styleClass", "placeholder", "formats", "modules", "bounds", "scrollingContainer", "debug", "readonly"], outputs: ["onInit", "onTextChange", "onSelectionChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: RequiredComponent, selector: "required" }] }); }
801
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyEditorComponent, isStandalone: true, selector: "spiderly-editor", inputs: { uploadImageMethod: "uploadImageMethod", objectId: "objectId" }, viewQueries: [{ propertyName: "editor", first: true, predicate: Editor, descendants: true }, { propertyName: "tooltip", first: true, predicate: Tooltip, descendants: true }], usesInheritance: true, ngImport: i0, template: "<!-- Can't put (onBlur) in this control -->\n\n<div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 0 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <!-- Disable doesn't work on this control -->\n <p-editor\n *ngIf=\"control\"\n [formControl]=\"control\"\n [pTooltip]=\"getValidationErrrorMessages()\"\n [tooltipEvent]=\"errorMessageTooltipEvent\"\n tooltipPosition=\"bottom\"\n [tooltipDisabled]=\"control.valid\"\n tooltipStyleClass=\"spiderly-tooltip-invalid\"\n [readonly]=\"control.disabled\"\n [class]=\"control.invalid && control.dirty ? 'control-error-border' : ''\"\n [id]=\"control.label\"\n [placeholder]=\"placeholder\"\n (click)=\"onClick()\"\n (onInit)=\"onEditorInit($event)\"\n [style]=\"{ height: '320px' }\"\n ></p-editor>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: EditorModule }, { kind: "component", type: i4$8.Editor, selector: "p-editor", inputs: ["style", "styleClass", "placeholder", "formats", "modules", "bounds", "scrollingContainer", "debug", "readonly"], outputs: ["onInit", "onTextChange", "onSelectionChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: RequiredComponent, selector: "required" }] }); }
769
802
  }
770
803
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyEditorComponent, decorators: [{
771
804
  type: Component,
@@ -776,13 +809,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
776
809
  EditorModule,
777
810
  TooltipModule,
778
811
  RequiredComponent,
779
- ], template: "<!-- Can't put (onBlur) in this control -->\n\n<div style=\"display: flex; flex-direction: column; gap: 0.5rem\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <!-- Disable doesn't work on this control -->\n <p-editor\n *ngIf=\"control\"\n [formControl]=\"control\"\n [pTooltip]=\"getValidationErrrorMessages()\"\n [tooltipEvent]=\"errorMessageTooltipEvent\"\n tooltipPosition=\"bottom\"\n [tooltipDisabled]=\"control.valid\"\n tooltipStyleClass=\"spiderly-tooltip-invalid\"\n [readonly]=\"control.disabled\"\n [class]=\"control.invalid && control.dirty ? 'control-error-border' : ''\"\n [id]=\"control.label\"\n [placeholder]=\"placeholder\"\n (click)=\"onClick()\"\n [style]=\"{ height: '320px' }\"\n ></p-editor>\n</div>\n" }]
812
+ ], template: "<!-- Can't put (onBlur) in this control -->\n\n<div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 0 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <!-- Disable doesn't work on this control -->\n <p-editor\n *ngIf=\"control\"\n [formControl]=\"control\"\n [pTooltip]=\"getValidationErrrorMessages()\"\n [tooltipEvent]=\"errorMessageTooltipEvent\"\n tooltipPosition=\"bottom\"\n [tooltipDisabled]=\"control.valid\"\n tooltipStyleClass=\"spiderly-tooltip-invalid\"\n [readonly]=\"control.disabled\"\n [class]=\"control.invalid && control.dirty ? 'control-error-border' : ''\"\n [id]=\"control.label\"\n [placeholder]=\"placeholder\"\n (click)=\"onClick()\"\n (onInit)=\"onEditorInit($event)\"\n [style]=\"{ height: '320px' }\"\n ></p-editor>\n</div>\n" }]
780
813
  }], ctorParameters: () => [{ type: i1.TranslocoService }], propDecorators: { editor: [{
781
814
  type: ViewChild,
782
815
  args: [Editor]
783
816
  }], tooltip: [{
784
817
  type: ViewChild,
785
818
  args: [Tooltip]
819
+ }], uploadImageMethod: [{
820
+ type: Input
821
+ }], objectId: [{
822
+ type: Input
786
823
  }] } });
787
824
 
788
825
  class SpiderlyButtonBaseComponent {
@@ -887,6 +924,7 @@ function getMimeTypeForFileName(fileName) {
887
924
  '.png': 'image/png',
888
925
  '.webp': 'image/webp',
889
926
  '.gif': 'image/gif',
927
+ '.svg': 'image/svg+xml',
890
928
  '.pdf': 'application/pdf',
891
929
  '.txt': 'text/plain',
892
930
  '.html': 'text/html',
@@ -1033,7 +1071,7 @@ function toCommaSeparatedString(input) {
1033
1071
  return stringList[0] ?? '';
1034
1072
  }
1035
1073
  }
1036
- function isImageFileType(mimeType) {
1074
+ function isFileImageType(mimeType) {
1037
1075
  if (mimeType.startsWith('image/')) {
1038
1076
  return true;
1039
1077
  }
@@ -1136,6 +1174,16 @@ const primitiveArrayTypes = [
1136
1174
  'Date[]',
1137
1175
  'string[]',
1138
1176
  ];
1177
+ function getImageDimensions(file) {
1178
+ return new Promise((resolve) => {
1179
+ const img = new Image();
1180
+ img.onload = () => {
1181
+ resolve({ width: img.width, height: img.height });
1182
+ URL.revokeObjectURL(img.src);
1183
+ };
1184
+ img.src = URL.createObjectURL(file);
1185
+ });
1186
+ }
1139
1187
 
1140
1188
  class SpiderlyMessageService {
1141
1189
  // TODO FT: nece da prikaze poruku ako je neki angular error koji se dogodi tek nakon api poziva
@@ -1221,7 +1269,7 @@ class SpiderlyFileComponent extends BaseControl {
1221
1269
  }
1222
1270
  filesSelected(event) {
1223
1271
  const file = event.files[0];
1224
- if (this.isImageFileType(file.type) &&
1272
+ if (this.isFileImageType(file.type) &&
1225
1273
  this.hasImageDimensionConstraints()) {
1226
1274
  this.files = [];
1227
1275
  this.validatorService
@@ -1240,10 +1288,17 @@ class SpiderlyFileComponent extends BaseControl {
1240
1288
  this.emitFileSelected(file);
1241
1289
  }
1242
1290
  }
1243
- emitFileSelected(file) {
1291
+ async emitFileSelected(file) {
1244
1292
  const formData = new FormData();
1245
1293
  formData.append('file', file, `${this.objectId}-${file.name}`);
1246
- this.onFileSelected.next(new SpiderlyFileSelectEvent({ file: file, formData: formData }));
1294
+ let width;
1295
+ let height;
1296
+ if (this.isFileImageType(file.type)) {
1297
+ const dimensions = await getImageDimensions(file);
1298
+ width = dimensions.width;
1299
+ height = dimensions.height;
1300
+ }
1301
+ this.onFileSelected.next(new SpiderlyFileSelectEvent({ file, formData, width, height }));
1247
1302
  }
1248
1303
  hasImageDimensionConstraints() {
1249
1304
  return this.imageWidth > 0 || this.imageHeight > 0;
@@ -1284,14 +1339,14 @@ class SpiderlyFileComponent extends BaseControl {
1284
1339
  const file = new File([blob], fileName, { type: mimeType });
1285
1340
  return file;
1286
1341
  }
1287
- isImageFileType(mimeType) {
1288
- return isImageFileType(mimeType);
1342
+ isFileImageType(mimeType) {
1343
+ return isFileImageType(mimeType);
1289
1344
  }
1290
1345
  isExcelFileType(mimeType) {
1291
1346
  return isExcelFileType(mimeType);
1292
1347
  }
1293
1348
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, deps: [{ token: i1.TranslocoService }, { token: SpiderlyMessageService }, { token: ValidatorAbstractService }], target: i0.ɵɵFactoryTarget.Component }); }
1294
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isCloudinaryFileData: "isCloudinaryFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"1000000\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-content-between align-items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-content-center p-0 gap-5\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-3 py-3 flex flex-column align-items-center gap-3\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isImageFileType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex align-items-center justify-content-center flex-column\">\n <i\n class=\"pi pi-cloud-upload border-2 border-circle p-5 text-8xl text-400 border-400 mt-3\"\n ></i>\n <p class=\"mt-4 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$2.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i1$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
1349
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isCloudinaryFileData: "isCloudinaryFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"1000000\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-content-between align-items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-content-center p-0 gap-5\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-3 py-3 flex flex-column align-items-center gap-3\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex align-items-center justify-content-center flex-column\">\n <i\n class=\"pi pi-cloud-upload border-2 border-circle p-5 text-8xl text-400 border-400 mt-3\"\n ></i>\n <p class=\"mt-4 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$2.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i1$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
1295
1350
  }
1296
1351
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, decorators: [{
1297
1352
  type: Component,
@@ -1303,7 +1358,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
1303
1358
  RequiredComponent,
1304
1359
  SpiderlyButtonComponent,
1305
1360
  TranslocoDirective,
1306
- ], template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"1000000\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-content-between align-items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-content-center p-0 gap-5\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-3 py-3 flex flex-column align-items-center gap-3\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isImageFileType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex align-items-center justify-content-center flex-column\">\n <i\n class=\"pi pi-cloud-upload border-2 border-circle p-5 text-8xl text-400 border-400 mt-3\"\n ></i>\n <p class=\"mt-4 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n" }]
1361
+ ], template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"1000000\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-content-between align-items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-content-center p-0 gap-5\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-3 py-3 flex flex-column align-items-center gap-3\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex align-items-center justify-content-center flex-column\">\n <i\n class=\"pi pi-cloud-upload border-2 border-circle p-5 text-8xl text-400 border-400 mt-3\"\n ></i>\n <p class=\"mt-4 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n" }]
1307
1362
  }], ctorParameters: () => [{ type: i1.TranslocoService }, { type: SpiderlyMessageService }, { type: ValidatorAbstractService }], propDecorators: { onFileSelected: [{
1308
1363
  type: Output
1309
1364
  }], onFileRemoved: [{
@@ -1328,10 +1383,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
1328
1383
  type: Input
1329
1384
  }] } });
1330
1385
  class SpiderlyFileSelectEvent extends BaseEntity {
1331
- constructor({ file, formData, } = {}) {
1386
+ constructor({ file, formData, width, height, } = {}) {
1332
1387
  super();
1333
1388
  this.file = file;
1334
1389
  this.formData = formData;
1390
+ this.width = width;
1391
+ this.height = height;
1335
1392
  }
1336
1393
  static { this.typeName = 'SpiderlyFileSelectEvent'; }
1337
1394
  }
@@ -1548,12 +1605,12 @@ class UserRole extends BaseEntity {
1548
1605
  }
1549
1606
  class LoginVerificationToken extends BaseEntity {
1550
1607
  static { this.typeName = 'LoginVerificationToken'; }
1551
- constructor({ email, userId, browserId, expireAt, } = {}) {
1608
+ constructor({ email, userId, browserId, expiresAt, } = {}) {
1552
1609
  super();
1553
1610
  this.email = email;
1554
1611
  this.userId = userId;
1555
1612
  this.browserId = browserId;
1556
- this.expireAt = expireAt;
1613
+ this.expiresAt = expiresAt;
1557
1614
  }
1558
1615
  static { this.schema = {
1559
1616
  email: {
@@ -1565,7 +1622,7 @@ class LoginVerificationToken extends BaseEntity {
1565
1622
  browserId: {
1566
1623
  type: 'string',
1567
1624
  },
1568
- expireAt: {
1625
+ expiresAt: {
1569
1626
  type: 'Date',
1570
1627
  },
1571
1628
  }; }
@@ -1622,21 +1679,8 @@ class SpiderlyError extends Error {
1622
1679
  }
1623
1680
  }
1624
1681
 
1625
- class TranslateLabelsAbstractService {
1626
- constructor() { }
1627
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: TranslateLabelsAbstractService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1628
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: TranslateLabelsAbstractService, providedIn: 'root' }); }
1629
- }
1630
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: TranslateLabelsAbstractService, decorators: [{
1631
- type: Injectable,
1632
- args: [{
1633
- providedIn: 'root',
1634
- }]
1635
- }], ctorParameters: () => [] });
1636
-
1637
1682
  class BaseFormService {
1638
- constructor(translateLabelsService, validatorService, messageService, translocoService) {
1639
- this.translateLabelsService = translateLabelsService;
1683
+ constructor(validatorService, messageService, translocoService) {
1640
1684
  this.validatorService = validatorService;
1641
1685
  this.messageService = messageService;
1642
1686
  this.translocoService = translocoService;
@@ -1656,12 +1700,14 @@ class BaseFormService {
1656
1700
  propSchema.type !== 'Namebook[]') {
1657
1701
  if (existingControl instanceof SpiderlyFormArray) {
1658
1702
  this.initFormArray(existingControl, propSchema.nestedConstructor, propInitialValue);
1703
+ this.validatorService.setFormArrayValidator(existingControl, targetClass.typeName);
1659
1704
  }
1660
1705
  else {
1661
1706
  const control = new SpiderlyFormArray([], this.translocoService, this);
1662
1707
  this.initFormArray(control, propSchema.nestedConstructor, propInitialValue);
1663
1708
  control.label = formControlName;
1664
1709
  control.labelForDisplay = this.getTranslatedLabel(formControlName);
1710
+ this.validatorService.setFormArrayValidator(control, targetClass.typeName);
1665
1711
  formGroup.setControl(formControlName, control);
1666
1712
  }
1667
1713
  }
@@ -1681,6 +1727,9 @@ class BaseFormService {
1681
1727
  if (formControlName === 'id' && !propInitialValue) {
1682
1728
  propInitialValue = 0;
1683
1729
  }
1730
+ if (propSchema.type.endsWith('[]') && propInitialValue == null) {
1731
+ propInitialValue = [];
1732
+ }
1684
1733
  if (existingControl instanceof SpiderlyFormControl) {
1685
1734
  existingControl.setValue(propInitialValue);
1686
1735
  }
@@ -1755,7 +1804,7 @@ class BaseFormService {
1755
1804
  else if (formControlName.endsWith('DisplayName')) {
1756
1805
  formControlName = formControlName.replace('DisplayName', '');
1757
1806
  }
1758
- return this.translateLabelsService.translate(formControlName);
1807
+ return this.translocoService.translate(firstCharToUpper(formControlName));
1759
1808
  }
1760
1809
  addNewFormGroupToFormArray(formArray, targetClass, initialValues, index) {
1761
1810
  let helperFormGroup = new SpiderlyFormGroup({});
@@ -1815,6 +1864,11 @@ class BaseFormService {
1815
1864
  });
1816
1865
  }
1817
1866
  else if (control instanceof SpiderlyFormArray) {
1867
+ if (control.errors) {
1868
+ control.markAsDirty();
1869
+ this.messageService.warningMessage(control.errors['_']);
1870
+ invalid = true;
1871
+ }
1818
1872
  control.controls.forEach((nestedControl) => {
1819
1873
  if (!this.isControlValid(nestedControl)) {
1820
1874
  invalid = true;
@@ -1826,7 +1880,7 @@ class BaseFormService {
1826
1880
  }
1827
1881
  return true;
1828
1882
  }
1829
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormService, deps: [{ token: TranslateLabelsAbstractService }, { token: ValidatorAbstractService }, { token: SpiderlyMessageService }, { token: i1.TranslocoService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1883
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormService, deps: [{ token: ValidatorAbstractService }, { token: SpiderlyMessageService }, { token: i1.TranslocoService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1830
1884
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormService, providedIn: 'root' }); }
1831
1885
  }
1832
1886
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormService, decorators: [{
@@ -1834,7 +1888,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
1834
1888
  args: [{
1835
1889
  providedIn: 'root',
1836
1890
  }]
1837
- }], ctorParameters: () => [{ type: TranslateLabelsAbstractService }, { type: ValidatorAbstractService }, { type: SpiderlyMessageService }, { type: i1.TranslocoService }] });
1891
+ }], ctorParameters: () => [{ type: ValidatorAbstractService }, { type: SpiderlyMessageService }, { type: i1.TranslocoService }] });
1838
1892
 
1839
1893
  class BaseFormCopy {
1840
1894
  constructor(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService) {
@@ -1846,28 +1900,81 @@ class BaseFormCopy {
1846
1900
  this.route = route;
1847
1901
  this.translocoService = translocoService;
1848
1902
  this.baseFormService = baseFormService;
1903
+ /**
1904
+ * The root form group that holds all form controls, typed to `TSaveBody`.
1905
+ * Assign `saveObservableMethod` on it to define the HTTP call used for saving.
1906
+ * The form controls are built automatically from the `TSaveBody` schema when you call
1907
+ * `baseFormService.initFormGroup(this.parentFormGroup, saveBodyClass, saveBody)`.
1908
+ *
1909
+ * @example
1910
+ * ```ts
1911
+ * this.parentFormGroup.saveObservableMethod = this.apiService.saveProduct;
1912
+ *
1913
+ * this.baseFormService.initFormGroup(
1914
+ * this.parentFormGroup,
1915
+ * ProductSaveBody,
1916
+ * saveBody,
1917
+ * );
1918
+ * ```
1919
+ */
1849
1920
  this.parentFormGroup = new SpiderlyFormGroup({});
1921
+ /**
1922
+ * The toast message displayed after a successful save.
1923
+ * Override this to customize the success notification text for a specific entity.
1924
+ * If you want to change the message for all entities, update the `SuccessfulSaveToastDescription` key
1925
+ * in your translation JSON file instead.
1926
+ *
1927
+ * @example
1928
+ * ```ts
1929
+ * this.successfulSaveToastDescription = 'Product saved successfully!';
1930
+ * ```
1931
+ */
1850
1932
  this.successfulSaveToastDescription = this.translocoService.translate('SuccessfulSaveToastDescription');
1851
1933
  //#region Model
1934
+ /**
1935
+ * Executes the save flow for the form. The execution order is:
1936
+ * 1. Builds the save body from the form's raw value.
1937
+ * 2. Calls {@link onBeforeSave} — use this to modify the save body before validation.
1938
+ * 3. Validates the form. If invalid, shows an error message and stops.
1939
+ * 4. Sends the save HTTP request via `saveObservableMethod`.
1940
+ * 5. Calls {@link onAfterSaveRequest} — fires immediately after the request is sent, before the response arrives.
1941
+ * 6. On successful response: shows a success toast, reroutes, and calls {@link onAfterSave}. The form is re-initialized only when `rerouteToParentSlugAfterSave` is `false`.
1942
+ *
1943
+ * @param rerouteToParentSlugAfterSave - When `true` (default), navigates to the parent URL after save. When `false`, re-initializes the form and navigates to the saved object's URL.
1944
+ *
1945
+ * @example
1946
+ * ```html
1947
+ * <button (click)="onSave()">Save</button>
1948
+ * ```
1949
+ *
1950
+ * @example
1951
+ * ```html
1952
+ * <!-- Save and stay on the saved object's page -->
1953
+ * <button (click)="onSave(false)">Save and stay</button>
1954
+ * ```
1955
+ */
1852
1956
  // onSave method is here only because of the hooks, we should move everything except them to the BaseFromService
1853
- this.onSave = (reroute = true) => {
1957
+ this.onSave = (rerouteToParentSlugAfterSave = true) => {
1854
1958
  if (!this.saveBodyClass)
1855
1959
  throw new SpiderlyError('You did not initialize saveBodyClass');
1856
1960
  if (!this.mainUIFormClass)
1857
1961
  throw new SpiderlyError('You did not initialize mainUIFormClass');
1858
- this.saveBody = this.parentFormGroup.initSaveBody();
1859
- this.onBeforeSave(this.saveBody);
1860
- this.saveBody = this.saveBody ?? this.parentFormGroup.getRawValue();
1962
+ let saveBody = this.parentFormGroup.getRawValue();
1963
+ this.onBeforeSave(saveBody);
1861
1964
  const isValid = this.baseFormService.isControlValid(this.parentFormGroup);
1862
1965
  if (isValid) {
1863
1966
  this.parentFormGroup
1864
- .saveObservableMethod(this.saveBody)
1967
+ .saveObservableMethod(saveBody)
1865
1968
  .subscribe((res) => {
1866
1969
  this.messageService.successMessage(this.successfulSaveToastDescription);
1867
- this.baseFormService.initFormGroup(this.parentFormGroup, this.mainUIFormClass, res);
1868
- if (reroute) {
1970
+ if (rerouteToParentSlugAfterSave) {
1971
+ this.rerouteToSavedObject(undefined);
1972
+ }
1973
+ else {
1974
+ saveBody = this.baseFormService.mapMainUIFormToSaveBody(this.mainUIFormClass, res);
1975
+ this.baseFormService.initFormGroup(this.parentFormGroup, this.saveBodyClass, saveBody);
1869
1976
  const saveBodyMainDTOKey = this.baseFormService.getSaveBodyMainDTOKey(this.saveBodyClass);
1870
- const savedObjectId = res[saveBodyMainDTOKey]?.id;
1977
+ const savedObjectId = saveBody[saveBodyMainDTOKey]?.id;
1871
1978
  this.rerouteToSavedObject(savedObjectId); // You always need to have id, because of id == 0 and version change
1872
1979
  }
1873
1980
  this.onAfterSave();
@@ -1878,6 +1985,21 @@ class BaseFormCopy {
1878
1985
  this.baseFormService.showInvalidFieldsMessage();
1879
1986
  }
1880
1987
  };
1988
+ /**
1989
+ * Handles navigation after a successful save.
1990
+ * Override this to customize the post-save navigation behavior.
1991
+ * By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
1992
+ *
1993
+ * @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
1994
+ *
1995
+ * @example
1996
+ * ```ts
1997
+ * // Override to navigate to a custom route after save
1998
+ * rerouteToSavedObject = (rerouteId: number | string): void => {
1999
+ * this.router.navigateByUrl(`/products/${rerouteId}/details`);
2000
+ * };
2001
+ * ```
2002
+ */
1881
2003
  this.rerouteToSavedObject = (rerouteId) => {
1882
2004
  if (rerouteId == null) {
1883
2005
  const currentUrl = this.router.url;
@@ -1890,8 +2012,43 @@ class BaseFormCopy {
1890
2012
  const newUrl = segments.join('/');
1891
2013
  this.router.navigateByUrl(newUrl);
1892
2014
  };
2015
+ /**
2016
+ * Hook that runs **before** form validation and the save request.
2017
+ * Use this to modify the save body or perform any pre-save logic (e.g., transforming data, setting computed fields).
2018
+ *
2019
+ * @param saveBody - The current save body built from the form's raw value. Mutate it directly to change what gets sent to the server.
2020
+ *
2021
+ * @example
2022
+ * ```ts
2023
+ * onBeforeSave = (saveBody?: ProductSaveBody) => {
2024
+ * saveBody.productDTO.fullName = saveBody.productDTO.firstName + ' ' + saveBody.productDTO.lastName;
2025
+ * };
2026
+ * ```
2027
+ */
1893
2028
  this.onBeforeSave = (saveBody) => { };
2029
+ /**
2030
+ * Hook that runs **after** a successful save response is received.
2031
+ * Use this for post-save side effects (e.g., refreshing related data, showing additional notifications).
2032
+ *
2033
+ * @example
2034
+ * ```ts
2035
+ * onAfterSave = () => {
2036
+ * this.loadRelatedProducts();
2037
+ * };
2038
+ * ```
2039
+ */
1894
2040
  this.onAfterSave = () => { };
2041
+ /**
2042
+ * Hook that runs immediately **after** the save HTTP request is sent, but **before** the response arrives.
2043
+ * Use this for side effects that should happen as soon as the request is dispatched (e.g., disabling UI elements, starting a loading indicator).
2044
+ *
2045
+ * @example
2046
+ * ```ts
2047
+ * onAfterSaveRequest = () => {
2048
+ * this.isSaving = true;
2049
+ * };
2050
+ * ```
2051
+ */
1895
2052
  this.onAfterSaveRequest = () => { };
1896
2053
  }
1897
2054
  ngOnInit() { }
@@ -1961,6 +2118,7 @@ class ConfigServiceBase {
1961
2118
  constructor() {
1962
2119
  this.production = false;
1963
2120
  this.frontendUrl = 'http://localhost:4200';
2121
+ this.showGoogleAuth = false;
1964
2122
  this.companyName = 'Company Name';
1965
2123
  this.primaryColor = '#111b2c';
1966
2124
  /* URLs */
@@ -2254,7 +2412,6 @@ class AuthComponent {
2254
2412
  this.authService = authService;
2255
2413
  this.initCompanyAuthDialogDetailsSubscription = null;
2256
2414
  this.onCompanyNameChange = new EventEmitter();
2257
- this.showGoogleAuth = false;
2258
2415
  }
2259
2416
  ngOnInit() {
2260
2417
  this.initCompanyDetails();
@@ -2279,15 +2436,13 @@ class AuthComponent {
2279
2436
  }
2280
2437
  }
2281
2438
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthComponent, deps: [{ token: ConfigServiceBase }, { token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
2282
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: AuthComponent, isStandalone: true, selector: "auth", inputs: { showGoogleAuth: "showGoogleAuth" }, outputs: { onCompanyNameChange: "onCompanyNameChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex min-h-screen overflow-hidden\" style=\"padding: 20px\">\n <div class=\"flex flex-column w-full\">\n <div\n class=\"w-full sm:w-30rem\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-6 px-5 sm:px-6\" style=\"border-radius: 45px\">\n <div class=\"text-center\" style=\"margin-bottom: 38px\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n height=\"60\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </div>\n\n <ng-content></ng-content>\n\n <div *ngIf=\"showGoogleAuth\">\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 7px;\n justify-content: center;\n margin-bottom: 16px;\n \"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div>\n <!-- https://code-maze.com/how-to-sign-in-with-google-angular-aspnet-webapi/ -->\n <google-button\n (loginWithGoogle)=\"onGoogleSignIn($event)\"\n [label]=\"t('ContinueWithGoogle')\"\n ></google-button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: GoogleButtonComponent, selector: "google-button", inputs: ["label"], outputs: ["loginWithGoogle"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
2439
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: AuthComponent, isStandalone: true, selector: "auth", outputs: { onCompanyNameChange: "onCompanyNameChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex min-h-screen overflow-hidden\" style=\"padding: 20px\">\n <div class=\"flex flex-column w-full\">\n <div\n class=\"w-full sm:w-30rem\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-6 px-5 sm:px-6\" style=\"border-radius: 45px\">\n <div class=\"text-center\" style=\"margin-bottom: 38px\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n height=\"60\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </div>\n\n <ng-content></ng-content>\n\n <div *ngIf=\"config.showGoogleAuth\">\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 7px;\n justify-content: center;\n margin-bottom: 16px;\n \"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div>\n <!-- https://code-maze.com/how-to-sign-in-with-google-angular-aspnet-webapi/ -->\n <google-button\n (loginWithGoogle)=\"onGoogleSignIn($event)\"\n [label]=\"t('ContinueWithGoogle')\"\n ></google-button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: GoogleButtonComponent, selector: "google-button", inputs: ["label"], outputs: ["loginWithGoogle"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
2283
2440
  }
2284
2441
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthComponent, decorators: [{
2285
2442
  type: Component,
2286
- args: [{ selector: 'auth', imports: [CommonModule, GoogleButtonComponent, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <div class=\"flex min-h-screen overflow-hidden\" style=\"padding: 20px\">\n <div class=\"flex flex-column w-full\">\n <div\n class=\"w-full sm:w-30rem\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-6 px-5 sm:px-6\" style=\"border-radius: 45px\">\n <div class=\"text-center\" style=\"margin-bottom: 38px\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n height=\"60\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </div>\n\n <ng-content></ng-content>\n\n <div *ngIf=\"showGoogleAuth\">\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 7px;\n justify-content: center;\n margin-bottom: 16px;\n \"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div>\n <!-- https://code-maze.com/how-to-sign-in-with-google-angular-aspnet-webapi/ -->\n <google-button\n (loginWithGoogle)=\"onGoogleSignIn($event)\"\n [label]=\"t('ContinueWithGoogle')\"\n ></google-button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n" }]
2443
+ args: [{ selector: 'auth', imports: [CommonModule, GoogleButtonComponent, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <div class=\"flex min-h-screen overflow-hidden\" style=\"padding: 20px\">\n <div class=\"flex flex-column w-full\">\n <div\n class=\"w-full sm:w-30rem\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-6 px-5 sm:px-6\" style=\"border-radius: 45px\">\n <div class=\"text-center\" style=\"margin-bottom: 38px\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n height=\"60\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </div>\n\n <ng-content></ng-content>\n\n <div *ngIf=\"config.showGoogleAuth\">\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 7px;\n justify-content: center;\n margin-bottom: 16px;\n \"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div>\n <!-- https://code-maze.com/how-to-sign-in-with-google-angular-aspnet-webapi/ -->\n <google-button\n (loginWithGoogle)=\"onGoogleSignIn($event)\"\n [label]=\"t('ContinueWithGoogle')\"\n ></google-button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n" }]
2287
2444
  }], ctorParameters: () => [{ type: ConfigServiceBase }, { type: AuthServiceBase }], propDecorators: { onCompanyNameChange: [{
2288
2445
  type: Output
2289
- }], showGoogleAuth: [{
2290
- type: Input
2291
2446
  }] } });
2292
2447
 
2293
2448
  class PanelBodyComponent {
@@ -2580,7 +2735,7 @@ class LoginComponent extends BaseFormCopy {
2580
2735
  });
2581
2736
  }
2582
2737
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: LoginComponent, deps: [{ token: i0.KeyValueDiffers }, { token: i1$3.HttpClient }, { token: SpiderlyMessageService }, { token: i0.ChangeDetectorRef }, { token: i3$2.Router }, { token: i3$2.ActivatedRoute }, { token: i1.TranslocoService }, { token: BaseFormService }, { token: AuthServiceBase }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
2583
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: LoginComponent, isStandalone: true, selector: "app-login", usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (loginFormGroup != null) {\n @if (showEmailSentDialog == false) {\n <auth (onCompanyNameChange)=\"companyNameChange($event)\">\n <!-- We are not loading anything from the server here so we don't need defer block -->\n <form [formGroup]=\"loginFormGroup\" style=\"margin-bottom: 16px\">\n <div class=\"col-12\" style=\"padding-left: 0; padding-right: 0\">\n <spiderly-textbox\n [control]=\"loginFormGroup.getControl('email')\"\n ></spiderly-textbox>\n </div>\n\n <div class=\"mb-4 gap-5\">\n <div class=\"text-center\" style=\"font-size: smaller\">\n {{ t(\"AgreementsOnRegister\") }}\n <b\n routerLink=\"/user-agreement\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"UserAgreement\") }}</b\n >\n {{ t(\"and\") }}\n <b\n routerLink=\"/privacy-policy\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"PrivacyPolicy\") }}</b\n >.\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column; gap: 16px\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"sendLoginVerificationEmail()\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n type=\"submit\"\n ></spiderly-button>\n </div>\n </form>\n </auth>\n } @else {\n <login-verification\n [email]=\"loginFormGroup.controls.email.getRawValue()\"\n ></login-verification>\n }\n } @else {\n <!-- TODO: Add skeleton -->\n }\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: AuthComponent, selector: "auth", inputs: ["showGoogleAuth"], outputs: ["onCompanyNameChange"] }, { kind: "ngmodule", type: SpiderlyControlsModule }, { kind: "component", type: SpiderlyTextboxComponent, selector: "spiderly-textbox", inputs: ["showButton", "buttonIcon"], outputs: ["onButtonClick"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "component", type: LoginVerificationComponent, selector: "login-verification", inputs: ["email", "userId"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
2738
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: LoginComponent, isStandalone: true, selector: "app-login", usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (loginFormGroup != null) {\n @if (showEmailSentDialog == false) {\n <auth (onCompanyNameChange)=\"companyNameChange($event)\">\n <!-- We are not loading anything from the server here so we don't need defer block -->\n <form [formGroup]=\"loginFormGroup\" style=\"margin-bottom: 16px\">\n <div class=\"col-12\" style=\"padding-left: 0; padding-right: 0\">\n <spiderly-textbox\n [control]=\"loginFormGroup.getControl('email')\"\n ></spiderly-textbox>\n </div>\n\n <div class=\"mb-4 gap-5\">\n <div class=\"text-center\" style=\"font-size: smaller\">\n {{ t(\"AgreementsOnRegister\") }}\n <b\n routerLink=\"/user-agreement\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"UserAgreement\") }}</b\n >\n {{ t(\"and\") }}\n <b\n routerLink=\"/privacy-policy\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"PrivacyPolicy\") }}</b\n >.\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column; gap: 16px\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"sendLoginVerificationEmail()\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n type=\"submit\"\n ></spiderly-button>\n </div>\n </form>\n </auth>\n } @else {\n <login-verification\n [email]=\"loginFormGroup.controls.email.getRawValue()\"\n ></login-verification>\n }\n } @else {\n <!-- TODO: Add skeleton -->\n }\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: AuthComponent, selector: "auth", outputs: ["onCompanyNameChange"] }, { kind: "ngmodule", type: SpiderlyControlsModule }, { kind: "component", type: SpiderlyTextboxComponent, selector: "spiderly-textbox", inputs: ["showButton", "buttonIcon"], outputs: ["onButtonClick"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "component", type: LoginVerificationComponent, selector: "login-verification", inputs: ["email", "userId"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
2584
2739
  }
2585
2740
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: LoginComponent, decorators: [{
2586
2741
  type: Component,
@@ -4379,21 +4534,6 @@ class LazyLoadSelectedIdsResult extends BaseEntity {
4379
4534
  class MenuChangeEvent {
4380
4535
  }
4381
4536
 
4382
- class MimeTypes {
4383
- constructor(value) {
4384
- this.value = value;
4385
- }
4386
- static { this.Pdf = new MimeTypes('application/pdf'); }
4387
- static { this.Zip = new MimeTypes('application/zip'); }
4388
- static { this.Jpeg = new MimeTypes('image/jpeg'); }
4389
- static { this.Png = new MimeTypes('image/png'); }
4390
- static { this.Svg = new MimeTypes('image/svg'); }
4391
- static { this.Webp = new MimeTypes('image/webp'); }
4392
- toString() {
4393
- return this.value;
4394
- }
4395
- }
4396
-
4397
4537
  class Namebook extends BaseEntity {
4398
4538
  constructor({ id, displayName, } = {}) {
4399
4539
  super();
@@ -4731,5 +4871,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
4731
4871
  * Generated bundle index. Do not edit.
4732
4872
  */
4733
4873
 
4734
- export { Action, AllClickEvent, ApiSecurityService, AppSidebarComponent, AuthGuard, AuthResult, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormCopy, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, ExternalProvider, Filter, FilterRule, FilterSortMeta, FooterComponent, GoogleButtonComponent, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginComponent, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, MimeTypes, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, TranslateLabelsAbstractService, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, capitalizeFirstChar, createFakeGoogleWrapper, deleteAction, exportListToExcel, firstCharToUpper, getFileNameFromContentDisposition, getHtmlImgDisplayString64, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, httpLoadingInterceptor, isExcelFileType, isImageFileType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, primitiveArrayTypes, pushAction, selectedTab, singleOrDefault, splitPascalCase, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
4874
+ export { Action, AllClickEvent, ApiSecurityService, AppSidebarComponent, AuthGuard, AuthResult, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormCopy, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, ExternalProvider, Filter, FilterRule, FilterSortMeta, FooterComponent, GoogleButtonComponent, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginComponent, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, capitalizeFirstChar, createFakeGoogleWrapper, deleteAction, exportListToExcel, firstCharToUpper, getFileNameFromContentDisposition, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, primitiveArrayTypes, pushAction, selectedTab, singleOrDefault, splitPascalCase, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
4735
4875
  //# sourceMappingURL=spiderly.mjs.map