@webilix/ngx-form-m3 0.0.13 → 0.0.15

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.
@@ -128,23 +128,21 @@ class FormErrorDirective {
128
128
  elementRef;
129
129
  formGroupDirective;
130
130
  onSubmit() {
131
- if (this.formGroupDirective && this.formGroupDirective.control.invalid) {
132
- const invalidControl = this.elementRef.nativeElement.querySelector('.ng-invalid');
131
+ setTimeout(() => {
132
+ const invalidControl = this.elementRef.nativeElement.querySelector('.mat-form-field-invalid');
133
133
  if (invalidControl)
134
134
  return this.scrollToElement(invalidControl);
135
- }
136
- const invalidControl = this.elementRef.nativeElement.querySelector('.ngx-form-invalid');
137
- if (invalidControl)
138
- this.scrollToElement(invalidControl);
135
+ }, 150);
139
136
  }
140
137
  constructor(elementRef, formGroupDirective) {
141
138
  this.elementRef = elementRef;
142
139
  this.formGroupDirective = formGroupDirective;
143
140
  }
141
+ getElementTop(element) {
142
+ return element.getBoundingClientRect().top + window.scrollY - 100;
143
+ }
144
144
  scrollToElement(element) {
145
- const labelOffset = 150;
146
- const top = element.getBoundingClientRect().top + window.scrollY - labelOffset;
147
- window.scroll({ top, left: 0, behavior: 'smooth' });
145
+ window.scroll({ top: this.getElementTop(element), behavior: 'smooth' });
148
146
  }
149
147
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: FormErrorDirective, deps: [{ token: i0.ElementRef }, { token: i1.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
150
148
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.5", type: FormErrorDirective, isStandalone: true, selector: "form.ngx-form", host: { listeners: { "submit": "onSubmit()" } }, ngImport: i0 });
@@ -193,6 +191,10 @@ class InputErrorPipe {
193
191
  return `زمان باید برابر یا بعد از ${value} انتخاب شده باشد.`;
194
192
  case 'maxmoment':
195
193
  return `زمان باید برابر یا قبل از ${value} انتخاب شده باشد.`;
194
+ case 'mincount':
195
+ return `انتخاب حداقل ${Helper.NUMBER.format(value)} گزینه الزامی است.`;
196
+ case 'maxcount':
197
+ return `امکان انتخاب بیشتر از ${Helper.NUMBER.format(value)} گزینه وجود ندارد.`;
196
198
  case 'pattern':
197
199
  switch (type) {
198
200
  case 'EMAIL':
@@ -467,6 +469,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
467
469
  args: [{ required: true }]
468
470
  }] } });
469
471
 
