chrv-components 1.12.215 → 1.12.216

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, input, Component, computed, InjectionToken, model, ElementRef, Renderer2, Directive, Injectable, output, signal, viewChild, viewChildren, effect, ApplicationRef, EnvironmentInjector, Injector, afterNextRender, HostListener, LOCALE_ID, ViewContainerRef, ViewChild, ChangeDetectionStrategy, EventEmitter, Output, ContentChildren, ChangeDetectorRef, PLATFORM_ID, DestroyRef, TemplateRef, contentChild, contentChildren, Input, Inject, createComponent, createEnvironmentInjector, ContentChild, ViewEncapsulation } from '@angular/core';
2
+ import { inject, input, Component, computed, InjectionToken, model, ElementRef, Renderer2, Directive, Injectable, output, signal, viewChild, viewChildren, effect, ApplicationRef, EnvironmentInjector, Injector, afterNextRender, HostListener, LOCALE_ID, ViewContainerRef, ViewChild, EventEmitter, Output, ContentChildren, ChangeDetectorRef, PLATFORM_ID, DestroyRef, TemplateRef, contentChild, contentChildren, ChangeDetectionStrategy, Input, Inject, createComponent, createEnvironmentInjector, ContentChild, ViewEncapsulation } from '@angular/core';
3
3
  import * as i2$4 from '@angular/router';
4
4
  import { Router, RouterModule } from '@angular/router';
5
5
  import { MatButton, MatButtonModule } from '@angular/material/button';
