@webilix/ngx-form-m3 0.0.14 → 0.0.16
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.
- package/fesm2022/webilix-ngx-form-m3.mjs +82 -10
- package/fesm2022/webilix-ngx-form-m3.mjs.map +1 -1
- package/lib/directives/form-error.directive.d.ts +1 -0
- package/lib/inputs/checkbox/input-checkbox.interface.d.ts +1 -1
- package/lib/inputs/index.d.ts +2 -0
- package/lib/inputs/input.component.d.ts +5 -0
- package/lib/inputs/multi-select/input-multi-select.component.d.ts +19 -0
- package/lib/inputs/multi-select/input-multi-select.interface.d.ts +18 -0
- package/lib/ngx-form.interface.d.ts +3 -3
- package/lib/validators/count/max-count.validator.d.ts +2 -0
- package/lib/validators/count/min-count.validator.d.ts +2 -0
- package/lib/validators/index.d.ts +2 -0
- package/ngx-form-m3.css +25 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -128,23 +128,21 @@ class FormErrorDirective {
|
|
|
128
128
|
elementRef;
|
|
129
129
|
formGroupDirective;
|
|
130
130
|
onSubmit() {
|
|
131
|
-
|
|
132
|
-
const invalidControl = this.elementRef.nativeElement.querySelector('.
|
|
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
|
-
|
|
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) => {
|
|
@@ -835,6 +851,59 @@ class InputMomentMethods extends InputMethods {
|
|
|
835
851
|
}
|
|
836
852
|
}
|
|
837
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
|
+
|
|
838
907
|
class InputNameComponent {
|
|
839
908
|
formControl = inject(INPUT_CONTROL);
|
|
840
909
|
input = inject(INPUT_TYPE);
|
|
@@ -1255,6 +1324,7 @@ const InputInfo = {
|
|
|
1255
1324
|
IP: { title: 'آدرس آیپی', methods: new InputIpMethods(), component: InputIpComponent },
|
|
1256
1325
|
MOBILE: { title: 'موبایل', methods: new InputMobileMethods(), component: InputMobileComponent },
|
|
1257
1326
|
MOMENT: { title: 'زمان', methods: new InputMomentMethods(), component: InputMomentComponent },
|
|
1327
|
+
'MULTI-SELECT': { title: 'چند انتخابی', methods: new InputMultiSelectMethods(), component: InputMultiSelectComponent },
|
|
1258
1328
|
NAME: { title: 'نام و نام خانوادگی', methods: new InputNameMethods(), component: InputNameComponent },
|
|
1259
1329
|
NUMBER: { title: 'مقدار عددی', methods: new InputNumberMethods(), component: InputNumberComponent },
|
|
1260
1330
|
PASSWORD: { title: 'کلمه عبور', methods: new InputPasswordMethods(), component: InputPasswordComponent },
|
|
@@ -1455,7 +1525,9 @@ class NgxFormComponent {
|
|
|
1455
1525
|
}
|
|
1456
1526
|
setInput(input) {
|
|
1457
1527
|
const name = input.name;
|
|
1458
|
-
const
|
|
1528
|
+
const optional = 'optional' in input && !!input.optional;
|
|
1529
|
+
const readonly = 'readonly' in input && !!input.readonly;
|
|
1530
|
+
const validators = !optional && !readonly ? [Validators.required] : [];
|
|
1459
1531
|
this.formGroup.setControl(name, InputInfo[input.type].methods.control(input, validators));
|
|
1460
1532
|
}
|
|
1461
1533
|
checkInputs() {
|