472
+ const MaxCountValidator = (max) => {
473
+ return (formControl) => {
474
+ const value = Array.isArray(formControl.value) ? formControl.value : [];
475
+ return value.length > max ? { maxcount: max } : null;
476
+ };
477
+ };
478
+
479
+ const MinCountValidator = (min) => {
480
+ return (formControl) => {
481
+ const value = Array.isArray(formControl.value) ? formControl.value : [];
482
+ return value.length === 0 ? { required: true } : value.length < min ? { mincount: min } : null;
483
+ };
484
+ };
485
+
470
486
  const MaxDateValidator = (max) => {
471
487
  const jalali = JalaliDateTime();
472
488
  return (formControl) => {
@@ -654,6 +670,45 @@ class InputFileMethods extends InputMethods {
654
670
  }
655
671
  }
656
672
 
673
+ class InputIconComponent {
674
+ formControl = inject(INPUT_CONTROL);
675
+ input = inject(INPUT_TYPE);
676
+ config = inject(INPUT_CONFIG);
677
+ values;
678
+ isButtonDisabled;
679
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: InputIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
680
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: InputIconComponent, isStandalone: true, selector: "ng-component", inputs: { values: "values", isButtonDisabled: "isButtonDisabled" }, host: { attributes: { "selector": "input-icon" } }, ngImport: i0, template: "<mat-form-field [appearance]=\"input.appearance || config.appearance\">\n <mat-label>{{ input.title || '\u0622\u06CC\u06A9\u0648\u0646' }}</mat-label>\n @if (formControl.invalid) { <mat-error>{{ formControl.errors | InputErrorPipe : input.type }}</mat-error> }\n\n <!-- HINT -->\n @if (input.hint) { <mat-hint>{{ input.hint }}</mat-hint> }\n\n <!-- BUTTON -->\n @if (input.button) {\n <span matIconSuffix>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"isButtonDisabled\"\n (click)=\"input.button.onClick(values)\"\n [tabIndex]=\"-1\"\n >\n <mat-icon [style.color]=\"isButtonDisabled ? undefined : input.button.color\">\n {{ input.button.icon }}\n </mat-icon>\n </button>\n </span>\n }\n\n <div class=\"ngx-helper-form-m3-icon-input\">\n @if (formControl.value) {\n <mat-icon>{{ formControl.value }}</mat-icon>\n }\n <input\n matInput\n type=\"text\"\n inputmode=\"url\"\n [name]=\"input.name\"\n [formControl]=\"formControl\"\n class=\"ngx-form-m3-en\"\n [dir]=\"'ltr'\"\n [AutoFocusDirective]=\"config.autoFocus === input.name\"\n />\n </div>\n\n <!-- DESCRIPTION -->\n @if (input.description) {\n <div\n class=\"ngx-form-m3-input-description\"\n [class.ngx-form-m3-disabled-input]=\"formControl.disabled\"\n [innerHTML]=\"input.description | MultiLinePipe\"\n ></div>\n }\n</mat-form-field>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.Dir, selector: "[dir]", inputs: ["dir"], outputs: ["dirChange"], exportAs: ["dir"] }, { kind: "directive", type: AutoCompleteDirective, selector: "input[type=\"text\"]" }, { kind: "directive", type: AutoFocusDirective, selector: "[AutoFocusDirective]", inputs: ["AutoFocusDirective"] }, { kind: "pipe", type: InputErrorPipe, name: "InputErrorPipe" }, { kind: "pipe", type: MultiLinePipe, name: "MultiLinePipe" }] });
681
+ }
682
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: InputIconComponent, decorators: [{
683
+ type: Component,
684
+ args: [{ host: { selector: 'input-icon' }, imports: [
685
+ ReactiveFormsModule,
686
+ MatFormField,
687
+ MatIcon,
688
+ MatIconButton,
689
+ MatInputModule,
690
+ AutoCompleteDirective,
691
+ AutoFocusDirective,
692
+ InputErrorPipe,
693
+ MultiLinePipe,
694
+ ], template: "<mat-form-field [appearance]=\"input.appearance || config.appearance\">\n <mat-label>{{ input.title || '\u0622\u06CC\u06A9\u0648\u0646' }}</mat-label>\n @if (formControl.invalid) { <mat-error>{{ formControl.errors | InputErrorPipe : input.type }}</mat-error> }\n\n <!-- HINT -->\n @if (input.hint) { <mat-hint>{{ input.hint }}</mat-hint> }\n\n <!-- BUTTON -->\n @if (input.button) {\n <span matIconSuffix>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"isButtonDisabled\"\n (click)=\"input.button.onClick(values)\"\n [tabIndex]=\"-1\"\n >\n <mat-icon [style.color]=\"isButtonDisabled ? undefined : input.button.color\">\n {{ input.button.icon }}\n </mat-icon>\n </button>\n </span>\n }\n\n <div class=\"ngx-helper-form-m3-icon-input\">\n @if (formControl.value) {\n <mat-icon>{{ formControl.value }}</mat-icon>\n }\n <input\n matInput\n type=\"text\"\n inputmode=\"url\"\n [name]=\"input.name\"\n [formControl]=\"formControl\"\n class=\"ngx-form-m3-en\"\n [dir]=\"'ltr'\"\n [AutoFocusDirective]=\"config.autoFocus === input.name\"\n />\n </div>\n\n <!-- DESCRIPTION -->\n @if (input.description) {\n <div\n class=\"ngx-form-m3-input-description\"\n [class.ngx-form-m3-disabled-input]=\"formControl.disabled\"\n [innerHTML]=\"input.description | MultiLinePipe\"\n ></div>\n }\n</mat-form-field>\n" }]
695
+ }], propDecorators: { values: [{
696
+ type: Input,
697
+ args: [{ required: true }]
698
+ }], isButtonDisabled: [{
699
+ type: Input,
700
+ args: [{ required: true }]
701
+ }] } });
702
+
703
+ class InputIconMethods extends InputMethods {
704
+ control(input, validators) {
705
+ return new FormControl(input.value || null, validators);
706
+ }
707
+ value(value, input) {
708
+ return typeof value === 'string' && value !== '' ? value : null;
709
+ }
710
+ }
711
+
657
712
  class InputIpComponent {
658
713
  formControl = inject(INPUT_CONTROL);
659
714
  input = inject(INPUT_TYPE);
@@ -796,6 +851,59 @@ class InputMomentMethods extends InputMethods {
796
851
  }
797
852
  }
