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,
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
let fileSize = null;
|
|
778
|
-
if (value === null)
|
|
775
|
+
const files = Array.isArray(value) ? value : [value];
|
|
776
|
+
if (!value)
|
|
779
777
|
return null;
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
852
|
-
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
:
|
|
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, /*
|
|
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: "[
|
|
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: '[
|
|
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: "
|
|
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
|
|
1035
|
-
|
|
1036
|
-
let fileSize = null;
|
|
1037
|
-
if (value === null)
|
|
1038
|
+
const files = Array.isArray(value) ? value : [value];
|
|
1039
|
+
if (!value)
|
|
1038
1040
|
return null;
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
:
|
|
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"]
|
|
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: [],
|
|
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
|
|
4647
|
-
*
|
|
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
|
|
4683
|
-
*
|
|
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
|
-
|
|
4693
|
-
|
|
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((
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
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
|
|
4731
|
-
if (
|
|
4732
|
-
this.addFiles(Array.from(
|
|
4733
|
-
|
|
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
|
|
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
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
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
|
|
4790
|
-
let
|
|
4791
|
-
for (const entry of
|
|
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
|
-
|
|
4768
|
+
this.files.update((entries) => entries.map((current) => current.id === entry.id
|
|
4795
4769
|
? {
|
|
4796
|
-
...
|
|
4797
|
-
file: result.file ??
|
|
4770
|
+
...current,
|
|
4771
|
+
file: result.file ?? current.file,
|
|
4798
4772
|
progress: result.progress,
|
|
4799
4773
|
}
|
|
4800
|
-
:
|
|
4801
|
-
this.fileEntries.set(updatedEntries);
|
|
4774
|
+
: current));
|
|
4802
4775
|
},
|
|
4803
4776
|
error: () => {
|
|
4804
|
-
|
|
4805
|
-
this.
|
|
4777
|
+
hasError = true;
|
|
4778
|
+
this.files.update((entries) => entries.map((current) => current.id === entry.id
|
|
4806
4779
|
? {
|
|
4807
|
-
...
|
|
4780
|
+
...current,
|
|
4808
4781
|
progress: 0,
|
|
4809
4782
|
error: 'La conversion du fichier a échoué',
|
|
4810
4783
|
}
|
|
4811
|
-
:
|
|
4812
|
-
completedConversions++;
|
|
4813
|
-
if (completedConversions === filesToConvert.length &&
|
|
4814
|
-
!conversionFailed) {
|
|
4815
|
-
this.updateValue(this.files());
|
|
4816
|
-
}
|
|
4784
|
+
: current));
|
|
4817
4785
|
},
|
|
4818
4786
|
complete: () => {
|
|
4819
|
-
|
|
4820
|
-
if (
|
|
4821
|
-
|
|
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
|
|
4835
|
-
|
|
4836
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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', { ...{
|
|
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-
|
|
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-
|
|
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(
|
|
8949
|
+
function minFileNameLength(minLength) {
|
|
8997
8950
|
return (control) => {
|
|
8998
8951
|
const value = control.value;
|
|
8999
|
-
const
|
|
9000
|
-
|
|
9001
|
-
let fileSize = null;
|
|
9002
|
-
if (value === null)
|
|
8952
|
+
const files = Array.isArray(value) ? value : [value];
|
|
8953
|
+
if (!value)
|
|
9003
8954
|
return null;
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
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
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
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
|
-
:
|
|
8969
|
+
},
|
|
8970
|
+
indexes: errors,
|
|
8971
|
+
};
|
|
9030
8972
|
};
|
|
9031
8973
|
}
|
|
9032
8974
|
|