@maro-tech/form 0.0.12 → 0.0.14
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/maro-tech-form.mjs +42 -11
- package/fesm2022/maro-tech-form.mjs.map +1 -1
- package/package.json +1 -1
- package/types/maro-tech-form.d.ts +104 -93
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { signal, Injectable, inject, InjectionToken, input, output, ViewChild, Component, ChangeDetectorRef, DestroyRef, EventEmitter, ElementRef, forwardRef, booleanAttribute, HostListener, Output, Input, ChangeDetectionStrategy, ViewContainerRef, computed } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
|
+
import { map, Subject, debounceTime, distinctUntilChanged, forkJoin, Observable, from, switchMap, of } from 'rxjs';
|
|
4
5
|
import * as i1$2 from '@angular/common';
|
|
5
6
|
import { CommonModule } from '@angular/common';
|
|
6
7
|
import * as i2 from '@angular/forms';
|
|
7
8
|
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
8
|
-
import { Subject, debounceTime, distinctUntilChanged, forkJoin, Observable, from, switchMap, of } from 'rxjs';
|
|
9
9
|
import { BrowserMultiFormatOneDReader } from '@zxing/browser';
|
|
10
10
|
import { DecodeHintType, BarcodeFormat } from '@zxing/library';
|
|
11
11
|
import { UiButtonComponent } from '@maro-tech/core';
|
|
@@ -48,6 +48,11 @@ class MaroFormsApiService {
|
|
|
48
48
|
const suffix = query ? `?q=${encodeURIComponent(query)}` : '';
|
|
49
49
|
return this.http.get(`/api/suggest/${encodeURIComponent(name)}${suffix}`);
|
|
50
50
|
}
|
|
51
|
+
uploadFile(file) {
|
|
52
|
+
const formData = new FormData();
|
|
53
|
+
formData.append('file', file);
|
|
54
|
+
return this.http.post('/api/files/', formData).pipe(map((response) => ({ imageId: response.id, type: response.type })));
|
|
55
|
+
}
|
|
51
56
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MaroFormsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
52
57
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MaroFormsApiService, providedIn: 'root' });
|
|
53
58
|
}
|
|
@@ -2463,12 +2468,12 @@ class UiDynamicFormComponent {
|
|
|
2463
2468
|
const next = { ...this.value };
|
|
2464
2469
|
for (const field of this.getLeafFields()) {
|
|
2465
2470
|
const defaultValue = field.type === 'checkbox' ? false : '';
|
|
2466
|
-
const
|
|
2471
|
+
const rawValue = this.value[field.id] !== undefined
|
|
2467
2472
|
? this.value[field.id]
|
|
2468
2473
|
: field.default !== undefined
|
|
2469
2474
|
? field.default
|
|
2470
2475
|
: defaultValue;
|
|
2471
|
-
next[field.id] =
|
|
2476
|
+
next[field.id] = this.normalizeFieldValue(field, rawValue);
|
|
2472
2477
|
}
|
|
2473
2478
|
this.modelSignal.set(next);
|
|
2474
2479
|
this.formContext.setValue(next);
|
|
@@ -2491,6 +2496,13 @@ class UiDynamicFormComponent {
|
|
|
2491
2496
|
return updated;
|
|
2492
2497
|
});
|
|
2493
2498
|
}
|
|
2499
|
+
normalizeFieldValue(field, value) {
|
|
2500
|
+
if (field.type === 'photo' && field.multiple) {
|
|
2501
|
+
const values = Array.isArray(value) ? value : String(value || '').split(',');
|
|
2502
|
+
return values.map((item) => String(item || '').trim()).filter(Boolean);
|
|
2503
|
+
}
|
|
2504
|
+
return value;
|
|
2505
|
+
}
|
|
2494
2506
|
onBarcodeValueChange(field, value) {
|
|
2495
2507
|
this.updateField(field.id, value);
|
|
2496
2508
|
this.lookupErrorsSignal.update((current) => ({ ...current, [field.id]: '' }));
|
|
@@ -2890,6 +2902,7 @@ class UiActionFormModalComponent {
|
|
|
2890
2902
|
recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
|
|
2891
2903
|
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
2892
2904
|
isOpen = input(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
2905
|
+
cancelLabel = input('Відмінити', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
2893
2906
|
initialValue = input({}, ...(ngDevMode ? [{ debugName: "initialValue" }] : /* istanbul ignore next */ []));
|
|
2894
2907
|
uploadPhoto = input(null, ...(ngDevMode ? [{ debugName: "uploadPhoto" }] : /* istanbul ignore next */ []));
|
|
2895
2908
|
closed = output();
|
|
@@ -2909,6 +2922,8 @@ class UiActionFormModalComponent {
|
|
|
2909
2922
|
}), ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
|
|
2910
2923
|
http = inject(HttpClient);
|
|
2911
2924
|
router = inject(Router);
|
|
2925
|
+
formsApi = inject(MaroFormsApiService);
|
|
2926
|
+
defaultUploadPhoto = (file) => this.formsApi.uploadFile(file);
|
|
2912
2927
|
ngOnChanges(changes) {
|
|
2913
2928
|
if (this.isOpen() && ('isOpen' in changes || 'formId' in changes || 'recordId' in changes)) {
|
|
2914
2929
|
this.loadForm();
|
|
@@ -2935,10 +2950,11 @@ class UiActionFormModalComponent {
|
|
|
2935
2950
|
this.isUploading.set(uploading);
|
|
2936
2951
|
}
|
|
2937
2952
|
onFieldActionTriggered(event) {
|
|
2938
|
-
const
|
|
2953
|
+
const payload = this.normalizePayload({ ...this.formValue(), ...(event.action.payload || {}) });
|
|
2954
|
+
const endpoint = this.resolveTemplate(this.resolveEndpoint(event.action.api), payload);
|
|
2939
2955
|
if (!endpoint)
|
|
2940
2956
|
return;
|
|
2941
|
-
this.http.request(this.resolveMethod(event.action.method), endpoint, { body:
|
|
2957
|
+
this.http.request(this.resolveMethod(event.action.method), endpoint, { body: payload }).subscribe({
|
|
2942
2958
|
next: (response) => {
|
|
2943
2959
|
const data = this.extractData(response);
|
|
2944
2960
|
if (data)
|
|
@@ -2950,15 +2966,19 @@ class UiActionFormModalComponent {
|
|
|
2950
2966
|
if (this.isSubmitting())
|
|
2951
2967
|
return;
|
|
2952
2968
|
const action = this.submitAction();
|
|
2953
|
-
const
|
|
2969
|
+
const endpointTemplate = this.formConfig()?.api || action?.api;
|
|
2970
|
+
const endpoint = this.resolveEndpoint(endpointTemplate);
|
|
2954
2971
|
if (!endpoint)
|
|
2955
2972
|
return;
|
|
2956
|
-
const payload = { ...(this.initialValue() || {}), ...(action?.payload || {}), ...this.formValue() };
|
|
2973
|
+
const payload = this.normalizePayload({ ...(this.initialValue() || {}), ...(action?.payload || {}), ...this.formValue() });
|
|
2957
2974
|
const recordId = String(this.recordId() || '').trim();
|
|
2958
2975
|
if (recordId && !Object.prototype.hasOwnProperty.call(payload, 'id'))
|
|
2959
2976
|
payload['id'] = recordId;
|
|
2977
|
+
const resolvedEndpoint = this.resolveTemplate(endpoint, payload);
|
|
2978
|
+
if (!resolvedEndpoint || resolvedEndpoint.includes('{{'))
|
|
2979
|
+
return;
|
|
2960
2980
|
this.isSubmitting.set(true);
|
|
2961
|
-
this.http.request(this.resolveMethod(action?.method || (recordId ? 'PATCH' : 'POST')),
|
|
2981
|
+
this.http.request(this.resolveMethod(action?.method || (recordId ? 'PATCH' : 'POST')), resolvedEndpoint, { body: payload }).subscribe({
|
|
2962
2982
|
next: (response) => {
|
|
2963
2983
|
this.isSubmitting.set(false);
|
|
2964
2984
|
this.completed.emit(response);
|
|
@@ -3004,6 +3024,17 @@ class UiActionFormModalComponent {
|
|
|
3004
3024
|
return value;
|
|
3005
3025
|
return `/api${value.startsWith('/') ? value : `/${value}`}`;
|
|
3006
3026
|
}
|
|
3027
|
+
normalizePayload(payload) {
|
|
3028
|
+
for (const field of this.fields()) {
|
|
3029
|
+
if (field.type !== 'photo' || !field.multiple)
|
|
3030
|
+
continue;
|
|
3031
|
+
const value = payload[field.id];
|
|
3032
|
+
if (typeof value === 'string') {
|
|
3033
|
+
payload[field.id] = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
return payload;
|
|
3037
|
+
}
|
|
3007
3038
|
normalizeActionType(type) {
|
|
3008
3039
|
const normalized = String(type || '').trim().toLowerCase();
|
|
3009
3040
|
if (normalized === 'api' || normalized === 'action_api')
|
|
@@ -3042,12 +3073,12 @@ class UiActionFormModalComponent {
|
|
|
3042
3073
|
});
|
|
3043
3074
|
}
|
|
3044
3075
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiActionFormModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3045
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiActionFormModalComponent, isStandalone: true, selector: "forms-action-form-modal", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: false, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, initialValue: { classPropertyName: "initialValue", publicName: "initialValue", isSignal: true, isRequired: false, transformFunction: null }, uploadPhoto: { classPropertyName: "uploadPhoto", publicName: "uploadPhoto", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", completed: "completed" }, usesOnChanges: true, ngImport: i0, template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-slate-950/40 p-4 sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-0 flex min-h-0 max-h-[calc(100vh-2rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:max-h-[calc(100vh-3rem)]\" [ngClass]=\"panelClass()\">\n <div class=\"flex shrink-0 items-center justify-between border-b border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <h2 class=\"text-lg font-semibold\">{{ formConfig()?.title || 'Form' }}</h2>\n <button type=\"button\" class=\"text-xl text-slate-400 hover:text-slate-900\" aria-label=\"Close\" (click)=\"close()\">\u00D7</button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n @if (isLoading()) {\n <p class=\"text-sm text-slate-500\">Loading form\u2026</p>\n } @else {\n <forms-dynamic-form [fields]=\"fields()\" [value]=\"formValue()\" [showSubmit]=\"false\" [uploadPhoto]=\"uploadPhoto()\" (valueChange)=\"onValueChange($event)\" (lookupCompleted)=\"onLookupCompleted($event)\" (uploadingChange)=\"onUploadingChange($event)\" (fieldActionTriggered)=\"onFieldActionTriggered($event)\"></forms-dynamic-form>\n }\n </div>\n <div class=\"relative z-10 flex shrink-0 justify-end gap-3 border-t border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <ui-button variant=\"ghost\" (click)=\"close()\">
|
|
3076
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiActionFormModalComponent, isStandalone: true, selector: "forms-action-form-modal", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: false, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, initialValue: { classPropertyName: "initialValue", publicName: "initialValue", isSignal: true, isRequired: false, transformFunction: null }, uploadPhoto: { classPropertyName: "uploadPhoto", publicName: "uploadPhoto", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", completed: "completed" }, usesOnChanges: true, ngImport: i0, template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-slate-950/40 p-4 sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-0 flex min-h-0 max-h-[calc(100vh-2rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:max-h-[calc(100vh-3rem)]\" [ngClass]=\"panelClass()\">\n <div class=\"flex shrink-0 items-center justify-between border-b border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <h2 class=\"text-lg font-semibold\">{{ formConfig()?.title || 'Form' }}</h2>\n <button type=\"button\" class=\"text-xl text-slate-400 hover:text-slate-900\" aria-label=\"Close\" (click)=\"close()\">\u00D7</button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n @if (isLoading()) {\n <p class=\"text-sm text-slate-500\">Loading form\u2026</p>\n } @else {\n <forms-dynamic-form [fields]=\"fields()\" [value]=\"formValue()\" [showSubmit]=\"false\" [uploadPhoto]=\"uploadPhoto() || defaultUploadPhoto\" (valueChange)=\"onValueChange($event)\" (lookupCompleted)=\"onLookupCompleted($event)\" (uploadingChange)=\"onUploadingChange($event)\" (fieldActionTriggered)=\"onFieldActionTriggered($event)\"></forms-dynamic-form>\n }\n </div>\n <div class=\"relative z-10 flex shrink-0 justify-end gap-3 border-t border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <ui-button variant=\"ghost\" (click)=\"close()\">{{ cancelLabel() }}</ui-button>\n @if (submitAction()) { <ui-button variant=\"primary\" [disabled]=\"isLoading() || isSubmitting() || isUploading()\" (click)=\"confirm()\">{{ submitLabel() }}</ui-button> }\n </div>\n </div>\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "component", type: UiDynamicFormComponent, selector: "forms-dynamic-form", inputs: ["fields", "value", "errors", "submitLabel", "showSubmit", "disabled", "uploadPhoto"], outputs: ["valueChange", "lookupCompleted", "submitted", "filesChange", "uploadingChange", "fieldActionTriggered"] }] });
|
|
3046
3077
|
}
|
|
3047
3078
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiActionFormModalComponent, decorators: [{
|
|
3048
3079
|
type: Component,
|
|
3049
|
-
args: [{ selector: 'forms-action-form-modal', standalone: true, imports: [CommonModule, UiButtonComponent, UiDynamicFormComponent], template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-slate-950/40 p-4 sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-0 flex min-h-0 max-h-[calc(100vh-2rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:max-h-[calc(100vh-3rem)]\" [ngClass]=\"panelClass()\">\n <div class=\"flex shrink-0 items-center justify-between border-b border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <h2 class=\"text-lg font-semibold\">{{ formConfig()?.title || 'Form' }}</h2>\n <button type=\"button\" class=\"text-xl text-slate-400 hover:text-slate-900\" aria-label=\"Close\" (click)=\"close()\">\u00D7</button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n @if (isLoading()) {\n <p class=\"text-sm text-slate-500\">Loading form\u2026</p>\n } @else {\n <forms-dynamic-form [fields]=\"fields()\" [value]=\"formValue()\" [showSubmit]=\"false\" [uploadPhoto]=\"uploadPhoto()\" (valueChange)=\"onValueChange($event)\" (lookupCompleted)=\"onLookupCompleted($event)\" (uploadingChange)=\"onUploadingChange($event)\" (fieldActionTriggered)=\"onFieldActionTriggered($event)\"></forms-dynamic-form>\n }\n </div>\n <div class=\"relative z-10 flex shrink-0 justify-end gap-3 border-t border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <ui-button variant=\"ghost\" (click)=\"close()\">
|
|
3050
|
-
}], propDecorators: { formId: [{ type: i0.Input, args: [{ isSignal: true, alias: "formId", required: false }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], initialValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialValue", required: false }] }], uploadPhoto: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadPhoto", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], completed: [{ type: i0.Output, args: ["completed"] }] } });
|
|
3080
|
+
args: [{ selector: 'forms-action-form-modal', standalone: true, imports: [CommonModule, UiButtonComponent, UiDynamicFormComponent], template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-slate-950/40 p-4 sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-0 flex min-h-0 max-h-[calc(100vh-2rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:max-h-[calc(100vh-3rem)]\" [ngClass]=\"panelClass()\">\n <div class=\"flex shrink-0 items-center justify-between border-b border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <h2 class=\"text-lg font-semibold\">{{ formConfig()?.title || 'Form' }}</h2>\n <button type=\"button\" class=\"text-xl text-slate-400 hover:text-slate-900\" aria-label=\"Close\" (click)=\"close()\">\u00D7</button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n @if (isLoading()) {\n <p class=\"text-sm text-slate-500\">Loading form\u2026</p>\n } @else {\n <forms-dynamic-form [fields]=\"fields()\" [value]=\"formValue()\" [showSubmit]=\"false\" [uploadPhoto]=\"uploadPhoto() || defaultUploadPhoto\" (valueChange)=\"onValueChange($event)\" (lookupCompleted)=\"onLookupCompleted($event)\" (uploadingChange)=\"onUploadingChange($event)\" (fieldActionTriggered)=\"onFieldActionTriggered($event)\"></forms-dynamic-form>\n }\n </div>\n <div class=\"relative z-10 flex shrink-0 justify-end gap-3 border-t border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <ui-button variant=\"ghost\" (click)=\"close()\">{{ cancelLabel() }}</ui-button>\n @if (submitAction()) { <ui-button variant=\"primary\" [disabled]=\"isLoading() || isSubmitting() || isUploading()\" (click)=\"confirm()\">{{ submitLabel() }}</ui-button> }\n </div>\n </div>\n </div>\n}\n" }]
|
|
3081
|
+
}], propDecorators: { formId: [{ type: i0.Input, args: [{ isSignal: true, alias: "formId", required: false }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], initialValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialValue", required: false }] }], uploadPhoto: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadPhoto", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], completed: [{ type: i0.Output, args: ["completed"] }] } });
|
|
3051
3082
|
|
|
3052
3083
|
class UiInputComponent {
|
|
3053
3084
|
cdr = inject(ChangeDetectorRef);
|