798
853
 
854
+ class InputMultiSelectComponent {
855
+ listHeight;
856
+ formControl = inject(INPUT_CONTROL);
857
+ input = inject(INPUT_TYPE);
858
+ config = inject(INPUT_CONFIG);
859
+ values;
860
+ isButtonDisabled;
861
+ ids = this.formControl.value || [];
862
+ ngOnInit() {
863
+ this.listHeight = `${this.input.listMaxHeight || 310}px`;
864
+ }
865
+ toggleValue(id) {
866
+ if (!this.ids.includes(id))
867
+ this.ids.push(id);
868
+ else
869
+ this.ids = this.ids.filter((i) => i !== id);
870
+ this.formControl.setValue(this.ids.length === 0 ? null : this.ids);
871
+ this.formControl.markAsTouched();
872
+ }
873
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: InputMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
874
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: InputMultiSelectComponent, isStandalone: true, selector: "ng-component", inputs: { values: "values", isButtonDisabled: "isButtonDisabled" }, host: { attributes: { "selector": "input-multi-select" }, properties: { "style.--listHeight": "this.listHeight" } }, ngImport: i0, template: "<mat-form-field [appearance]=\"input.appearance || config.appearance\">\n @if (formControl.invalid) { <mat-error>{{ formControl.errors | InputErrorPipe : input.type }}</mat-error> }\n\n <!-- HINT -->\n @if (input.hint) { <mat-hint>{{ input.hint }}</mat-hint> }\n\n <!-- BUTTON -->\n @if (input.button) {\n <span matIconSuffix>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"isButtonDisabled\"\n (click)=\"input.button.onClick(values)\"\n [tabIndex]=\"-1\"\n >\n <mat-icon [style.color]=\"isButtonDisabled ? undefined : input.button.color\">\n {{ input.button.icon }}\n </mat-icon>\n </button>\n </span>\n }\n\n <!-- INPUT -->\n <input matInput type=\"text\" [name]=\"input.name\" [formControl]=\"formControl\" [style.display]=\"'none !important'\" />\n <div class=\"ngx-helper-form-m3-multi-select-input\" [class.ngx-form-m3-disabled-input]=\"formControl.disabled\">\n <div class=\"title\">{{ input.title }}</div>\n <div class=\"options\">\n @for (item of input.options; track $index) {\n <div>\n <div class=\"option\" (click)=\"toggleValue(item.id)\">\n <mat-icon>{{ ids.includes(item.id) ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <div class=\"message\" [class.ngx-form-m3-en]=\"!!input.english\">{{ item.title }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- DESCRIPTION -->\n @if (input.description) {\n <div\n class=\"ngx-form-m3-input-description\"\n [class.ngx-form-m3-disabled-input]=\"formControl.disabled\"\n [innerHTML]=\"input.description | MultiLinePipe\"\n ></div>\n }\n</mat-form-field>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i3$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "pipe", type: InputErrorPipe, name: "InputErrorPipe" }, { kind: "pipe", type: MultiLinePipe, name: "MultiLinePipe" }] });
875
+ }
876
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: InputMultiSelectComponent, decorators: [{
877
+ type: Component,
878
+ args: [{ host: { selector: 'input-multi-select' }, imports: [ReactiveFormsModule, MatFormField, MatIcon, MatIconButton, MatInputModule, InputErrorPipe, MultiLinePipe], template: "<mat-form-field [appearance]=\"input.appearance || config.appearance\">\n @if (formControl.invalid) { <mat-error>{{ formControl.errors | InputErrorPipe : input.type }}</mat-error> }\n\n <!-- HINT -->\n @if (input.hint) { <mat-hint>{{ input.hint }}</mat-hint> }\n\n <!-- BUTTON -->\n @if (input.button) {\n <span matIconSuffix>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"isButtonDisabled\"\n (click)=\"input.button.onClick(values)\"\n [tabIndex]=\"-1\"\n >\n <mat-icon [style.color]=\"isButtonDisabled ? undefined : input.button.color\">\n {{ input.button.icon }}\n </mat-icon>\n </button>\n </span>\n }\n\n <!-- INPUT -->\n <input matInput type=\"text\" [name]=\"input.name\" [formControl]=\"formControl\" [style.display]=\"'none !important'\" />\n <div class=\"ngx-helper-form-m3-multi-select-input\" [class.ngx-form-m3-disabled-input]=\"formControl.disabled\">\n <div class=\"title\">{{ input.title }}</div>\n <div class=\"options\">\n @for (item of input.options; track $index) {\n <div>\n <div class=\"option\" (click)=\"toggleValue(item.id)\">\n <mat-icon>{{ ids.includes(item.id) ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>\n <div class=\"message\" [class.ngx-form-m3-en]=\"!!input.english\">{{ item.title }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- DESCRIPTION -->\n @if (input.description) {\n <div\n class=\"ngx-form-m3-input-description\"\n [class.ngx-form-m3-disabled-input]=\"formControl.disabled\"\n [innerHTML]=\"input.description | MultiLinePipe\"\n ></div>\n }\n</mat-form-field>\n" }]
879
+ }], propDecorators: { listHeight: [{
880
+ type: HostBinding,
881
+ args: ['style.--listHeight']
882
+ }], values: [{
883
+ type: Input,
884
+ args: [{ required: true }]
885
+ }], isButtonDisabled: [{
886
+ type: Input,
887
+ args: [{ required: true }]
888
+ }] } });
889
+
890
+ class InputMultiSelectMethods extends InputMethods {
891
+ control(input, validators) {
892
+ const ids = input.options.map((option) => option.id);
893
+ const value = (input.value || []).filter((id) => ids.includes(id));
894
+ if (input.minCount)
895
+ validators.push(MinCountValidator(input.minCount));
896
+ if (input.maxCount)
897
+ validators.push(MaxCountValidator(input.maxCount));
898
+ return new FormControl(value, validators);
899
+ }
900
+ value(value, input) {
901
+ const ids = input.options.map((option) => option.id);
902
+ value = (Array.isArray(value) ? value : []).filter((id) => ids.includes(id));
903
+ return value.length > 0 ? value : null;
904
+ }
905
+ }
906
+
799
907
  class InputNameComponent {
800
908
  formControl = inject(INPUT_CONTROL);
801
909
  input = inject(INPUT_TYPE);
@@ -1212,9 +1320,11 @@ const InputInfo = {
1212
1320
  DATE: { title: 'تاریخ', methods: new InputDateMethods(), component: InputDateComponent },
1213
1321
  EMAIL: { title: 'ایمیل', methods: new InputEmailMethods(), component: InputEmailComponent },
1214
1322
  FILE: { title: 'فایل', methods: new InputFileMethods(), component: InputFileComponent },
1323
+ ICON: { title: 'آیکون', methods: new InputIconMethods(), component: InputIconComponent },
1215
1324
  IP: { title: 'آدرس آی‌پی', methods: new InputIpMethods(), component: InputIpComponent },
1216
1325
  MOBILE: { title: 'موبایل', methods: new InputMobileMethods(), component: InputMobileComponent },
1217
1326
  MOMENT: { title: 'زمان', methods: new InputMomentMethods(), component: InputMomentComponent },
1327
+ 'MULTI-SELECT': { title: 'چند انتخابی', methods: new InputMultiSelectMethods(), component: InputMultiSelectComponent },
1218
1328
  NAME: { title: 'نام و نام خانوادگی', methods: new InputNameMethods(), component: InputNameComponent },
1219
1329
  NUMBER: { title: 'مقدار عددی', methods: new InputNumberMethods(), component: InputNumberComponent },
1220
1330
  PASSWORD: { title: 'کلمه عبور', methods: new InputPasswordMethods(), component: InputPasswordComponent },
@@ -1415,7 +1525,7 @@ class NgxFormComponent {
1415
1525
  }
1416
1526
  setInput(input) {
1417
1527
  const name = input.name;
1418
- const validators = input.optional || ('readonly' in input && input.readonly) ? [] : [Validators.required];
1528
+ const validators = !('optional' in input) || input.optional || ('readonly' in input && input.readonly) ? [] : [Validators.required];
1419
1529
  this.formGroup.setControl(name, InputInfo[input.type].methods.control(input, validators));
1420
1530
  }
1421
1531
  checkInputs() {