@maro-tech/form 0.0.10 → 0.0.12

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.
@@ -2900,7 +2900,7 @@ class UiActionFormModalComponent {
2900
2900
  formConfig = signal(null, ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
2901
2901
  formValue = signal({}, ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
2902
2902
  fields = computed(() => this.formConfig()?.fields ?? [], ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
2903
- submitAction = computed(() => this.formConfig()?.actions?.find((action) => String(action.type).toUpperCase() === 'ACTION_SUBMIT' || String(action.type).toUpperCase() === 'ACTION_API'), ...(ngDevMode ? [{ debugName: "submitAction" }] : /* istanbul ignore next */ []));
2903
+ submitAction = computed(() => this.formConfig()?.actions?.find((action) => this.isSubmitAction(action)), ...(ngDevMode ? [{ debugName: "submitAction" }] : /* istanbul ignore next */ []));
2904
2904
  submitLabel = computed(() => this.submitAction()?.submit || this.submitAction()?.label || 'Save', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
2905
2905
  panelClass = computed(() => ({
2906
2906
  'max-w-xl': this.size() === 'sm',
@@ -2920,8 +2920,16 @@ class UiActionFormModalComponent {
2920
2920
  onValueChange(value) {
2921
2921
  this.formValue.set(value);
2922
2922
  }
2923
- onLookupCompleted(_event) {
2924
- // Lookup results are already applied by the dynamic form and emitted via valueChange.
2923
+ onLookupCompleted(event) {
2924
+ const redirectTo = String(this.formConfig()?.redirectTo || '').trim();
2925
+ if (!redirectTo)
2926
+ return;
2927
+ const response = this.extractData(event.response) || event.response;
2928
+ const target = this.resolveTemplate(redirectTo, response);
2929
+ if (target && !target.includes('{{') && !target.includes(':')) {
2930
+ this.closed.emit();
2931
+ void this.router.navigateByUrl(target.startsWith('/') ? target : `/${target}`);
2932
+ }
2925
2933
  }
2926
2934
  onUploadingChange(uploading) {
2927
2935
  this.isUploading.set(uploading);
@@ -2969,7 +2977,15 @@ class UiActionFormModalComponent {
2969
2977
  const endpoint = `/api/crud/form/${encodeURIComponent(formId)}${recordId ? `/${encodeURIComponent(recordId)}` : ''}`;
2970
2978
  this.http.get(endpoint).subscribe({
2971
2979
  next: (response) => {
2972
- const config = response.form || response;
2980
+ const raw = response.form || response;
2981
+ const config = {
2982
+ ...raw,
2983
+ title: raw.title || raw.meta?.title,
2984
+ actions: (raw.actions || []).map((action) => ({
2985
+ ...action,
2986
+ type: this.normalizeActionType(action.type),
2987
+ })),
2988
+ };
2973
2989
  this.formConfig.set(config);
2974
2990
  this.formValue.set({ ...(config.defaults || {}), ...(config.item || {}), ...(this.initialValue() || {}), ...(recordId ? { id: recordId } : {}) });
2975
2991
  this.isLoading.set(false);
@@ -2988,6 +3004,18 @@ class UiActionFormModalComponent {
2988
3004
  return value;
2989
3005
  return `/api${value.startsWith('/') ? value : `/${value}`}`;
2990
3006
  }
3007
+ normalizeActionType(type) {
3008
+ const normalized = String(type || '').trim().toLowerCase();
3009
+ if (normalized === 'api' || normalized === 'action_api')
3010
+ return 'ACTION_API';
3011
+ if (normalized === 'submit' || normalized === 'action_submit')
3012
+ return 'ACTION_SUBMIT';
3013
+ return String(type || '');
3014
+ }
3015
+ isSubmitAction(action) {
3016
+ const type = this.normalizeActionType(action.type);
3017
+ return type === 'ACTION_API' || type === 'ACTION_SUBMIT';
3018
+ }
2991
3019
  resolveMethod(method) {
2992
3020
  return String(method || 'POST').toUpperCase();
2993
3021
  }
@@ -3002,19 +3030,23 @@ class UiActionFormModalComponent {
3002
3030
  if (!redirect)
3003
3031
  return;
3004
3032
  const data = this.extractData(response) || {};
3005
- const target = redirect.replace(/:([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, key) => {
3006
- const value = String(key).split('.').reduce((current, part) => current && typeof current === 'object' ? current[part] : undefined, data);
3007
- return value == null ? '' : String(value);
3008
- });
3033
+ const target = this.resolveTemplate(redirect, data);
3009
3034
  if (target && !target.includes(':'))
3010
3035
  void this.router.navigateByUrl(target.startsWith('/') ? target : `/${target}`);
3011
3036
  }
3037
+ resolveTemplate(template, data) {
3038
+ return String(template || '').replace(/\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}|:([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, bracesKey, colonKey) => {
3039
+ const key = String(bracesKey || colonKey || '');
3040
+ const value = key.split('.').reduce((current, part) => current && typeof current === 'object' ? current[part] : undefined, data);
3041
+ return value == null || value === '' ? '' : encodeURIComponent(String(value));
3042
+ });
3043
+ }
3012
3044
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiActionFormModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3013
- 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 items-start justify-center overflow-y-auto bg-slate-950/40 p-4 sm:items-center sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-2 flex min-h-0 max-h-[calc(100vh-1rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:my-0 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()\">Cancel</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"] }] });
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()\">Cancel</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"] }] });
3014
3046
  }
3015
3047
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiActionFormModalComponent, decorators: [{
3016
3048
  type: Component,
3017
- args: [{ selector: 'forms-action-form-modal', standalone: true, imports: [CommonModule, UiButtonComponent, UiDynamicFormComponent], template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-slate-950/40 p-4 sm:items-center sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-2 flex min-h-0 max-h-[calc(100vh-1rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:my-0 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()\">Cancel</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" }]
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()\">Cancel</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" }]
3018
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"] }] } });
3019
3051
 
3020
3052
  class UiInputComponent {