@@ -772,39 +772,26 @@ function maxFileSize(maxSize) {
772
772
  return (control) => {
773
773
  const value = control.value;
774
774
  const sizeInBytes = maxSize * 1024 * 1024;
775
- let isSizeCompliant = true;
776
- const errors = {};
777
- let fileSize = null;
778
- if (value === null)
775
+ const files = Array.isArray(value) ? value : [value];
776
+ if (!value)
779
777
  return null;
780
- if (Array.isArray(value)) {
781
- for (let i = 0; i < value.length; i++) {
782
- if (!value[i].file)
783
- continue;
784
- if (value[i].file.size > sizeInBytes) {
785
- errors[i] = true;
786
- isSizeCompliant = false;
787
- fileSize = value[i].file.size;
788
- }
789
- }
790
- }
791
- else {
792
- if (!value?.file)
793
- return null;
794
- fileSize =
795
- (value instanceof File ? value.size : value.file?.size) ?? null;
796
- isSizeCompliant = fileSize !== null && fileSize < sizeInBytes;
778
+ const errors = {};
779
+ for (let i = 0; i < files.length; i++) {
780
+ const fileSize = files[i].file?.size;
781
+ if (fileSize === undefined)
782
+ continue;
783
+ if (fileSize > sizeInBytes)
784
+ errors[i] = +(fileSize / 1024 / 1024).toFixed(2);
797
785
  }
798
- return !isSizeCompliant
799
- ? {
800
- maxfilesize: {
801
- maxfilesize: maxSize + 'mb',
802
- actual: ((fileSize || 0) / 1024 / 1024).toFixed(2) + 'mb',
803
- indexes: errors,
804
- },
786
+ if (Object.keys(errors).length <= 0)
787
+ return null;
788
+ return {
789
+ maxFileSize: {
790
+ maxFileSize: maxSize,
805
791
  indexes: errors,
806
- }
807
- : null;
792
+ },
793
+ indexes: errors,
794
+ };
808
795
  };
809
796
  }
810
797
 
@@ -848,39 +835,57 @@ function minFileSize(minSize) {
848
835
  return (control) => {
849
836
  const value = control.value;
850
837
  const sizeInBytes = minSize * 1024 * 1024;
851
- let isSizeCompliant = true;
852
- const errors = {};
853
- let fileSize = null;
854
- //console.log(value);
855
- if (value === null)
838
+ const files = Array.isArray(value) ? value : [value];
839
+ if (!value)
856
840
  return null;
857
- if (Array.isArray(value)) {
858
- for (let i = 0; i < value.length; i++) {
859
- if (!value[i].file)
860
- continue;
861
- if (value[i].file.size <= sizeInBytes) {
862
- errors[i] = true;
863
- isSizeCompliant = false;
864
- }
865
- }
866
- }
867
- else {
868
- if (!value?.file)
869
- return null;
870
- fileSize =
871
- (value instanceof File ? value.size : value.file?.size) ?? null;
872
- isSizeCompliant = fileSize !== null && fileSize >= sizeInBytes;
841
+ const errors = {};
842
+ for (let i = 0; i < files.length; i++) {
843
+ const fileSize = files[i].file?.size;
844
+ if (fileSize === undefined)
845
+ continue;
846
+ if (fileSize < sizeInBytes)
847
+ errors[i] = +(fileSize / 1024 / 1024).toFixed(2);
873
848
  }
874
- return !isSizeCompliant
875
- ? {
876
- minfilesize: {
877
- minfilesize: minSize + 'mb',
878
- actual: ((fileSize || 0) / 1024 / 1024).toFixed(2) + 'mb',
879
- indexes: errors,
880
- },
849
+ if (Object.keys(errors).length <= 0)
850
+ return null;
851
+ return {
852
+ minFileSize: {
853
+ minFileSize: minSize,
881
854
  indexes: errors,
882
- }
883
- : null;
855
+ },
856
+ indexes: errors,
857
+ };
858
+ // const value: IFile | IFile[] = control.value;
859
+ // const sizeInBytes = minSize * 1024 * 1024;
860
+ // let isSizeCompliant = true;
861
+ // const errors: { [index: number]: boolean } = {};
862
+ // let fileSize: number | null = null;
863
+ // //console.log(value);
864
+ // if (value === null) return null;
865
+ // if (Array.isArray(value)) {
866
+ // for (let i = 0; i < value.length; i++) {
867
+ // if (!value[i].file) continue;
868
+ // if (value[i].file!.size <= sizeInBytes) {
869
+ // errors[i] = true;
870
+ // isSizeCompliant = false;
871
+ // }
872
+ // }
873
+ // } else {
874
+ // if (!value?.file) return null;
875
+ // fileSize =
876
+ // (value instanceof File ? value.size : value.file?.size) ?? null;
877
+ // isSizeCompliant = fileSize !== null && fileSize >= sizeInBytes;
878
+ // }
879
+ // return !isSizeCompliant
880
+ // ? ({
881
+ // minfilesize: {
882
+ // minfilesize: minSize + 'mb',
883
+ // actual: ((fileSize || 0) / 1024 / 1024).toFixed(2) + 'mb',
884
+ // indexes: errors,
885
+ // },
886
+ // indexes: errors,
887
+ // } as ValidationErrors)
888
+ // : null;
884
889
  };
885
890
  }
886
891
 
@@ -994,15 +999,14 @@ function minLength(treshold) {
994
999
 
995
1000
  class MaxFileNameLengthValidator {
996
1001
  constructor() {
997
- this.max = input(0, /* @ts-ignore */
998
- ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1002
+ this.max = input(0, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), alias: 'appMaxFileNameLength' });
999
1003
  }
1000
1004
  validate(control) {
1001
1005
  const max = this.max();
1002
1006
  return max ? maxFileNameLength(max)(control) : null;
1003
1007
  }
1004
1008
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MaxFileNameLengthValidator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1005
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: MaxFileNameLengthValidator, isStandalone: true, selector: "[app-max-file-size]", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1009
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: MaxFileNameLengthValidator, isStandalone: true, selector: "[appMaxFileNameLength]", inputs: { max: { classPropertyName: "max", publicName: "appMaxFileNameLength", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1006
1010
  {
1007
1011
  provide: NG_VALIDATORS,
1008
1012
  useExisting: MaxFileNameLengthValidator,
@@ -1017,7 +1021,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1017
1021
  }, {
1018
1022
  type: Directive,
1019
1023
  args: [{
1020
- selector: '[app-max-file-size]',
1024
+ selector: '[appMaxFileNameLength]',
1021
1025
  providers: [
1022
1026
  {
1023
1027
  provide: NG_VALIDATORS,
@@ -1027,41 +1031,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1027
1031
  ],
1028
1032
  standalone: true,
1029
1033
  }]
1030
- }], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }] } });
1034
+ }], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "appMaxFileNameLength", required: false }] }] } });
1031
1035
  function maxFileNameLength(maxLength) {
1032
1036
  return (control) => {
1033
1037
  const value = control.value;
1034
- const errors = {};
1035
- let isSizeCompliant = true;
1036
- let fileSize = null;
1037
- if (value === null)
1038
+ const files = Array.isArray(value) ? value : [value];
1039
+ if (!value)
1038
1040
  return null;
1039
- if (Array.isArray(value)) {
1040
- for (let i = 0; i < value.length; i++) {
1041
- const fileNameLength = value[i].name?.length;
1042
- if (fileNameLength === undefined)
1043
- continue;
1044
- if (fileNameLength >= maxLength) {
1045
- errors[i] = true;
1046
- isSizeCompliant = false;
1047
- fileSize = value[i].name.length;
1048
- }
1049
- }
1050
- }
1051
- else {
1052
- isSizeCompliant =
1053
- value?.name?.length === undefined || value.name.length <= maxLength;
1041
+ const errors = {};
1042
+ for (let i = 0; i < files.length; i++) {
1043
+ const fileNameLength = files[i].name?.length;
1044
+ if (fileNameLength === undefined)
1045
+ continue;
1046
+ if (fileNameLength > maxLength)
1047
+ errors[i] = fileNameLength;
1054
1048
  }
1055
- return isSizeCompliant == false
1056
- ? {
1057
- maxFileNameLength: {
1058
- maxFileNameLength: maxLength,
1059
- actual: fileSize,
1060
- indexes: errors,
1061
- },
1049
+ if (Object.keys(errors).length <= 0)
1050
+ return null;
1051
+ return {
1052
+ maxFileNameLength: {
1053
+ maxFileNameLength: maxLength,
1062
1054
  indexes: errors,
1063
- }
1064
- : null;
1055
+ },
1056
+ indexes: errors,
1057
+ };
1065
1058
  };
1066
1059
  }
1067
1060
 
@@ -2875,11 +2868,11 @@ class ToastContainer {
2875
2868
  return ref;
2876
2869
  }
2877
2870
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ToastContainer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2878
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: ToastContainer, isStandalone: true, selector: "chr-toast-container", viewQueries: [{ propertyName: "contentTemplateRef", first: true, predicate: ["contentTemplate"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: "<div class=\"toast-container\" [style.flexDirection]=\"direction\">\r\n <!-- @for (toast of toasts(); track $index) {\r\n <app-toast [title]=\"toast.title || null\" [message]=\"toast.message\" [type]=\"toast.type\"\r\n [duration]=\"toast.duration\"></app-toast>\r\n } -->\r\n <ng-container #contentTemplate></ng-container>\r\n</div>", styles: [".toast-container{position:relative;display:flex;gap:1rem}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
2871
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: ToastContainer, isStandalone: true, selector: "chr-toast-container", viewQueries: [{ propertyName: "contentTemplateRef", first: true, predicate: ["contentTemplate"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: "<div class=\"toast-container\" [style.flexDirection]=\"direction\">\r\n <!-- @for (toast of toasts(); track $index) {\r\n <app-toast [title]=\"toast.title || null\" [message]=\"toast.message\" [type]=\"toast.type\"\r\n [duration]=\"toast.duration\"></app-toast>\r\n } -->\r\n <ng-container #contentTemplate></ng-container>\r\n</div>", styles: [".toast-container{position:relative;display:flex;gap:1rem}\n"] }); }
2879
2872
  }
2880
2873
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ToastContainer, decorators: [{
2881
2874
  type: Component,
2882
- args: [{ selector: 'chr-toast-container', imports: [], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"toast-container\" [style.flexDirection]=\"direction\">\r\n <!-- @for (toast of toasts(); track $index) {\r\n <app-toast [title]=\"toast.title || null\" [message]=\"toast.message\" [type]=\"toast.type\"\r\n [duration]=\"toast.duration\"></app-toast>\r\n } -->\r\n <ng-container #contentTemplate></ng-container>\r\n</div>", styles: [".toast-container{position:relative;display:flex;gap:1rem}\n"] }]
2875
+ args: [{ selector: 'chr-toast-container', imports: [], template: "<div class=\"toast-container\" [style.flexDirection]=\"direction\">\r\n <!-- @for (toast of toasts(); track $index) {\r\n <app-toast [title]=\"toast.title || null\" [message]=\"toast.message\" [type]=\"toast.type\"\r\n [duration]=\"toast.duration\"></app-toast>\r\n } -->\r\n <ng-container #contentTemplate></ng-container>\r\n</div>", styles: [".toast-container{position:relative;display:flex;gap:1rem}\n"] }]
2883
2876
  }], ctorParameters: () => [], propDecorators: { contentTemplateRef: [{
2884
2877
  type: ViewChild,
2885
2878
  args: ['contentTemplate', { read: ViewContainerRef, static: true }]
@@ -4643,99 +4636,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4643
4636
  }] });
4644
4637
 
4645
4638
  /**
4646
- * Custom File Input. Support multiple files, base64 encoding and per-file validation.
4647
- * Disclaimer: this is an AI created input.
4639
+ * Custom file input.
4640
+ * Supports multiple files, drag & drop, base64 encoding and per-file validation.
4648
4641
  */
4649
4642
  class ChrFileInputComponent extends ChrInputComponent {
4650
4643
  constructor() {
4651
4644
  super();
4652
4645
  this.fileService = inject(FileService);
4653
4646
  this.nextFileId = 0;
4654
- /**
4655
- * Types de fichiers autorisés (ex: "image/*,.pdf")
4656
- */
4647
+ /** Types de fichiers autorisés (ex: "image/*,.pdf"). */
4657
4648
  this.accept = input('*', /* @ts-ignore */
4658
4649
  ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
4659
- /**
4660
- * Autoriser la sélection de plusieurs fichiers
4661
- */
4650
+ /** Autoriser la sélection de plusieurs fichiers. */
4662
4651
  this.multiple = input(false, /* @ts-ignore */
4663
4652
  ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
4664
- /**
4665
- * Convert selected files to data URLs before updating the form value
4666
- */
4653
+ /** Convertir les fichiers sélectionnés en base64 avant de mettre à jour la valeur du formulaire. */
4667
4654
  this.base64 = input(false, /* @ts-ignore */
4668
4655
  ...(ngDevMode ? [{ debugName: "base64" }] : /* istanbul ignore next */ []));
4669
4656
  this.type = input('file', /* @ts-ignore */
4670
4657
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
4671
- /**
4672
- * Wether or not the user can update the file's name directly from the file item
4673
- */
4658
+ /** Autoriser la modification du nom du fichier depuis la liste. */
4674
4659
  this.allowChangingFileName = input(false, /* @ts-ignore */
4675
4660
  ...(ngDevMode ? [{ debugName: "allowChangingFileName" }] : /* istanbul ignore next */ []));
4676
- /**
4677
- * Référence de l'input HTML type="file" masqué
4678
- */
4679
- // @ViewChild('fileNativeInput');
4661
+ /** Référence de l'input HTML type="file" masqué. */
4680
4662
  this.fileNativeInput = viewChild('fileNativeInput', { ...(ngDevMode ? { debugName: "fileNativeInput" } : /* istanbul ignore next */ {}), read: (ElementRef) });
4681
4663
  /**
4682
- * Template Ref to the <ng-template #data let-file></ng-template> allowed to be passed for the parent.
4683
- * This template will be set next to the regular file data and allow the parent to customize display.
4664
+ * Template Ref <ng-template #data let-file></ng-template>
4665
+ * permettant au parent de personnaliser l'affichage des données du fichier.
4684
4666
  */
4685
4667
  this.dataTemplateRef = contentChild('data', { ...(ngDevMode ? { debugName: "dataTemplateRef" } : /* istanbul ignore next */ {}), read: TemplateRef });
4686
4668
  this.dataTemplateControls = contentChildren(NgControl, { ...(ngDevMode ? { debugName: "dataTemplateControls" } : /* istanbul ignore next */ {}), descendants: true });
4687
- /**
4688
- * État du glisser-déposer (Drag & Drop)
4689
- */
4669
+ /** État du drag & drop. */
4690
4670
  this.isDragging = signal(false, /* @ts-ignore */
4691
4671
  ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
4692
- this.fileEntries = signal([], /* @ts-ignore */
4693
- ...(ngDevMode ? [{ debugName: "fileEntries" }] : /* istanbul ignore next */ []));
4694
- /**
4695
- * Signal calculé pour garantir la manipulation d'un tableau de fichiers
4696
- */
4697
- this.files = computed(() => this.fileEntries(), /* @ts-ignore */
4672
+ /** Liste des fichiers exposée au template. */
4673
+ this.files = signal([], /* @ts-ignore */
4698
4674
  ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
4675
+ this.fileValidationErrors = signal([], /* @ts-ignore */
4676
+ ...(ngDevMode ? [{ debugName: "fileValidationErrors" }] : /* istanbul ignore next */ []));
4699
4677
  this.dataTemplateControls$ = toObservable(this.dataTemplateControls);
4678
+ toObservable(this.files)
4679
+ .pipe(startWith([]), distinctUntilChanged())
4680
+ .subscribe((f) => {
4681
+ this.onChange(f.length > 0 ? (this.multiple() ? f.map((f) => f.file) : f[0]) : null);
4682
+ });
4700
4683
  }
4701
4684
  ngOnInit() {
4702
4685
  super.ngOnInit();
4686
+ this.ngControl()
4687
+ ?.statusChanges?.pipe()
4688
+ .subscribe((s) => {
4689
+ this.fileValidationErrors.set(this.files().map((_, index) => this.getFileValidationError(index)));
4690
+ });
4703
4691
  }
4704
4692
  ngAfterContentInit() {
4705
4693
  this.dataTemplateControls$
4706
- .pipe(distinctUntilChanged(), filter((list) => list.length > 0), switchMap((controlsList) => {
4707
- if (!controlsList.length)
4708
- return of(null);
4709
- const valueChanges$ = controlsList
4710
- .filter((c) => !!c.valueChanges)
4711
- .map((c) => c.valueChanges);
4712
- return merge(...valueChanges$);
4694
+ .pipe(distinctUntilChanged(), filter((controls) => controls.length > 0), switchMap((controls) => {
4695
+ const valueChanges = controls
4696
+ .filter((control) => !!control.valueChanges)
4697
+ .map((control) => control.valueChanges);
4698
+ return valueChanges.length > 0 ? merge(...valueChanges) : of(null);
4713
4699
  }))
4714
4700
  .subscribe(() => {
4715
4701
  this.onChange(this.value());
4716
4702
  });
4717
4703
  }
4718
- /**
4719
- * Ouvre la fenêtre native de sélection de fichiers
4720
- */
4704
+ /** Ouvre la fenêtre native de sélection de fichiers. */
4721
4705
  triggerFileSelect() {
4722
4706
  if (!this.isDisabled()) {
4723
4707
  this.fileNativeInput()?.nativeElement.click();
4724
4708
  }
4725
4709
  }
4726
- /**
4727
- * Gestion du changement sur l'input natif
4728
- */
4710
+ /** Gestion du changement sur l'input natif. */
4729
4711
  onFileSelected(event) {
4730
- const inputEl = event.target;
4731
- if (inputEl.files && inputEl.files.length > 0) {
4732
- this.addFiles(Array.from(inputEl.files));
4733
- inputEl.value = ''; // Réinitialiser pour permettre la ré-sélection du même fichier
4712
+ const inputElement = event.target;
4713
+ if (inputElement.files?.length) {
4714
+ this.addFiles(Array.from(inputElement.files));
4715
+ inputElement.value = '';
4734
4716
  }
4735
4717
  }
4736
- /**
4737
- * Gestion du Drag & Drop
4738
- */
4718
+ /** Gestion du drag & drop. */
4739
4719
  onDragOver(event) {
4740
4720
  event.preventDefault();
4741
4721
  event.stopPropagation();
@@ -4752,103 +4732,75 @@ class ChrFileInputComponent extends ChrInputComponent {
4752
4732
  event.preventDefault();
4753
4733
  event.stopPropagation();
4754
4734
  this.isDragging.set(false);
4755
- if (this.isDisabled())
4735
+ if (this.isDisabled()) {
4756
4736
  return;
4737
+ }
4757
4738
  const droppedFiles = event.dataTransfer?.files;
4758
- if (droppedFiles && droppedFiles.length > 0) {
4739
+ if (droppedFiles?.length) {
4759
4740
  this.addFiles(Array.from(droppedFiles));
4760
4741
  }
4761
4742
  this.blur.emit();
4762
4743
  }
4763
- /**
4764
- * Ajoute les fichiers et met à jour le Form Value
4765
- */
4744
+ /** Ajoute les fichiers et met à jour la valeur du formulaire. */
4766
4745
  addFiles(newFiles) {
4767
- const currentEntries = this.files();
4768
- const filteredNewFiles = this.multiple()
4769
- ? newFiles.filter((newFile) => !currentEntries.some((entry) => entry.file.file?.name === newFile.name &&
4770
- entry.file.file?.size === newFile.size))
4771
- : newFiles.slice(0, 1);
4772
- const newEntries = filteredNewFiles.map((file) => ({
4773
- id: this.nextFileId++,
4774
- file: new ChrFile({ file, url: URL.createObjectURL(file) }),
4775
- progress: this.isAcceptedFile(file) && this.base64() ? 0 : 100,
4776
- error: this.isAcceptedFile(file)
4777
- ? undefined
4778
- : 'Type de fichier non autorisé',
4779
- }));
4780
- const updatedEntries = this.multiple()
4781
- ? [...currentEntries, ...newEntries]
4782
- : newEntries;
4783
- this.fileEntries.set(updatedEntries);
4784
- const filesToConvert = newEntries.filter((entry) => !entry.error);
4785
- if (!this.base64() || filesToConvert.length === 0) {
4786
- this.updateValue(updatedEntries);
4746
+ const newEntries = newFiles.map((file) => {
4747
+ const accepted = this.isAcceptedFile(file);
4748
+ return {
4749
+ id: this.nextFileId++,
4750
+ file: new ChrFile({
4751
+ file,
4752
+ url: URL.createObjectURL(file),
4753
+ }),
4754
+ progress: accepted && this.base64() ? 0 : 100,
4755
+ error: accepted ? undefined : 'Type de fichier non autorisé',
4756
+ };
4757
+ });
4758
+ const entriesToConvert = newEntries.filter((entry) => !entry.error);
4759
+ if (!this.base64() || entriesToConvert.length === 0) {
4760
+ this.files.set([...this.files(), ...newEntries]);
4787
4761
  return;
4788
4762
  }
4789
- let completedConversions = 0;
4790
- let conversionFailed = false;
4791
- for (const entry of filesToConvert) {
4763
+ let completed = 0;
4764
+ let hasError = false;
4765
+ for (const entry of entriesToConvert) {
4792
4766
  this.fileService.toBase64WithProgress(entry.file).subscribe({
4793
4767
  next: (result) => {
4794
- const updatedEntries = this.files().map((currentEntry) => currentEntry.id === entry.id
4768
+ this.files.update((entries) => entries.map((current) => current.id === entry.id
4795
4769
  ? {
4796
- ...currentEntry,
4797
- file: result.file ?? currentEntry.file,
4770
+ ...current,
4771
+ file: result.file ?? current.file,
4798
4772
  progress: result.progress,
4799
4773
  }
4800
- : currentEntry);
4801
- this.fileEntries.set(updatedEntries);
4774
+ : current));
4802
4775
  },
4803
4776
  error: () => {
4804
- conversionFailed = true;
4805
- this.fileEntries.update((entries) => entries.map((currentEntry) => currentEntry.id === entry.id
4777
+ hasError = true;
4778
+ this.files.update((entries) => entries.map((current) => current.id === entry.id
4806
4779
  ? {
4807
- ...currentEntry,
4780
+ ...current,
4808
4781
  progress: 0,
4809
4782
  error: 'La conversion du fichier a échoué',
4810
4783
  }
4811
- : currentEntry));
4812
- completedConversions++;
4813
- if (completedConversions === filesToConvert.length &&
4814
- !conversionFailed) {
4815
- this.updateValue(this.files());
4816
- }
4784
+ : current));
4817
4785
  },
4818
4786
  complete: () => {
4819
- completedConversions++;
4820
- if (completedConversions === filesToConvert.length &&
4821
- !conversionFailed) {
4822
- this.updateValue(this.files());
4787
+ completed++;
4788
+ if (completed === entriesToConvert.length && !hasError) {
4789
+ this.files.set([...this.files()]);
4823
4790
  }
4824
4791
  },
4825
4792
  });
4826
4793
  }
4827
4794
  }
4828
- /**
4829
- * Suppression d'un fichier sélectionné
4830
- */
4795
+ /** Suppression d'un fichier sélectionné. */
4831
4796
  removeFile(index, event) {
4832
4797
  event?.stopPropagation();
4833
4798
  if (this.multiple()) {
4834
- const updatedList = [...this.files()];
4835
- updatedList.splice(index, 1);
4836
- this.fileEntries.set(updatedList);
4837
- this.updateValue(updatedList);
4838
- }
4839
- else {
4840
- this.fileEntries.set([]);
4841
- // this.value.set(null);
4842
- this.onChange(null);
4799
+ const updatedEntries = this.files().filter((_, i) => i !== index);
4800
+ this.files.set(updatedEntries);
4801
+ return;
4843
4802
  }
4844
- }
4845
- updateValue(entries) {
4846
- const value = entries
4847
- .filter((entry) => !entry.error)
4848
- .map((entry) => entry.file);
4849
- const nextValue = this.multiple() ? value : (value[0] ?? null);
4850
- // this.value.set(nextValue);
4851
- this.onChange(nextValue);
4803
+ this.files.set([]);
4852
4804
  }
4853
4805
  isAcceptedFile(file) {
4854
4806
  const acceptedTypes = this.accept()
@@ -4865,59 +4817,60 @@ class ChrFileInputComponent extends ChrInputComponent {
4865
4817
  return fileName.endsWith(acceptedType);
4866
4818
  }
4867
4819
  if (acceptedType.endsWith('/*')) {
4868
- return fileType.startsWith(`${acceptedType.slice(0, -1)}`);
4820
+ return fileType.startsWith(acceptedType.slice(0, -1));
4869
4821
  }
4870
4822
  return fileType === acceptedType;
4871
4823
  });
4872
4824
  }
4825
+ /**
4826
+ * Retourne l'erreur de validation correspondant au fichier à l'index donné.
4827
+ *
4828
+ * Pour un tableau, le validator retourne :
4829
+ * {
4830
+ * indexes: {
4831
+ * [index]: true
4832
+ * }
4833
+ * }
4834
+ *
4835
+ * On ne cherche donc jamais à faire correspondre l'erreur avec `FileEntry.id`.
4836
+ */
4873
4837
  getFileValidationError(index) {
4874
4838
  const errors = this.ngControl()?.control?.errors;
4875
- const errorDisplays = BaseErrorDisplays;
4876
- if (!errors)
4877
- return null;
4878
- // console.log('Remaining errors:', errors);
4879
- const indexedErrors = errors['indexes'];
4880
- const isSingleFile = !this.multiple() && index === 0;
4881
- if (!isSingleFile && !indexedErrors?.[index])
4839
+ if (!errors) {
4882
4840
  return null;
4883
- const firstError = Object.keys(errors)?.[0];
4884
- const errorDisplay = errorDisplays[firstError.toLowerCase()];
4885
- const error = errors[firstError];
4886
- const lowerLimit = error[firstError];
4887
- const actual = error.actual;
4888
- if (firstError !== undefined) {
4889
- if (typeof lowerLimit == 'boolean' && typeof actual == 'boolean')
4890
- return errorDisplay;
4891
- else
4892
- return errorDisplay
4893
- ? this.trimmedErrorDisplay(error, lowerLimit, actual)
4894
- : errors[0];
4895
4841
  }
4896
- return null;
4842
+ const errorKey = Object.keys(errors).find((key) => key !== 'indexes');
4843
+ if (!errorKey)
4844
+ return null;
4845
+ const indexedErrors = errors[errorKey]['indexes'];
4846
+ if (!indexedErrors)
4847
+ return null;
4848
+ const limit = errors[errorKey][errorKey];
4849
+ const actual = indexedErrors[index];
4850
+ const display = BaseErrorDisplays[errorKey.toLowerCase()];
4851
+ return this.trimmedErrorDisplay(display, limit, actual) ?? null;
4897
4852
  }
4898
- /**
4899
- * Utilitaire pour afficher la taille en Ko/Mo
4900
- */
4853
+ /** Formatage d'une taille en octets. */
4901
4854
  formatFileSize(bytes) {
4902
- if (bytes === 0)
4855
+ if (bytes === 0) {
4903
4856
  return '0 B';
4857
+ }
4904
4858
  const k = 1024;
4905
4859
  const sizes = ['B', 'KB', 'MB', 'GB'];
4906
4860
  const i = Math.floor(Math.log(bytes) / Math.log(k));
4907
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
4908
- }
4909
- getRandom() {
4910
- return crypto.randomUUID();
4861
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
4911
4862
  }
4912
4863
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChrFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4913
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ChrFileInputComponent, isStandalone: true, selector: "chr-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, base64: { classPropertyName: "base64", publicName: "base64", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, allowChangingFileName: { classPropertyName: "allowChangingFileName", publicName: "allowChangingFileName", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "dataTemplateRef", first: true, predicate: ["data"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "dataTemplateControls", predicate: NgControl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "fileNativeInput", first: true, predicate: ["fileNativeInput"], descendants: true, read: ElementRef, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"input-wrapper\" [attr.data-state]=\"state()\">\n <!-- Zone de Drop & S\u00E9lection (remplace visuellement l'input texte standard) -->\n <div class=\"drop-zone\" [hidden]=\"files().length>0\" [class.dragging]=\"isDragging()\" [class.disabled]=\"isDisabled()\"\n (click)=\"triggerFileSelect();blur.emit();\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <input #fileNativeInput #input type=\"file\" class=\"native-file-input\" [accept]=\"accept()\" [multiple]=\"multiple()\"\n (change)=\"onFileSelected($event)\" />\n\n <div class=\"drop-zone-content\">\n <span class=\"material-symbols upload-icon\">cloud_upload</span>\n <div class=\"drop-zone-text\">\n <span class=\"primary-text\">\n @if (label()) { {{ label() }} } @else { Cliquez ou glissez un fichier ici }\n </span>\n <span class=\"secondary-text\">\n {{ multiple() ? 'S\u00E9lectionnez un ou plusieurs fichiers' : 'S\u00E9lectionnez un fichier' }}\n </span>\n @if(isTouched()){\n @let error = getLastError();\n @if(error != null){\n <span class=\"error-text error\">\n {{getLastError() ?? null}}\n </span>\n }\n }\n </div>\n </div>\n </div>\n\n <!-- Liste des fichiers s\u00E9lectionn\u00E9s sous forme de cartes/puces -->\n @if (files().length > 0) {\n <div class=\"file-list\">\n @for (entry of files(); track entry.id) {\n <div class=\"file-item\" [class.error]=\"entry.error || getFileValidationError($index)\">\n <span class=\"material-symbols file-icon\">{{ entry.error || getFileValidationError($index) ? 'error' :\n 'description' }}</span>\n <div class=\"file-info\">\n <div class=\"file-data\" [class.full-width]=\"dataTemplateRef() == undefined\">\n <div class=\"file-name\">\n @if(allowChangingFileName()){\n <chr-input [value]=\"entry.file.name\"\n (valueChange)=\"entry.file.name=$event?.trim()??entry.file.file!.name.split('.')[0];onChange(this.value())\"\n type=\"text\"></chr-input>\n }@else {\n <span class=\"file-name\">{{\n entry.file.name }}\n </span>\n }\n <span>.{{entry.file.extension}}</span>\n </div>\n <div class=\"file-name\">\n <span class=\"file-size\">{{ formatFileSize(entry.file.file?.size ?? 0) }}</span>\n <span class=\"file-content\">|</span>\n <span class=\"file-content\">{{ entry.file.contentType }}</span>\n </div>\n </div>\n @if(dataTemplateRef()){\n <div class=\"file-data\">\n <ng-container *ngTemplateOutlet=\"dataTemplateRef(); context: { $implicit: entry.file}\">\n </ng-container>\n </div>\n }\n <div class=\"file-data full-width\">\n @if (entry.error) {\n <span class=\"file-error\" role=\"alert\">{{ entry.error }}</span>\n } @else if (getFileValidationError($index); as validationError) {\n <span class=\"file-error\" role=\"alert\">{{ validationError }}</span>\n }\n </div>\n </div>\n <button type=\"button\" class=\"file-remove-btn\" (click)=\"removeFile($index, $event)\"\n [attr.aria-label]=\"'Supprimer ' + entry.file.file?.name\">\n <span class=\"material-symbols\">close</span>\n </button>\n <div class=\"file-progress\" [class.error]=\"entry.error || getFileValidationError($index)\" role=\"progressbar\"\n [attr.aria-label]=\"entry.error || getFileValidationError($index) || 'Progression de la conversion'\"\n [attr.aria-valuenow]=\"entry.progress\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"file-progress-value\" [style.width.%]=\"entry.progress\"></div>\n </div>\n </div>\n }\n @if(multiple()){\n <button class=\"file-item fixed-size file-add-btn\" (click)=\"triggerFileSelect()\">\n <span class=\"material-symbols\">add</span>\n </button>\n }\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.input-wrapper{--chr-input-current-color: var(--text-neutral-color);position:relative;box-sizing:border-box;min-height:1.5rem;max-height:1.5rem;height:1.5rem;display:flex;flex:1 1 auto;min-width:0;flex-direction:column;border-bottom:.125rem solid var(--chr-input-current-color)}.input-wrapper .input-row{display:flex;flex:1 1 auto;flex-wrap:nowrap;height:inherit}.input-wrapper .input{min-width:0;height:inherit;color:var(--text-color)}.input-wrapper .indicators{display:flex;flex-direction:row;flex:1 1 auto;flex-wrap:nowrap;gap:.25rem;max-width:fit-content;height:inherit;align-items:center;color:var(--chr-input-current-color);font-size:.75rem!important}.input-wrapper .input~.indicators{position:absolute;top:0;right:0;visibility:hidden;opacity:0;transform:translate(1rem);transition:opacity .15s ease-in-out,transform .15s ease-in-out,width .15s ease-in-out .15s}.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]{position:relative;visibility:visible;padding-left:.125rem;opacity:1;transform:translate(0)}:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(chr-input-indicators:not(:empty)),:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(:nth-child(2)){border-left:.125rem solid color-mix(in srgb,var(--chr-input-current-color) 40%,transparent 60%)}.input-wrapper:has(.indicators[data-indicators-open=true]) .input{padding-right:.125rem}.input-wrapper .input:has(~.indicators:empty){padding-right:0}.input-wrapper .input~.indicators:empty{width:0;border-left:none;padding-left:0}.input-wrapper .tooltip-indicator{cursor:pointer;font-size:1.25rem;padding:0;color:var(--chr-input-current-color)}.input-wrapper .tooltip-indicator:hover{color:var(--primary-color)}.input-wrapper .required{color:var(--error-color)}.input-wrapper:has(.input:focus){--chr-input-current-color: var(--primary-color)}.input-wrapper[data-state=IDLE]{--chr-input-current-color: color-mix(in srgb, var(--text-neutral-color) 90%, transparent 10%) }.input-wrapper[data-state=INVALID],.input-wrapper[data-state=DISABLED]{--chr-input-current-color: var(--error-color)}.input-wrapper[data-state=PENDING],.input-wrapper[data-state=WARNING]{--chr-input-current-color: var(--warn-color)}.input-wrapper label{position:absolute;pointer-events:none;top:50%;color:var(--chr-input-current-color);transform:translateY(-50%);transition:transfom 125ms ease-in-out,position 125ms ease-in-out,top 125ms ease-in-out,font-size 125ms ease-in-out,left 125ms ease-in-out;font-size:.85rem;font-weight:700}.input-wrapper label:not(:empty):after{position:absolute;content:\":\"}.input-wrapper label:has(~.input:focus),.input-wrapper label:has(~.input:not(:placeholder-shown)){top:-.25rem;left:0;font-size:75%}.input-wrapper .input{width:100%;outline:none;border:none;padding:0;background-color:transparent}.data-overlay{height:0;grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.data{grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.error{grid-area:data / data;display:flex;flex:1 1 auto;flex-direction:row;flex-wrap:wrap;min-width:calc(100% - .5rem);max-width:min-content;color:var(--error-color);padding:.25rem;border-top-left-radius:0;border-top-right-radius:0;background-color:var(--background-color);z-index:2}.error:empty{display:none}.input[disabled]{--chr-input-disabled-neutral-muted-color: color-mix(in srgb, var(--text-neutral-color) 15%, transparent 85%);background:repeating-linear-gradient(45deg,var(--chr-input-disabled-neutral-muted-color),var(--chr-input-disabled-neutral-muted-color) .625rem,var(--background-color) .625rem,var(--background-color) 1.25rem);cursor:not-allowed}.input{accent-color:var(--primary-color)}.input ::-webkit-datetime-edit,.input ::-webkit-datetime-edit-fields-wrapper,.input ::-webkit-datetime-edit-text,.input ::-webkit-datetime-edit-month-field,.input ::-webkit-datetime-edit-day-field,.input ::-webkit-datetime-edit-year-field,.input ::-webkit-inner-spin-button,.input ::-webkit-calendar-picker-indicator{place-self:end;justify-self:end;background-color:red}.input[type=range]{color:red}.input[type=range]:after{content:attr(value)}.input[type=textarea]::-webkit-scrollbar{display:none!important}.no-scrollbar{-ms-overflow-style:none!important;scrollbar-width:none!important}:host{display:flex;flex-direction:column;flex:1 1 auto;justify-self:stretch;align-self:center;min-width:0}.input-wrapper{display:flex;flex-direction:column;gap:.75rem;height:auto;max-height:none;width:100%;border-bottom:none}.native-file-input{display:none}.drop-zone{border:2px dashed var(--chr-input-current-color);border-radius:.5rem;padding:1.25rem 1rem;background-color:var(--background-color);cursor:pointer;transition:all .2s ease-in-out}.drop-zone:hover:not(.disabled){border-color:var(--primary-color);background-color:var(--background-neutral-color)}.drop-zone.dragging{border-color:var(--tertiary-color);background-color:var(--background-neutral-color)}.drop-zone.disabled{opacity:.6;cursor:not-allowed}.drop-zone-content{display:flex;align-items:center;justify-content:center;gap:.75rem}.upload-icon{font-size:2rem;color:color-mix(in srgb,var(--primary-color) 80%,transparent 20%)}.drop-zone-text{display:flex;flex-direction:column;text-align:center}.primary-text{font-size:.875rem;font-weight:500;color:var(--text-color)}.secondary-text{font-size:.75rem;color:var(--text-neutral-color)}.error-text{font-size:.75rem;font-weight:500;color:var(--error-color)}.file-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:.375rem}.file-item{position:relative;display:flex;flex:1 1 auto;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:var(--background-color);border:1px solid var(--neutral-color);border-radius:.375rem;overflow:hidden}.file-item.fixed-size{flex:0 1 auto}.file-item.error{border-color:var(--error-color)}.file-icon{font-size:1.25rem;color:var(--text-neutral-color)}.file-info{display:grid;flex:1 1 auto;grid-template-columns:1fr 1fr}.file-data{grid-column:span 1;border-left:1px solid var(--neutral-color);padding-left:.5rem}.file-data.full-width{grid-column:span 2}.file-data:not(:last-of-type){padding-right:.5rem}.file-name{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;font-size:.8125rem;font-weight:500;white-space:nowrap}.file-size,.file-content{font-size:.6875rem;color:var(--text-neutral-color)}.file-content{margin-left:.25rem}.file-error{color:var(--error-color);font-size:.6875rem}.file-remove-btn{background:transparent;border:none;color:var(--text-neutral-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:.25rem;border-radius:.25rem;transition:color .15s,background-color .15s}.file-remove-btn:hover{color:var(--error-color);background-color:color-mix(in srgb,var(--error-color) 10%,transparent 90%)}.file-remove-btn .material-symbols{font-size:1rem}.file-add-btn{color:var(--text-neutral-color);cursor:pointer}.file-add-btn:hover{color:var(--primary-color);background-color:color-mix(in srgb,var(--primary-color) 10%,transparent 90%)}.file-progress{position:absolute;right:0;bottom:0;left:0;height:.2rem;background-color:color-mix(in srgb,var(--neutral-color) 55%,transparent 45%)}.file-progress-value{height:100%;background-color:var(--primary-color);transition:width .15s ease-out}.file-progress.error{background-color:color-mix(in srgb,var(--error-color) 20%,transparent 80%)}.file-progress.error .file-progress-value{background-color:var(--error-color)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChrInputComponent, selector: "chr-input", inputs: ["label", "placeholder", "id", "name", "value", "displayValue", "type", "outputType", "step", "debounceTime", "isDisabled", "isFocused", "min", "max", "minLength", "maxLength", "ngControl", "tooltip", "isIndicatorsOpen", "tooltipPosition", "required", "state", "errors"], outputs: ["idChange", "nameChange", "valueChange", "displayValueChange", "outputTypeChange", "isDisabledChange", "isFocusedChange", "ngControlChange", "isIndicatorsOpenChange", "requiredChange", "stateChange", "input", "change", "debouncedInput", "focus", "enter", "blur"] }] }); }
4864
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ChrFileInputComponent, isStandalone: true, selector: "chr-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, base64: { classPropertyName: "base64", publicName: "base64", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, allowChangingFileName: { classPropertyName: "allowChangingFileName", publicName: "allowChangingFileName", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "dataTemplateRef", first: true, predicate: ["data"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "dataTemplateControls", predicate: NgControl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "fileNativeInput", first: true, predicate: ["fileNativeInput"], descendants: true, read: ElementRef, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"input-wrapper\" [attr.data-state]=\"state()\">\n <!-- Zone de Drop & S\u00E9lection (remplace visuellement l'input texte standard) -->\n <div class=\"drop-zone\" [hidden]=\"files().length>0\" [class.dragging]=\"isDragging()\" [class.disabled]=\"isDisabled()\"\n (click)=\"triggerFileSelect();blur.emit();\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <input #fileNativeInput #input type=\"file\" class=\"native-file-input\" [accept]=\"accept()\" [multiple]=\"multiple()\"\n (change)=\"onFileSelected($event)\" />\n\n <div class=\"drop-zone-content\">\n <span class=\"material-symbols upload-icon\">cloud_upload</span>\n <div class=\"drop-zone-text\">\n <span class=\"primary-text\">\n @if (label()) { {{ label() }} } @else { Cliquez ou glissez un fichier ici }\n </span>\n <span class=\"secondary-text\">\n {{ multiple() ? 'S\u00E9lectionnez un ou plusieurs fichiers' : 'S\u00E9lectionnez un fichier' }}\n </span>\n @if(isTouched()){\n @let error = getLastError();\n @if(error != null){\n <span class=\"error-text error\">\n {{getLastError() ?? null}}\n </span>\n }\n }\n </div>\n </div>\n </div>\n\n <!-- Liste des fichiers s\u00E9lectionn\u00E9s sous forme de cartes/puces -->\n @if (files().length > 0) {\n <div class=\"file-list\">\n @for (entry of files(); track entry.id) {\n @let error = fileValidationErrors()[$index];\n <div class=\"file-item\" [class.error]=\"entry.error || error\">\n <span class=\"material-symbols file-icon\">{{ entry.error || error ? 'error' :\n 'description' }}</span>\n <div class=\"file-info\">\n <div class=\"file-data\" [class.full-width]=\"dataTemplateRef() == undefined\">\n <div class=\"file-name\">\n @if(allowChangingFileName()){\n <chr-input [value]=\"entry.file.name\"\n (valueChange)=\"entry.file.name=$event?.trim()??entry.file.file!.name.split('.')[0];onChange(this.value())\"\n type=\"text\"></chr-input>\n }@else {\n <span class=\"file-name\">{{\n entry.file.name }}\n </span>\n }\n <span>.{{entry.file.extension}}</span>\n </div>\n <div class=\"file-name\">\n <span class=\"file-size\">{{ formatFileSize(entry.file.file?.size ?? 0) }}</span>\n <span class=\"file-content\">|</span>\n <span class=\"file-content\">{{ entry.file.contentType }}</span>\n </div>\n </div>\n @if(dataTemplateRef()){\n <div class=\"file-data\">\n <ng-container *ngTemplateOutlet=\"dataTemplateRef(); context: { $implicit: entry.file}\">\n </ng-container>\n </div>\n }\n <div class=\"file-data full-width\">\n @if (entry.error) {\n <span class=\"file-error\" role=\"alert\">{{ entry.error }}</span>\n } @else if (error; as validationError) {\n <span class=\"file-error\" role=\"alert\">{{ validationError }}</span>\n }\n </div>\n </div>\n <button type=\"button\" class=\"file-remove-btn\" (click)=\"removeFile($index, $event)\"\n [attr.aria-label]=\"'Supprimer ' + entry.file.file?.name\">\n <span class=\"material-symbols\">close</span>\n </button>\n <div class=\"file-progress\" [class.error]=\"entry.error ||error\" role=\"progressbar\"\n [attr.aria-label]=\"entry.error || error || 'Progression de la conversion'\"\n [attr.aria-valuenow]=\"entry.progress\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"file-progress-value\" [style.width.%]=\"entry.progress\"></div>\n </div>\n </div>\n }\n @if(multiple()){\n <button class=\"file-item fixed-size file-add-btn\" (click)=\"triggerFileSelect()\">\n <span class=\"material-symbols\">add</span>\n </button>\n }\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.input-wrapper{--chr-input-current-color: var(--text-neutral-color);position:relative;box-sizing:border-box;min-height:1.5rem;max-height:1.5rem;height:1.5rem;display:flex;flex:1 1 auto;min-width:0;flex-direction:column;border-bottom:.125rem solid var(--chr-input-current-color)}.input-wrapper .input-row{display:flex;flex:1 1 auto;flex-wrap:nowrap;height:inherit}.input-wrapper .input{min-width:0;height:inherit;color:var(--text-color)}.input-wrapper .indicators{display:flex;flex-direction:row;flex:1 1 auto;flex-wrap:nowrap;gap:.25rem;max-width:fit-content;height:inherit;align-items:center;color:var(--chr-input-current-color);font-size:.75rem!important}.input-wrapper .input~.indicators{position:absolute;top:0;right:0;visibility:hidden;opacity:0;transform:translate(1rem);transition:opacity .15s ease-in-out,transform .15s ease-in-out,width .15s ease-in-out .15s}.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]{position:relative;visibility:visible;padding-left:.125rem;opacity:1;transform:translate(0)}:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(chr-input-indicators:not(:empty)),:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(:nth-child(2)){border-left:.125rem solid color-mix(in srgb,var(--chr-input-current-color) 40%,transparent 60%)}.input-wrapper:has(.indicators[data-indicators-open=true]) .input{padding-right:.125rem}.input-wrapper .input:has(~.indicators:empty){padding-right:0}.input-wrapper .input~.indicators:empty{width:0;border-left:none;padding-left:0}.input-wrapper .tooltip-indicator{cursor:pointer;font-size:1.25rem;padding:0;color:var(--chr-input-current-color)}.input-wrapper .tooltip-indicator:hover{color:var(--primary-color)}.input-wrapper .required{color:var(--error-color)}.input-wrapper:has(.input:focus){--chr-input-current-color: var(--primary-color)}.input-wrapper[data-state=IDLE]{--chr-input-current-color: color-mix(in srgb, var(--text-neutral-color) 90%, transparent 10%) }.input-wrapper[data-state=INVALID],.input-wrapper[data-state=DISABLED]{--chr-input-current-color: var(--error-color)}.input-wrapper[data-state=PENDING],.input-wrapper[data-state=WARNING]{--chr-input-current-color: var(--warn-color)}.input-wrapper label{position:absolute;pointer-events:none;top:50%;color:var(--chr-input-current-color);transform:translateY(-50%);transition:transfom 125ms ease-in-out,position 125ms ease-in-out,top 125ms ease-in-out,font-size 125ms ease-in-out,left 125ms ease-in-out;font-size:.85rem;font-weight:700}.input-wrapper label:not(:empty):after{position:absolute;content:\":\"}.input-wrapper label:has(~.input:focus),.input-wrapper label:has(~.input:not(:placeholder-shown)){top:-.25rem;left:0;font-size:75%}.input-wrapper .input{width:100%;outline:none;border:none;padding:0;background-color:transparent}.data-overlay{height:0;grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.data{grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.error{grid-area:data / data;display:flex;flex:1 1 auto;flex-direction:row;flex-wrap:wrap;min-width:calc(100% - .5rem);max-width:min-content;color:var(--error-color);padding:.25rem;border-top-left-radius:0;border-top-right-radius:0;background-color:var(--background-color);z-index:2}.error:empty{display:none}.input[disabled]{--chr-input-disabled-neutral-muted-color: color-mix(in srgb, var(--text-neutral-color) 15%, transparent 85%);background:repeating-linear-gradient(45deg,var(--chr-input-disabled-neutral-muted-color),var(--chr-input-disabled-neutral-muted-color) .625rem,var(--background-color) .625rem,var(--background-color) 1.25rem);cursor:not-allowed}.input{accent-color:var(--primary-color)}.input ::-webkit-datetime-edit,.input ::-webkit-datetime-edit-fields-wrapper,.input ::-webkit-datetime-edit-text,.input ::-webkit-datetime-edit-month-field,.input ::-webkit-datetime-edit-day-field,.input ::-webkit-datetime-edit-year-field,.input ::-webkit-inner-spin-button,.input ::-webkit-calendar-picker-indicator{place-self:end;justify-self:end;background-color:red}.input[type=range]{color:red}.input[type=range]:after{content:attr(value)}.input[type=textarea]::-webkit-scrollbar{display:none!important}.no-scrollbar{-ms-overflow-style:none!important;scrollbar-width:none!important}:host{display:flex;flex-direction:column;flex:1 1 auto;justify-self:stretch;align-self:center;min-width:0}.input-wrapper{display:flex;flex-direction:column;gap:.75rem;height:auto;max-height:none;width:100%;border-bottom:none}.native-file-input{display:none}.drop-zone{border:2px dashed var(--chr-input-current-color);border-radius:.5rem;padding:1.25rem 1rem;background-color:var(--background-color);cursor:pointer;transition:all .2s ease-in-out}.drop-zone:hover:not(.disabled){border-color:var(--primary-color);background-color:var(--background-neutral-color)}.drop-zone.dragging{border-color:var(--tertiary-color);background-color:var(--background-neutral-color)}.drop-zone.disabled{opacity:.6;cursor:not-allowed}.drop-zone-content{display:flex;align-items:center;justify-content:center;gap:.75rem}.upload-icon{font-size:2rem;color:color-mix(in srgb,var(--primary-color) 80%,transparent 20%)}.drop-zone-text{display:flex;flex-direction:column;text-align:center}.primary-text{font-size:.875rem;font-weight:500;color:var(--text-color)}.secondary-text{font-size:.75rem;color:var(--text-neutral-color)}.error-text{font-size:.75rem;font-weight:500;color:var(--error-color)}.file-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:.375rem}.file-item{position:relative;display:flex;flex:1 1 auto;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:var(--background-color);border:1px solid var(--neutral-color);border-radius:.375rem;overflow:hidden}.file-item.fixed-size{flex:0 1 auto}.file-item.error{border-color:var(--error-color)}.file-icon{font-size:1.25rem;color:var(--text-neutral-color)}.file-info{display:grid;flex:1 1 auto;grid-template-columns:1fr 1fr}.file-data{grid-column:span 1;border-left:1px solid var(--neutral-color);padding-left:.5rem}.file-data.full-width{grid-column:span 2}.file-data:not(:last-of-type){padding-right:.5rem}.file-name{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;font-size:.8125rem;font-weight:500;white-space:nowrap}.file-size,.file-content{font-size:.6875rem;color:var(--text-neutral-color)}.file-content{margin-left:.25rem}.file-error{color:var(--error-color);font-size:.6875rem}.file-remove-btn{background:transparent;border:none;color:var(--text-neutral-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:.25rem;border-radius:.25rem;transition:color .15s,background-color .15s}.file-remove-btn:hover{color:var(--error-color);background-color:color-mix(in srgb,var(--error-color) 10%,transparent 90%)}.file-remove-btn .material-symbols{font-size:1rem}.file-add-btn{color:var(--text-neutral-color);cursor:pointer}.file-add-btn:hover{color:var(--primary-color);background-color:color-mix(in srgb,var(--primary-color) 10%,transparent 90%)}.file-progress{position:absolute;right:0;bottom:0;left:0;height:.2rem;background-color:color-mix(in srgb,var(--neutral-color) 55%,transparent 45%)}.file-progress-value{height:100%;background-color:var(--primary-color);transition:width .15s ease-out}.file-progress.error{background-color:color-mix(in srgb,var(--error-color) 20%,transparent 80%)}.file-progress.error .file-progress-value{background-color:var(--error-color)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChrInputComponent, selector: "chr-input", inputs: ["label", "placeholder", "id", "name", "value", "displayValue", "type", "outputType", "step", "debounceTime", "isDisabled", "isFocused", "min", "max", "minLength", "maxLength", "ngControl", "tooltip", "isIndicatorsOpen", "tooltipPosition", "required", "state", "errors"], outputs: ["idChange", "nameChange", "valueChange", "displayValueChange", "outputTypeChange", "isDisabledChange", "isFocusedChange", "ngControlChange", "isIndicatorsOpenChange", "requiredChange", "stateChange", "input", "change", "debouncedInput", "focus", "enter", "blur"] }] }); }
4914
4865
  }
4915
4866
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChrFileInputComponent, decorators: [{
4916
4867
  type: Component,
4917
- args: [{ selector: 'chr-file-input', imports: [ReactiveFormsModule, NgTemplateOutlet, ChrInputComponent], template: "<div class=\"input-wrapper\" [attr.data-state]=\"state()\">\n <!-- Zone de Drop & S\u00E9lection (remplace visuellement l'input texte standard) -->\n <div class=\"drop-zone\" [hidden]=\"files().length>0\" [class.dragging]=\"isDragging()\" [class.disabled]=\"isDisabled()\"\n (click)=\"triggerFileSelect();blur.emit();\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <input #fileNativeInput #input type=\"file\" class=\"native-file-input\" [accept]=\"accept()\" [multiple]=\"multiple()\"\n (change)=\"onFileSelected($event)\" />\n\n <div class=\"drop-zone-content\">\n <span class=\"material-symbols upload-icon\">cloud_upload</span>\n <div class=\"drop-zone-text\">\n <span class=\"primary-text\">\n @if (label()) { {{ label() }} } @else { Cliquez ou glissez un fichier ici }\n </span>\n <span class=\"secondary-text\">\n {{ multiple() ? 'S\u00E9lectionnez un ou plusieurs fichiers' : 'S\u00E9lectionnez un fichier' }}\n </span>\n @if(isTouched()){\n @let error = getLastError();\n @if(error != null){\n <span class=\"error-text error\">\n {{getLastError() ?? null}}\n </span>\n }\n }\n </div>\n </div>\n </div>\n\n <!-- Liste des fichiers s\u00E9lectionn\u00E9s sous forme de cartes/puces -->\n @if (files().length > 0) {\n <div class=\"file-list\">\n @for (entry of files(); track entry.id) {\n <div class=\"file-item\" [class.error]=\"entry.error || getFileValidationError($index)\">\n <span class=\"material-symbols file-icon\">{{ entry.error || getFileValidationError($index) ? 'error' :\n 'description' }}</span>\n <div class=\"file-info\">\n <div class=\"file-data\" [class.full-width]=\"dataTemplateRef() == undefined\">\n <div class=\"file-name\">\n @if(allowChangingFileName()){\n <chr-input [value]=\"entry.file.name\"\n (valueChange)=\"entry.file.name=$event?.trim()??entry.file.file!.name.split('.')[0];onChange(this.value())\"\n type=\"text\"></chr-input>\n }@else {\n <span class=\"file-name\">{{\n entry.file.name }}\n </span>\n }\n <span>.{{entry.file.extension}}</span>\n </div>\n <div class=\"file-name\">\n <span class=\"file-size\">{{ formatFileSize(entry.file.file?.size ?? 0) }}</span>\n <span class=\"file-content\">|</span>\n <span class=\"file-content\">{{ entry.file.contentType }}</span>\n </div>\n </div>\n @if(dataTemplateRef()){\n <div class=\"file-data\">\n <ng-container *ngTemplateOutlet=\"dataTemplateRef(); context: { $implicit: entry.file}\">\n </ng-container>\n </div>\n }\n <div class=\"file-data full-width\">\n @if (entry.error) {\n <span class=\"file-error\" role=\"alert\">{{ entry.error }}</span>\n } @else if (getFileValidationError($index); as validationError) {\n <span class=\"file-error\" role=\"alert\">{{ validationError }}</span>\n }\n </div>\n </div>\n <button type=\"button\" class=\"file-remove-btn\" (click)=\"removeFile($index, $event)\"\n [attr.aria-label]=\"'Supprimer ' + entry.file.file?.name\">\n <span class=\"material-symbols\">close</span>\n </button>\n <div class=\"file-progress\" [class.error]=\"entry.error || getFileValidationError($index)\" role=\"progressbar\"\n [attr.aria-label]=\"entry.error || getFileValidationError($index) || 'Progression de la conversion'\"\n [attr.aria-valuenow]=\"entry.progress\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"file-progress-value\" [style.width.%]=\"entry.progress\"></div>\n </div>\n </div>\n }\n @if(multiple()){\n <button class=\"file-item fixed-size file-add-btn\" (click)=\"triggerFileSelect()\">\n <span class=\"material-symbols\">add</span>\n </button>\n }\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.input-wrapper{--chr-input-current-color: var(--text-neutral-color);position:relative;box-sizing:border-box;min-height:1.5rem;max-height:1.5rem;height:1.5rem;display:flex;flex:1 1 auto;min-width:0;flex-direction:column;border-bottom:.125rem solid var(--chr-input-current-color)}.input-wrapper .input-row{display:flex;flex:1 1 auto;flex-wrap:nowrap;height:inherit}.input-wrapper .input{min-width:0;height:inherit;color:var(--text-color)}.input-wrapper .indicators{display:flex;flex-direction:row;flex:1 1 auto;flex-wrap:nowrap;gap:.25rem;max-width:fit-content;height:inherit;align-items:center;color:var(--chr-input-current-color);font-size:.75rem!important}.input-wrapper .input~.indicators{position:absolute;top:0;right:0;visibility:hidden;opacity:0;transform:translate(1rem);transition:opacity .15s ease-in-out,transform .15s ease-in-out,width .15s ease-in-out .15s}.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]{position:relative;visibility:visible;padding-left:.125rem;opacity:1;transform:translate(0)}:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(chr-input-indicators:not(:empty)),:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(:nth-child(2)){border-left:.125rem solid color-mix(in srgb,var(--chr-input-current-color) 40%,transparent 60%)}.input-wrapper:has(.indicators[data-indicators-open=true]) .input{padding-right:.125rem}.input-wrapper .input:has(~.indicators:empty){padding-right:0}.input-wrapper .input~.indicators:empty{width:0;border-left:none;padding-left:0}.input-wrapper .tooltip-indicator{cursor:pointer;font-size:1.25rem;padding:0;color:var(--chr-input-current-color)}.input-wrapper .tooltip-indicator:hover{color:var(--primary-color)}.input-wrapper .required{color:var(--error-color)}.input-wrapper:has(.input:focus){--chr-input-current-color: var(--primary-color)}.input-wrapper[data-state=IDLE]{--chr-input-current-color: color-mix(in srgb, var(--text-neutral-color) 90%, transparent 10%) }.input-wrapper[data-state=INVALID],.input-wrapper[data-state=DISABLED]{--chr-input-current-color: var(--error-color)}.input-wrapper[data-state=PENDING],.input-wrapper[data-state=WARNING]{--chr-input-current-color: var(--warn-color)}.input-wrapper label{position:absolute;pointer-events:none;top:50%;color:var(--chr-input-current-color);transform:translateY(-50%);transition:transfom 125ms ease-in-out,position 125ms ease-in-out,top 125ms ease-in-out,font-size 125ms ease-in-out,left 125ms ease-in-out;font-size:.85rem;font-weight:700}.input-wrapper label:not(:empty):after{position:absolute;content:\":\"}.input-wrapper label:has(~.input:focus),.input-wrapper label:has(~.input:not(:placeholder-shown)){top:-.25rem;left:0;font-size:75%}.input-wrapper .input{width:100%;outline:none;border:none;padding:0;background-color:transparent}.data-overlay{height:0;grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.data{grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.error{grid-area:data / data;display:flex;flex:1 1 auto;flex-direction:row;flex-wrap:wrap;min-width:calc(100% - .5rem);max-width:min-content;color:var(--error-color);padding:.25rem;border-top-left-radius:0;border-top-right-radius:0;background-color:var(--background-color);z-index:2}.error:empty{display:none}.input[disabled]{--chr-input-disabled-neutral-muted-color: color-mix(in srgb, var(--text-neutral-color) 15%, transparent 85%);background:repeating-linear-gradient(45deg,var(--chr-input-disabled-neutral-muted-color),var(--chr-input-disabled-neutral-muted-color) .625rem,var(--background-color) .625rem,var(--background-color) 1.25rem);cursor:not-allowed}.input{accent-color:var(--primary-color)}.input ::-webkit-datetime-edit,.input ::-webkit-datetime-edit-fields-wrapper,.input ::-webkit-datetime-edit-text,.input ::-webkit-datetime-edit-month-field,.input ::-webkit-datetime-edit-day-field,.input ::-webkit-datetime-edit-year-field,.input ::-webkit-inner-spin-button,.input ::-webkit-calendar-picker-indicator{place-self:end;justify-self:end;background-color:red}.input[type=range]{color:red}.input[type=range]:after{content:attr(value)}.input[type=textarea]::-webkit-scrollbar{display:none!important}.no-scrollbar{-ms-overflow-style:none!important;scrollbar-width:none!important}:host{display:flex;flex-direction:column;flex:1 1 auto;justify-self:stretch;align-self:center;min-width:0}.input-wrapper{display:flex;flex-direction:column;gap:.75rem;height:auto;max-height:none;width:100%;border-bottom:none}.native-file-input{display:none}.drop-zone{border:2px dashed var(--chr-input-current-color);border-radius:.5rem;padding:1.25rem 1rem;background-color:var(--background-color);cursor:pointer;transition:all .2s ease-in-out}.drop-zone:hover:not(.disabled){border-color:var(--primary-color);background-color:var(--background-neutral-color)}.drop-zone.dragging{border-color:var(--tertiary-color);background-color:var(--background-neutral-color)}.drop-zone.disabled{opacity:.6;cursor:not-allowed}.drop-zone-content{display:flex;align-items:center;justify-content:center;gap:.75rem}.upload-icon{font-size:2rem;color:color-mix(in srgb,var(--primary-color) 80%,transparent 20%)}.drop-zone-text{display:flex;flex-direction:column;text-align:center}.primary-text{font-size:.875rem;font-weight:500;color:var(--text-color)}.secondary-text{font-size:.75rem;color:var(--text-neutral-color)}.error-text{font-size:.75rem;font-weight:500;color:var(--error-color)}.file-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:.375rem}.file-item{position:relative;display:flex;flex:1 1 auto;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:var(--background-color);border:1px solid var(--neutral-color);border-radius:.375rem;overflow:hidden}.file-item.fixed-size{flex:0 1 auto}.file-item.error{border-color:var(--error-color)}.file-icon{font-size:1.25rem;color:var(--text-neutral-color)}.file-info{display:grid;flex:1 1 auto;grid-template-columns:1fr 1fr}.file-data{grid-column:span 1;border-left:1px solid var(--neutral-color);padding-left:.5rem}.file-data.full-width{grid-column:span 2}.file-data:not(:last-of-type){padding-right:.5rem}.file-name{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;font-size:.8125rem;font-weight:500;white-space:nowrap}.file-size,.file-content{font-size:.6875rem;color:var(--text-neutral-color)}.file-content{margin-left:.25rem}.file-error{color:var(--error-color);font-size:.6875rem}.file-remove-btn{background:transparent;border:none;color:var(--text-neutral-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:.25rem;border-radius:.25rem;transition:color .15s,background-color .15s}.file-remove-btn:hover{color:var(--error-color);background-color:color-mix(in srgb,var(--error-color) 10%,transparent 90%)}.file-remove-btn .material-symbols{font-size:1rem}.file-add-btn{color:var(--text-neutral-color);cursor:pointer}.file-add-btn:hover{color:var(--primary-color);background-color:color-mix(in srgb,var(--primary-color) 10%,transparent 90%)}.file-progress{position:absolute;right:0;bottom:0;left:0;height:.2rem;background-color:color-mix(in srgb,var(--neutral-color) 55%,transparent 45%)}.file-progress-value{height:100%;background-color:var(--primary-color);transition:width .15s ease-out}.file-progress.error{background-color:color-mix(in srgb,var(--error-color) 20%,transparent 80%)}.file-progress.error .file-progress-value{background-color:var(--error-color)}\n"] }]
4868
+ args: [{ selector: 'chr-file-input', imports: [ReactiveFormsModule, NgTemplateOutlet, ChrInputComponent], template: "<div class=\"input-wrapper\" [attr.data-state]=\"state()\">\n <!-- Zone de Drop & S\u00E9lection (remplace visuellement l'input texte standard) -->\n <div class=\"drop-zone\" [hidden]=\"files().length>0\" [class.dragging]=\"isDragging()\" [class.disabled]=\"isDisabled()\"\n (click)=\"triggerFileSelect();blur.emit();\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n\n <input #fileNativeInput #input type=\"file\" class=\"native-file-input\" [accept]=\"accept()\" [multiple]=\"multiple()\"\n (change)=\"onFileSelected($event)\" />\n\n <div class=\"drop-zone-content\">\n <span class=\"material-symbols upload-icon\">cloud_upload</span>\n <div class=\"drop-zone-text\">\n <span class=\"primary-text\">\n @if (label()) { {{ label() }} } @else { Cliquez ou glissez un fichier ici }\n </span>\n <span class=\"secondary-text\">\n {{ multiple() ? 'S\u00E9lectionnez un ou plusieurs fichiers' : 'S\u00E9lectionnez un fichier' }}\n </span>\n @if(isTouched()){\n @let error = getLastError();\n @if(error != null){\n <span class=\"error-text error\">\n {{getLastError() ?? null}}\n </span>\n }\n }\n </div>\n </div>\n </div>\n\n <!-- Liste des fichiers s\u00E9lectionn\u00E9s sous forme de cartes/puces -->\n @if (files().length > 0) {\n <div class=\"file-list\">\n @for (entry of files(); track entry.id) {\n @let error = fileValidationErrors()[$index];\n <div class=\"file-item\" [class.error]=\"entry.error || error\">\n <span class=\"material-symbols file-icon\">{{ entry.error || error ? 'error' :\n 'description' }}</span>\n <div class=\"file-info\">\n <div class=\"file-data\" [class.full-width]=\"dataTemplateRef() == undefined\">\n <div class=\"file-name\">\n @if(allowChangingFileName()){\n <chr-input [value]=\"entry.file.name\"\n (valueChange)=\"entry.file.name=$event?.trim()??entry.file.file!.name.split('.')[0];onChange(this.value())\"\n type=\"text\"></chr-input>\n }@else {\n <span class=\"file-name\">{{\n entry.file.name }}\n </span>\n }\n <span>.{{entry.file.extension}}</span>\n </div>\n <div class=\"file-name\">\n <span class=\"file-size\">{{ formatFileSize(entry.file.file?.size ?? 0) }}</span>\n <span class=\"file-content\">|</span>\n <span class=\"file-content\">{{ entry.file.contentType }}</span>\n </div>\n </div>\n @if(dataTemplateRef()){\n <div class=\"file-data\">\n <ng-container *ngTemplateOutlet=\"dataTemplateRef(); context: { $implicit: entry.file}\">\n </ng-container>\n </div>\n }\n <div class=\"file-data full-width\">\n @if (entry.error) {\n <span class=\"file-error\" role=\"alert\">{{ entry.error }}</span>\n } @else if (error; as validationError) {\n <span class=\"file-error\" role=\"alert\">{{ validationError }}</span>\n }\n </div>\n </div>\n <button type=\"button\" class=\"file-remove-btn\" (click)=\"removeFile($index, $event)\"\n [attr.aria-label]=\"'Supprimer ' + entry.file.file?.name\">\n <span class=\"material-symbols\">close</span>\n </button>\n <div class=\"file-progress\" [class.error]=\"entry.error ||error\" role=\"progressbar\"\n [attr.aria-label]=\"entry.error || error || 'Progression de la conversion'\"\n [attr.aria-valuenow]=\"entry.progress\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"file-progress-value\" [style.width.%]=\"entry.progress\"></div>\n </div>\n </div>\n }\n @if(multiple()){\n <button class=\"file-item fixed-size file-add-btn\" (click)=\"triggerFileSelect()\">\n <span class=\"material-symbols\">add</span>\n </button>\n }\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.input-wrapper{--chr-input-current-color: var(--text-neutral-color);position:relative;box-sizing:border-box;min-height:1.5rem;max-height:1.5rem;height:1.5rem;display:flex;flex:1 1 auto;min-width:0;flex-direction:column;border-bottom:.125rem solid var(--chr-input-current-color)}.input-wrapper .input-row{display:flex;flex:1 1 auto;flex-wrap:nowrap;height:inherit}.input-wrapper .input{min-width:0;height:inherit;color:var(--text-color)}.input-wrapper .indicators{display:flex;flex-direction:row;flex:1 1 auto;flex-wrap:nowrap;gap:.25rem;max-width:fit-content;height:inherit;align-items:center;color:var(--chr-input-current-color);font-size:.75rem!important}.input-wrapper .input~.indicators{position:absolute;top:0;right:0;visibility:hidden;opacity:0;transform:translate(1rem);transition:opacity .15s ease-in-out,transform .15s ease-in-out,width .15s ease-in-out .15s}.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]{position:relative;visibility:visible;padding-left:.125rem;opacity:1;transform:translate(0)}:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(chr-input-indicators:not(:empty)),:is(.input-wrapper:focus-within .input~.indicators,.input-wrapper:hover .input~.indicators,.input-wrapper .input~.indicators[data-indicators-open=true]):has(:nth-child(2)){border-left:.125rem solid color-mix(in srgb,var(--chr-input-current-color) 40%,transparent 60%)}.input-wrapper:has(.indicators[data-indicators-open=true]) .input{padding-right:.125rem}.input-wrapper .input:has(~.indicators:empty){padding-right:0}.input-wrapper .input~.indicators:empty{width:0;border-left:none;padding-left:0}.input-wrapper .tooltip-indicator{cursor:pointer;font-size:1.25rem;padding:0;color:var(--chr-input-current-color)}.input-wrapper .tooltip-indicator:hover{color:var(--primary-color)}.input-wrapper .required{color:var(--error-color)}.input-wrapper:has(.input:focus){--chr-input-current-color: var(--primary-color)}.input-wrapper[data-state=IDLE]{--chr-input-current-color: color-mix(in srgb, var(--text-neutral-color) 90%, transparent 10%) }.input-wrapper[data-state=INVALID],.input-wrapper[data-state=DISABLED]{--chr-input-current-color: var(--error-color)}.input-wrapper[data-state=PENDING],.input-wrapper[data-state=WARNING]{--chr-input-current-color: var(--warn-color)}.input-wrapper label{position:absolute;pointer-events:none;top:50%;color:var(--chr-input-current-color);transform:translateY(-50%);transition:transfom 125ms ease-in-out,position 125ms ease-in-out,top 125ms ease-in-out,font-size 125ms ease-in-out,left 125ms ease-in-out;font-size:.85rem;font-weight:700}.input-wrapper label:not(:empty):after{position:absolute;content:\":\"}.input-wrapper label:has(~.input:focus),.input-wrapper label:has(~.input:not(:placeholder-shown)){top:-.25rem;left:0;font-size:75%}.input-wrapper .input{width:100%;outline:none;border:none;padding:0;background-color:transparent}.data-overlay{height:0;grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.data{grid-area:data / data;display:flex;flex-direction:column;flex-wrap:nowrap}.error{grid-area:data / data;display:flex;flex:1 1 auto;flex-direction:row;flex-wrap:wrap;min-width:calc(100% - .5rem);max-width:min-content;color:var(--error-color);padding:.25rem;border-top-left-radius:0;border-top-right-radius:0;background-color:var(--background-color);z-index:2}.error:empty{display:none}.input[disabled]{--chr-input-disabled-neutral-muted-color: color-mix(in srgb, var(--text-neutral-color) 15%, transparent 85%);background:repeating-linear-gradient(45deg,var(--chr-input-disabled-neutral-muted-color),var(--chr-input-disabled-neutral-muted-color) .625rem,var(--background-color) .625rem,var(--background-color) 1.25rem);cursor:not-allowed}.input{accent-color:var(--primary-color)}.input ::-webkit-datetime-edit,.input ::-webkit-datetime-edit-fields-wrapper,.input ::-webkit-datetime-edit-text,.input ::-webkit-datetime-edit-month-field,.input ::-webkit-datetime-edit-day-field,.input ::-webkit-datetime-edit-year-field,.input ::-webkit-inner-spin-button,.input ::-webkit-calendar-picker-indicator{place-self:end;justify-self:end;background-color:red}.input[type=range]{color:red}.input[type=range]:after{content:attr(value)}.input[type=textarea]::-webkit-scrollbar{display:none!important}.no-scrollbar{-ms-overflow-style:none!important;scrollbar-width:none!important}:host{display:flex;flex-direction:column;flex:1 1 auto;justify-self:stretch;align-self:center;min-width:0}.input-wrapper{display:flex;flex-direction:column;gap:.75rem;height:auto;max-height:none;width:100%;border-bottom:none}.native-file-input{display:none}.drop-zone{border:2px dashed var(--chr-input-current-color);border-radius:.5rem;padding:1.25rem 1rem;background-color:var(--background-color);cursor:pointer;transition:all .2s ease-in-out}.drop-zone:hover:not(.disabled){border-color:var(--primary-color);background-color:var(--background-neutral-color)}.drop-zone.dragging{border-color:var(--tertiary-color);background-color:var(--background-neutral-color)}.drop-zone.disabled{opacity:.6;cursor:not-allowed}.drop-zone-content{display:flex;align-items:center;justify-content:center;gap:.75rem}.upload-icon{font-size:2rem;color:color-mix(in srgb,var(--primary-color) 80%,transparent 20%)}.drop-zone-text{display:flex;flex-direction:column;text-align:center}.primary-text{font-size:.875rem;font-weight:500;color:var(--text-color)}.secondary-text{font-size:.75rem;color:var(--text-neutral-color)}.error-text{font-size:.75rem;font-weight:500;color:var(--error-color)}.file-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:.375rem}.file-item{position:relative;display:flex;flex:1 1 auto;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:var(--background-color);border:1px solid var(--neutral-color);border-radius:.375rem;overflow:hidden}.file-item.fixed-size{flex:0 1 auto}.file-item.error{border-color:var(--error-color)}.file-icon{font-size:1.25rem;color:var(--text-neutral-color)}.file-info{display:grid;flex:1 1 auto;grid-template-columns:1fr 1fr}.file-data{grid-column:span 1;border-left:1px solid var(--neutral-color);padding-left:.5rem}.file-data.full-width{grid-column:span 2}.file-data:not(:last-of-type){padding-right:.5rem}.file-name{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;font-size:.8125rem;font-weight:500;white-space:nowrap}.file-size,.file-content{font-size:.6875rem;color:var(--text-neutral-color)}.file-content{margin-left:.25rem}.file-error{color:var(--error-color);font-size:.6875rem}.file-remove-btn{background:transparent;border:none;color:var(--text-neutral-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:.25rem;border-radius:.25rem;transition:color .15s,background-color .15s}.file-remove-btn:hover{color:var(--error-color);background-color:color-mix(in srgb,var(--error-color) 10%,transparent 90%)}.file-remove-btn .material-symbols{font-size:1rem}.file-add-btn{color:var(--text-neutral-color);cursor:pointer}.file-add-btn:hover{color:var(--primary-color);background-color:color-mix(in srgb,var(--primary-color) 10%,transparent 90%)}.file-progress{position:absolute;right:0;bottom:0;left:0;height:.2rem;background-color:color-mix(in srgb,var(--neutral-color) 55%,transparent 45%)}.file-progress-value{height:100%;background-color:var(--primary-color);transition:width .15s ease-out}.file-progress.error{background-color:color-mix(in srgb,var(--error-color) 20%,transparent 80%)}.file-progress.error .file-progress-value{background-color:var(--error-color)}\n"] }]
4918
4869
  }], ctorParameters: () => [], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], base64: [{ type: i0.Input, args: [{ isSignal: true, alias: "base64", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], allowChangingFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowChangingFileName", required: false }] }], fileNativeInput: [{ type: i0.ViewChild, args: ['fileNativeInput', { ...{
4919
4870
  read: (ElementRef),
4920
- }, isSignal: true }] }], dataTemplateRef: [{ type: i0.ContentChild, args: ['data', { ...{ read: TemplateRef }, isSignal: true }] }], dataTemplateControls: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NgControl), { ...{
4871
+ }, isSignal: true }] }], dataTemplateRef: [{ type: i0.ContentChild, args: ['data', { ...{
4872
+ read: TemplateRef,
4873
+ }, isSignal: true }] }], dataTemplateControls: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NgControl), { ...{
4921
4874
  descendants: true,
4922
4875
  }, isSignal: true }] }] } });
4923
4876
 
@@ -8967,7 +8920,7 @@ class MinFileNameLengthValidator {
8967
8920
  return max ? minFileNameLength(max)(control) : null;
8968
8921
  }
8969
8922
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MinFileNameLengthValidator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
8970
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: MinFileNameLengthValidator, isStandalone: true, selector: "[app-max-file-size]", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
8923
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: MinFileNameLengthValidator, isStandalone: true, selector: "[app-min-file-name-length]", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
8971
8924
  {
8972
8925
  provide: NG_VALIDATORS,
8973
8926
  useExisting: MinFileNameLengthValidator,
@@ -8982,7 +8935,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
8982
8935
  }, {
8983
8936
  type: Directive,
8984
8937
  args: [{
8985
- selector: '[app-max-file-size]',
8938
+ selector: '[app-min-file-name-length]',
8986
8939
  providers: [
8987
8940
  {
8988
8941
  provide: NG_VALIDATORS,
@@ -8993,40 +8946,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
8993
8946
  standalone: true,
8994
8947
  }]
8995
8948
  }], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }] } });
8996
- function minFileNameLength(maxLength) {
8949
+ function minFileNameLength(minLength) {
8997
8950
  return (control) => {
8998
8951
  const value = control.value;
8999
- const errors = {};
9000
- let isSizeCompliant = true;
9001
- let fileSize = null;
9002
- if (value === null)
8952
+ const files = Array.isArray(value) ? value : [value];
8953
+ if (!value)
9003
8954
  return null;
9004
- if (Array.isArray(value)) {
9005
- for (let i = 0; i < value.length; i++) {
9006
- const fileNameLength = value[i].name?.length;
9007
- if (fileNameLength === undefined)
9008
- continue;
9009
- if (fileNameLength <= maxLength) {
9010
- errors[i] = true;
9011
- isSizeCompliant = false;
9012
- fileSize = value[i].name.length;
9013
- }
9014
- }
9015
- }
9016
- else {
9017
- isSizeCompliant =
9018
- value?.name?.length === undefined || value.name.length >= maxLength;
8955
+ const errors = {};
8956
+ for (let i = 0; i < files.length; i++) {
8957
+ const fileNameLength = files[i].name?.length;
8958
+ if (fileNameLength === undefined)
8959
+ continue;
8960
+ if (fileNameLength < minLength)
8961
+ errors[i] = fileNameLength;
9019
8962
  }
9020
- return isSizeCompliant == false
9021
- ? {
9022
- minFileNameLength: {
9023
- maxFileNameLength: maxLength,
9024
- actual: fileSize,
9025
- indexes: errors,
9026
- },
8963
+ if (Object.keys(errors).length <= 0)
8964
+ return null;
8965
+ return {
8966
+ minFileNameLength: {
8967
+ minFileNameLength: minLength,
9027
8968
  indexes: errors,
9028
- }
9029
- : null;
8969
+ },
8970
+ indexes: errors,
8971
+ };
9030
8972
  };
9031
8973
  }
9032
8974