@fuentis/phoenix-ui 0.0.9-alpha.658 → 0.0.9-alpha.660

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 { Input, Component, EventEmitter, Output, ViewEncapsulation, signal, Injectable, inject, input, output, ViewChild, ChangeDetectionStrategy, ViewChildren, Pipe, DestroyRef, ChangeDetectorRef, HostBinding, ContentChild, Directive, forwardRef, HostListener, computed, InjectionToken, Optional, Inject } from '@angular/core';
2
+ import { Input, Component, EventEmitter, Output, ViewEncapsulation, signal, Injectable, inject, input, output, ViewChild, ChangeDetectionStrategy, ViewChildren, Pipe, DestroyRef, ChangeDetectorRef, HostBinding, ContentChild, Directive, forwardRef, HostListener, computed, InjectionToken, Optional, Inject, effect } from '@angular/core';
3
3
  import * as i1$2 from '@angular/common';
4
4
  import { CommonModule, DatePipe } from '@angular/common';
5
5
  import * as i1 from 'primeng/tooltip';
@@ -37,7 +37,7 @@ import * as i5 from 'primeng/inputicon';
37
37
  import { InputIconModule } from 'primeng/inputicon';
38
38
  import * as i2$5 from 'primeng/message';
39
39
  import { MessageModule } from 'primeng/message';
40
- import { Subject, debounceTime, switchMap, of, catchError, map, BehaviorSubject, merge, distinctUntilChanged, combineLatest, firstValueFrom } from 'rxjs';
40
+ import { Subject, debounceTime, switchMap, of, catchError, map, BehaviorSubject, merge, distinctUntilChanged, combineLatest, EMPTY, startWith, firstValueFrom } from 'rxjs';
41
41
  import * as i4$3 from '@angular/common/http';
42
42
  import { HttpParams } from '@angular/common/http';
43
43
  import * as i2$1 from '@angular/forms';
@@ -60,7 +60,7 @@ import { PanelModule } from 'primeng/panel';
60
60
  import { SkeletonModule } from 'primeng/skeleton';
61
61
  import * as i2$4 from 'primeng/table';
62
62
  import { TableModule } from 'primeng/table';
63
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
63
+ import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
64
64
  import * as i3$4 from 'primeng/select';
65
65
  import { SelectModule } from 'primeng/select';
66
66
  import * as i5$2 from 'primeng/multiselect';
@@ -100,6 +100,7 @@ import { Schema } from 'prosemirror-model';
100
100
  import * as i2$9 from 'primeng/fileupload';
101
101
  import { FileUploadModule } from 'primeng/fileupload';
102
102
  import * as i1$4 from '@angular/platform-browser';
103
+ import { switchMap as switchMap$1 } from 'rxjs/operators';
103
104
 
104
105
  class InnerHeaderComponent {
105
106
  title = '';
@@ -8669,6 +8670,108 @@ function clearPersistedTableState(userPrefix) {
8669
8670
  store.removeItem(key);
8670
8671
  }
8671
8672
 
8673
+ /**
8674
+ * V2 TIMEPERIOD field (signal-based, OnPush). A plain text input for a duration
8675
+ * string (e.g. `2w 4d 6h 45m`) with a calm, always-on format helper underneath.
8676
+ *
8677
+ * Unlike the legacy `phoenix-meta-timeperiod`, the format guidance is rendered as
8678
+ * neutral helper text — never a red validation error — because the pattern is
8679
+ * guidance, not a failure. The control still carries whatever validators the form
8680
+ * config attaches (for form-level validity on submit); this field just doesn't
8681
+ * shout at the user while they type. The label is owned by the field wrapper, as
8682
+ * with every other V2 field.
8683
+ */
8684
+ class MetaTimeperiodV2Component {
8685
+ /** Field key, used for the input id / data-cy. */
8686
+ key = input(...(ngDevMode ? [undefined, { debugName: "key" }] : []));
8687
+ /** Field-level disable coming from the meta config (combined with the CVA state). */
8688
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
8689
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
8690
+ /** The duration string shown in the input (the control value is this string). */
8691
+ value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : []));
8692
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
8693
+ onChange = () => { };
8694
+ onTouched = () => { };
8695
+ writeValue(v) {
8696
+ if (v === null || v === undefined || v === '') {
8697
+ this.value.set('');
8698
+ return;
8699
+ }
8700
+ this.value.set(typeof v === 'string' ? v : timePeriodFromMS(v));
8701
+ }
8702
+ registerOnChange(fn) {
8703
+ this.onChange = fn;
8704
+ }
8705
+ registerOnTouched(fn) {
8706
+ this.onTouched = fn;
8707
+ }
8708
+ setDisabledState(isDisabled) {
8709
+ this.formDisabled.set(isDisabled);
8710
+ }
8711
+ onInput(event) {
8712
+ const next = event.target?.value ?? '';
8713
+ this.value.set(next);
8714
+ this.onChange(next);
8715
+ }
8716
+ handleBlur() {
8717
+ this.onTouched();
8718
+ }
8719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaTimeperiodV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
8720
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaTimeperiodV2Component, isStandalone: true, selector: "phoenix-meta-timeperiod-v2", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
8721
+ {
8722
+ provide: NG_VALUE_ACCESSOR,
8723
+ useExisting: forwardRef(() => MetaTimeperiodV2Component),
8724
+ multi: true,
8725
+ },
8726
+ ], ngImport: i0, template: `
8727
+ <input
8728
+ pInputText
8729
+ type="text"
8730
+ class="w-full"
8731
+ [attr.id]="key() ?? null"
8732
+ [attr.data-cy]="'time-period-v2-' + (key() ?? '')"
8733
+ [value]="value()"
8734
+ [disabled]="disabled()"
8735
+ (input)="onInput($event)"
8736
+ (blur)="handleBlur()"
8737
+ />
8738
+ <!-- Guidance only once the user starts typing — not on an empty field. -->
8739
+ @if (value()) {
8740
+ <small class="block mt-1 timeperiod-v2-hint">
8741
+ <i class="pi pi-info-circle mr-1"></i>{{ 'VALIDATION_MESSAGE.VALUE_SHOULD_MATCH_TIMEPATTERN' | translate }}
8742
+ </small>
8743
+ }
8744
+ `, isInline: true, styles: [".timeperiod-v2-hint{color:var(--p-text-muted-color, #6b7280);font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8745
+ }
8746
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaTimeperiodV2Component, decorators: [{
8747
+ type: Component,
8748
+ args: [{ selector: 'phoenix-meta-timeperiod-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, InputTextModule, TranslateModule], template: `
8749
+ <input
8750
+ pInputText
8751
+ type="text"
8752
+ class="w-full"
8753
+ [attr.id]="key() ?? null"
8754
+ [attr.data-cy]="'time-period-v2-' + (key() ?? '')"
8755
+ [value]="value()"
8756
+ [disabled]="disabled()"
8757
+ (input)="onInput($event)"
8758
+ (blur)="handleBlur()"
8759
+ />
8760
+ <!-- Guidance only once the user starts typing — not on an empty field. -->
8761
+ @if (value()) {
8762
+ <small class="block mt-1 timeperiod-v2-hint">
8763
+ <i class="pi pi-info-circle mr-1"></i>{{ 'VALIDATION_MESSAGE.VALUE_SHOULD_MATCH_TIMEPATTERN' | translate }}
8764
+ </small>
8765
+ }
8766
+ `, providers: [
8767
+ {
8768
+ provide: NG_VALUE_ACCESSOR,
8769
+ useExisting: forwardRef(() => MetaTimeperiodV2Component),
8770
+ multi: true,
8771
+ },
8772
+ ], styles: [".timeperiod-v2-hint{color:var(--p-text-muted-color, #6b7280);font-size:12px}\n"] }]
8773
+ }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
8774
+
8672
8775
  class StripHtmlSafePipe {
8673
8776
  transform(value) {
8674
8777
  if (value === null || value === undefined)
@@ -8933,120 +9036,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8933
9036
  args: [{ required: true }]
8934
9037
  }] } });
8935
9038
 
9039
+ /**
9040
+ * V2 ASSIGN field — signal-based ControlValueAccessor (OnPush, no manual CD).
9041
+ * Holds the full selected assignee (downstream fields often need more than uuid);
9042
+ * a picker dialog returns the chosen row.
9043
+ */
8936
9044
  class MetaAssignResponsibleV2Component {
8937
- /**
8938
- * List of available assignees used as table data inside the selection dialog.
8939
- * This replaces legacy `control.configuration.items` access and keeps the CVA independent
8940
- * from meta-form internals.
8941
- */
8942
- items = [];
8943
- /**
8944
- * Translation key for the dialog header title.
8945
- * Kept as an input so different contexts can reuse this field with a custom title.
8946
- */
8947
- dialogHeaderKey = 'LABELS.ASSIGN_RESPONSIBLE';
9045
+ /** Selectable assignees shown in the picker dialog. */
9046
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
9047
+ /** i18n key for the dialog header title. */
9048
+ dialogHeaderKey = input('LABELS.ASSIGN_RESPONSIBLE', ...(ngDevMode ? [{ debugName: "dialogHeaderKey" }] : []));
8948
9049
  translate = inject(TranslateService);
8949
9050
  dialog = inject(DialogService);
8950
- /**
8951
- * Currently selected assignee value bound to the parent form control.
8952
- * We store the full object (row) because downstream fields often need more than uuid.
8953
- */
8954
- value = null;
8955
- /**
8956
- * Disabled state propagated from Angular forms via `setDisabledState`.
8957
- * (If you later add a field-level `@Input() disable`, combine them like in other CVAs.)
8958
- */
8959
- disabled = false;
8960
- /**
8961
- * CVA callback invoked when the value changes (selection/clear).
8962
- */
9051
+ /** The full selected assignee bound to the parent control. */
9052
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9053
+ /** Disabled state from Angular Forms (this field has no separate field-level disable). */
9054
+ disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
8963
9055
  onChange = () => { };
8964
- /**
8965
- * CVA callback invoked when the control is marked as touched (user interaction).
8966
- * Standard: call on meaningful interactions (open dialog, select, clear).
8967
- */
8968
9056
  onTouched = () => { };
8969
- /**
8970
- * Called by Angular forms when the model value changes programmatically.
8971
- * Keep it side-effect free: do not call `onChange`/`onTouched` from here.
8972
- */
8973
9057
  writeValue(value) {
8974
- this.value = value ?? null;
9058
+ this.value.set(value ?? null);
8975
9059
  }
8976
- /**
8977
- * Registers the callback that should be called when the component updates the value.
8978
- */
8979
9060
  registerOnChange(fn) {
8980
9061
  this.onChange = fn;
8981
9062
  }
8982
- /**
8983
- * Registers the callback that should be called when the control becomes "touched".
8984
- */
8985
9063
  registerOnTouched(fn) {
8986
9064
  this.onTouched = fn;
8987
9065
  }
8988
- /**
8989
- * Receives disabled state from Angular forms and updates local state.
8990
- */
8991
9066
  setDisabledState(isDisabled) {
8992
- this.disabled = isDisabled;
9067
+ this.disabled.set(isDisabled);
8993
9068
  }
8994
- /**
8995
- * Clears current assignee value.
8996
- * - emits `null`
8997
- * - marks as touched (user action)
8998
- */
9069
+ /** Clears the assignee (emits null + touched). */
8999
9070
  clear() {
9000
- if (this.disabled)
9071
+ if (this.disabled())
9001
9072
  return;
9002
- // prevent redundant emits
9003
- if (this.value === null) {
9073
+ if (this.value() === null) {
9004
9074
  this.onTouched();
9005
9075
  return;
9006
9076
  }
9007
- this.value = null;
9077
+ this.value.set(null);
9008
9078
  this.onChange(null);
9009
9079
  this.onTouched();
9010
9080
  }
9011
9081
  /**
9012
- * Opens object selection dialog for choosing a new assignee.
9013
- * The dialog renders a generic table and returns the selected row on close.
9014
- *
9015
- * Standard for CVA:
9016
- * - mark as touched when user opens the picker (interaction started)
9017
- * - emit onChange only when a row is actually selected
9082
+ * Opens the picker dialog and applies the selected row. Touched on open
9083
+ * (interaction started); onChange only when a row is actually selected.
9018
9084
  */
9019
9085
  openDialog() {
9020
- if (this.disabled)
9086
+ if (this.disabled())
9021
9087
  return;
9022
9088
  this.onTouched();
9023
9089
  const ref = this.dialog.open(ObjectItemDialogComponent, {
9024
- header: this.translate.instant(this.dialogHeaderKey),
9090
+ header: this.translate.instant(this.dialogHeaderKey()),
9025
9091
  width: '700px',
9026
9092
  modal: true,
9027
9093
  data: {
9028
- tableData: this.items,
9094
+ tableData: this.items(),
9029
9095
  columns: [
9030
- {
9031
- field: 'name',
9032
- header: 'LABELS.NAME',
9033
- columnType: tableColumnType.TEXT,
9034
- },
9035
- {
9036
- field: 'function',
9037
- header: 'LABELS.FUNCTION',
9038
- columnType: tableColumnType.TEXT,
9039
- },
9040
- {
9041
- field: 'phone',
9042
- header: 'LABELS.PHONE',
9043
- columnType: tableColumnType.TEXT,
9044
- },
9045
- {
9046
- field: 'email',
9047
- header: 'LABELS.EMAIL',
9048
- columnType: tableColumnType.TEXT,
9049
- },
9096
+ { field: 'name', header: 'LABELS.NAME', columnType: tableColumnType.TEXT },
9097
+ { field: 'function', header: 'LABELS.FUNCTION', columnType: tableColumnType.TEXT },
9098
+ { field: 'phone', header: 'LABELS.PHONE', columnType: tableColumnType.TEXT },
9099
+ { field: 'email', header: 'LABELS.EMAIL', columnType: tableColumnType.TEXT },
9050
9100
  ],
9051
9101
  },
9052
9102
  contentStyle: { overflow: 'auto' },
@@ -9056,47 +9106,46 @@ class MetaAssignResponsibleV2Component {
9056
9106
  ref?.onClose.subscribe((response) => {
9057
9107
  if (!response)
9058
9108
  return;
9059
- // prevent redundant emits (same selection)
9060
- const same = (this.value?.uuid ?? null) === (response?.uuid ?? null) &&
9061
- JSON.stringify(this.value ?? null) === JSON.stringify(response ?? null);
9062
- this.value = response;
9063
- if (!same) {
9109
+ const current = this.value();
9110
+ const same = (current?.uuid ?? null) === (response?.uuid ?? null) &&
9111
+ JSON.stringify(current ?? null) === JSON.stringify(response ?? null);
9112
+ this.value.set(response);
9113
+ if (!same)
9064
9114
  this.onChange(response);
9065
- }
9066
9115
  this.onTouched();
9067
9116
  });
9068
9117
  }
9069
9118
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaAssignResponsibleV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
9070
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaAssignResponsibleV2Component, isStandalone: true, selector: "phoenix-meta-assign-responsible-v2", inputs: { items: "items", dialogHeaderKey: "dialogHeaderKey" }, providers: [
9119
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaAssignResponsibleV2Component, isStandalone: true, selector: "phoenix-meta-assign-responsible-v2", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, dialogHeaderKey: { classPropertyName: "dialogHeaderKey", publicName: "dialogHeaderKey", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
9071
9120
  {
9072
9121
  provide: NG_VALUE_ACCESSOR,
9073
9122
  useExisting: forwardRef(() => MetaAssignResponsibleV2Component),
9074
9123
  multi: true,
9075
9124
  },
9076
9125
  ], ngImport: i0, template: `
9077
- @if (value?.uuid) {
9126
+ @if (value()?.uuid) {
9078
9127
  <div class="flex align-items-center">
9079
9128
  <div>
9080
9129
  <p-button
9081
9130
  [rounded]="true"
9082
9131
  [text]="true"
9083
9132
  (onClick)="op.toggle($event)"
9084
- [disabled]="disabled"
9133
+ [disabled]="disabled()"
9085
9134
  >
9086
9135
  <div class="person-wrap">
9087
9136
  <div class="person-avatar">
9088
- {{ (value?.name ?? '').toUpperCase().charAt(0) }}
9137
+ {{ (value()?.name ?? '').toUpperCase().charAt(0) }}
9089
9138
  </div>
9090
9139
  <div>
9091
9140
  <p
9092
9141
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9093
9142
  >
9094
- {{ value?.name ?? '--' }}
9143
+ {{ value()?.name ?? '--' }}
9095
9144
  </p>
9096
9145
  <p
9097
9146
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9098
9147
  >
9099
- {{ value?.function ?? '--' }}
9148
+ {{ value()?.function ?? '--' }}
9100
9149
  </p>
9101
9150
  </div>
9102
9151
  </div>
@@ -9107,28 +9156,28 @@ class MetaAssignResponsibleV2Component {
9107
9156
  <span
9108
9157
  class="block mb-2"
9109
9158
  pTooltip="{{
9110
- (value?.email?.length ?? 0) > 25 ? value?.email : ''
9159
+ (value()?.email?.length ?? 0) > 25 ? value()?.email : ''
9111
9160
  }}"
9112
9161
  tooltipPosition="right"
9113
9162
  >
9114
9163
  <i class="pi pi-envelope mr-1 text-500"></i>
9115
9164
  {{
9116
- (value?.email?.length ?? 0) > 25
9117
- ? value?.email?.slice(0, 25) + '...'
9118
- : (value?.email ?? '--')
9165
+ (value()?.email?.length ?? 0) > 25
9166
+ ? value()?.email?.slice(0, 25) + '...'
9167
+ : (value()?.email ?? '--')
9119
9168
  }}
9120
9169
  </span>
9121
9170
  <p
9122
9171
  pTooltip="{{
9123
- (value?.phone?.length ?? 0) > 25 ? value?.phone : ''
9172
+ (value()?.phone?.length ?? 0) > 25 ? value()?.phone : ''
9124
9173
  }}"
9125
9174
  tooltipPosition="right"
9126
9175
  >
9127
9176
  <i class="pi pi-phone mr-1 text-500"></i>
9128
9177
  {{
9129
- (value?.phone?.length ?? 0) > 25
9130
- ? value?.phone?.slice(0, 25) + '...'
9131
- : (value?.phone ?? '--')
9178
+ (value()?.phone?.length ?? 0) > 25
9179
+ ? value()?.phone?.slice(0, 25) + '...'
9180
+ : (value()?.phone ?? '--')
9132
9181
  }}
9133
9182
  </p>
9134
9183
  </div>
@@ -9140,7 +9189,7 @@ class MetaAssignResponsibleV2Component {
9140
9189
  styleClass="p-button-sm mt-1"
9141
9190
  type="button"
9142
9191
  [text]="true"
9143
- [disabled]="disabled"
9192
+ [disabled]="disabled()"
9144
9193
  [label]="'ACTION.REASSIGN' | translate"
9145
9194
  (click)="op.hide(); openDialog()"
9146
9195
  ></p-button>
@@ -9148,7 +9197,7 @@ class MetaAssignResponsibleV2Component {
9148
9197
  <p-button
9149
9198
  type="button"
9150
9199
  icon="pi pi-times"
9151
- [disabled]="disabled"
9200
+ [disabled]="disabled()"
9152
9201
  (click)="op.hide(); clear()"
9153
9202
  [text]="true"
9154
9203
  styleClass="p-button-danger p-button-sm mt-1 ml-2"
@@ -9163,14 +9212,14 @@ class MetaAssignResponsibleV2Component {
9163
9212
  [label]="'ACTION.ASSIGN' | translate"
9164
9213
  (click)="openDialog()"
9165
9214
  [text]="true"
9166
- [disabled]="disabled"
9215
+ [disabled]="disabled()"
9167
9216
  ></p-button>
9168
9217
  }
9169
- `, isInline: true, styles: ["::ng-deep .p-popover-content{padding:0!important}.person-wrap{display:flex;align-items:center;margin-left:-6px}.person-wrap p{margin:0}.person-avatar{display:flex;justify-content:center;align-items:center;width:28px;height:28px;min-width:28px;min-height:28px;margin-right:5px;background-color:#e94260;color:#fff;border-radius:50%;font-size:1rem}.person-details{border-top:1px solid #e0e0e0;padding:10px;width:200px}.person-details p{margin:0;display:flex;align-items:center}.person-details i{color:#e94260;padding-right:5px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }] });
9218
+ `, isInline: true, styles: ["::ng-deep .p-popover-content{padding:0!important}.person-wrap{display:flex;align-items:center;margin-left:-6px}.person-wrap p{margin:0}.person-avatar{display:flex;justify-content:center;align-items:center;width:28px;height:28px;min-width:28px;min-height:28px;margin-right:5px;background-color:#e94260;color:#fff;border-radius:50%;font-size:1rem}.person-details{border-top:1px solid #e0e0e0;padding:10px;width:200px}.person-details p{margin:0;display:flex;align-items:center}.person-details i{color:#e94260;padding-right:5px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9170
9219
  }
9171
9220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaAssignResponsibleV2Component, decorators: [{
9172
9221
  type: Component,
9173
- args: [{ selector: 'phoenix-meta-assign-responsible-v2', standalone: true, imports: [
9222
+ args: [{ selector: 'phoenix-meta-assign-responsible-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
9174
9223
  CommonModule,
9175
9224
  ButtonModule,
9176
9225
  PopoverModule,
@@ -9183,29 +9232,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9183
9232
  multi: true,
9184
9233
  },
9185
9234
  ], template: `
9186
- @if (value?.uuid) {
9235
+ @if (value()?.uuid) {
9187
9236
  <div class="flex align-items-center">
9188
9237
  <div>
9189
9238
  <p-button
9190
9239
  [rounded]="true"
9191
9240
  [text]="true"
9192
9241
  (onClick)="op.toggle($event)"
9193
- [disabled]="disabled"
9242
+ [disabled]="disabled()"
9194
9243
  >
9195
9244
  <div class="person-wrap">
9196
9245
  <div class="person-avatar">
9197
- {{ (value?.name ?? '').toUpperCase().charAt(0) }}
9246
+ {{ (value()?.name ?? '').toUpperCase().charAt(0) }}
9198
9247
  </div>
9199
9248
  <div>
9200
9249
  <p
9201
9250
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9202
9251
  >
9203
- {{ value?.name ?? '--' }}
9252
+ {{ value()?.name ?? '--' }}
9204
9253
  </p>
9205
9254
  <p
9206
9255
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9207
9256
  >
9208
- {{ value?.function ?? '--' }}
9257
+ {{ value()?.function ?? '--' }}
9209
9258
  </p>
9210
9259
  </div>
9211
9260
  </div>
@@ -9216,28 +9265,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9216
9265
  <span
9217
9266
  class="block mb-2"
9218
9267
  pTooltip="{{
9219
- (value?.email?.length ?? 0) > 25 ? value?.email : ''
9268
+ (value()?.email?.length ?? 0) > 25 ? value()?.email : ''
9220
9269
  }}"
9221
9270
  tooltipPosition="right"
9222
9271
  >
9223
9272
  <i class="pi pi-envelope mr-1 text-500"></i>
9224
9273
  {{
9225
- (value?.email?.length ?? 0) > 25
9226
- ? value?.email?.slice(0, 25) + '...'
9227
- : (value?.email ?? '--')
9274
+ (value()?.email?.length ?? 0) > 25
9275
+ ? value()?.email?.slice(0, 25) + '...'
9276
+ : (value()?.email ?? '--')
9228
9277
  }}
9229
9278
  </span>
9230
9279
  <p
9231
9280
  pTooltip="{{
9232
- (value?.phone?.length ?? 0) > 25 ? value?.phone : ''
9281
+ (value()?.phone?.length ?? 0) > 25 ? value()?.phone : ''
9233
9282
  }}"
9234
9283
  tooltipPosition="right"
9235
9284
  >
9236
9285
  <i class="pi pi-phone mr-1 text-500"></i>
9237
9286
  {{
9238
- (value?.phone?.length ?? 0) > 25
9239
- ? value?.phone?.slice(0, 25) + '...'
9240
- : (value?.phone ?? '--')
9287
+ (value()?.phone?.length ?? 0) > 25
9288
+ ? value()?.phone?.slice(0, 25) + '...'
9289
+ : (value()?.phone ?? '--')
9241
9290
  }}
9242
9291
  </p>
9243
9292
  </div>
@@ -9249,7 +9298,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9249
9298
  styleClass="p-button-sm mt-1"
9250
9299
  type="button"
9251
9300
  [text]="true"
9252
- [disabled]="disabled"
9301
+ [disabled]="disabled()"
9253
9302
  [label]="'ACTION.REASSIGN' | translate"
9254
9303
  (click)="op.hide(); openDialog()"
9255
9304
  ></p-button>
@@ -9257,7 +9306,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9257
9306
  <p-button
9258
9307
  type="button"
9259
9308
  icon="pi pi-times"
9260
- [disabled]="disabled"
9309
+ [disabled]="disabled()"
9261
9310
  (click)="op.hide(); clear()"
9262
9311
  [text]="true"
9263
9312
  styleClass="p-button-danger p-button-sm mt-1 ml-2"
@@ -9272,109 +9321,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9272
9321
  [label]="'ACTION.ASSIGN' | translate"
9273
9322
  (click)="openDialog()"
9274
9323
  [text]="true"
9275
- [disabled]="disabled"
9324
+ [disabled]="disabled()"
9276
9325
  ></p-button>
9277
9326
  }
9278
9327
  `, styles: ["::ng-deep .p-popover-content{padding:0!important}.person-wrap{display:flex;align-items:center;margin-left:-6px}.person-wrap p{margin:0}.person-avatar{display:flex;justify-content:center;align-items:center;width:28px;height:28px;min-width:28px;min-height:28px;margin-right:5px;background-color:#e94260;color:#fff;border-radius:50%;font-size:1rem}.person-details{border-top:1px solid #e0e0e0;padding:10px;width:200px}.person-details p{margin:0;display:flex;align-items:center}.person-details i{color:#e94260;padding-right:5px}\n"] }]
9279
- }], propDecorators: { items: [{
9280
- type: Input
9281
- }], dialogHeaderKey: [{
9282
- type: Input
9283
- }] } });
9328
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], dialogHeaderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dialogHeaderKey", required: false }] }] } });
9284
9329
 
9330
+ /**
9331
+ * V2 COLOR field — signal-based ControlValueAccessor (OnPush, no manual CD).
9332
+ * Stores a HEX string (or null); the template reads the value/disabled signals.
9333
+ */
9285
9334
  class MetaColorPickerV2Component {
9286
- /**
9287
- * Optional external disable flag (field-level disable coming from meta config).
9288
- * This is combined with the disabled state coming from Angular Forms (CVA).
9289
- */
9290
- disable = false;
9291
- cdr = inject(ChangeDetectorRef);
9292
- /**
9293
- * Currently selected color value.
9294
- * Expected format: HEX string (e.g. "#ff00aa") or null.
9295
- */
9296
- value = null;
9297
- /**
9298
- * CVA callback invoked when the value changes.
9299
- */
9335
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9336
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9337
+ /** Selected colour: HEX string (e.g. "#ff00aa") or null. */
9338
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9339
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9340
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9300
9341
  onChange = () => { };
9301
- /**
9302
- * CVA callback invoked when the control is marked as touched.
9303
- * Standard: call on blur / close, not on every value change.
9304
- */
9305
9342
  onTouched = () => { };
9306
- /**
9307
- * Disabled state coming from Angular Forms (ControlValueAccessor).
9308
- */
9309
- isDisabled = false;
9310
- /**
9311
- * Final disabled state combining:
9312
- * - form-level disabled state (CVA)
9313
- * - field-level disable flag (input)
9314
- */
9315
- get disabled() {
9316
- return this.disable || this.isDisabled;
9317
- }
9318
- /**
9319
- * Writes a new value from the parent form control into the component.
9320
- * Keep it idempotent and UI-safe.
9321
- */
9322
9343
  writeValue(v) {
9323
- this.value = this.normalizeHex(v);
9324
- this.cdr.markForCheck();
9344
+ this.value.set(this.normalizeHex(v));
9325
9345
  }
9326
- /**
9327
- * Registers callback that is triggered when the value changes.
9328
- */
9329
9346
  registerOnChange(fn) {
9330
9347
  this.onChange = fn;
9331
9348
  }
9332
- /**
9333
- * Registers callback that is triggered when the control is touched.
9334
- */
9335
9349
  registerOnTouched(fn) {
9336
9350
  this.onTouched = fn;
9337
9351
  }
9338
- /**
9339
- * Receives disabled state from Angular Forms and updates local state.
9340
- */
9341
9352
  setDisabledState(isDisabled) {
9342
- this.isDisabled = isDisabled;
9343
- this.cdr.markForCheck();
9353
+ this.formDisabled.set(isDisabled);
9344
9354
  }
9345
- /**
9346
- * Handler for PrimeNG color picker change event.
9347
- * Propagates the currently selected color value to the parent form control.
9348
- *
9349
- * Note: we intentionally do NOT call onTouched here (our standard is: touched on blur).
9350
- */
9351
- onPickerChange() {
9352
- if (this.disabled)
9353
- return;
9354
- const next = this.normalizeHex(this.value);
9355
- // prevent redundant emits (useful when PrimeNG fires multiple times)
9356
- if (next === this.value) {
9357
- this.cdr.markForCheck();
9355
+ /** Touched is emitted on blur (not on value change). */
9356
+ onPickerChange(next) {
9357
+ if (this.disabled())
9358
9358
  return;
9359
- }
9360
- this.value = next;
9361
- this.onChange(next);
9362
- this.cdr.markForCheck();
9359
+ const normalized = this.normalizeHex(next);
9360
+ this.value.set(normalized);
9361
+ this.onChange(normalized);
9363
9362
  }
9364
- /**
9365
- * Marks control as touched when user leaves the component.
9366
- * (Matches "touched on blur" CVA guideline.)
9367
- */
9368
9363
  handleBlur() {
9369
- if (this.disabled)
9364
+ if (this.disabled())
9370
9365
  return;
9371
9366
  this.onTouched();
9372
9367
  }
9373
- /**
9374
- * Normalizes incoming values to a safe HEX string or null.
9375
- * - accepts "#RRGGBB" / "#RGB" / "RRGGBB"
9376
- * - returns null for empty/invalid inputs
9377
- */
9368
+ /** Normalizes to "#rrggbb"/"#rgb" (adds '#', lowercases) or null. */
9378
9369
  normalizeHex(v) {
9379
9370
  if (v === null || v === undefined)
9380
9371
  return null;
@@ -9386,7 +9377,7 @@ class MetaColorPickerV2Component {
9386
9377
  return ok ? withHash.toLowerCase() : null;
9387
9378
  }
9388
9379
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaColorPickerV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
9389
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: MetaColorPickerV2Component, isStandalone: true, selector: "phoenix-meta-color-picker-v2", inputs: { disable: "disable" }, providers: [
9380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: MetaColorPickerV2Component, isStandalone: true, selector: "phoenix-meta-color-picker-v2", inputs: { disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
9390
9381
  {
9391
9382
  provide: NG_VALUE_ACCESSOR,
9392
9383
  useExisting: forwardRef(() => MetaColorPickerV2Component),
@@ -9395,10 +9386,10 @@ class MetaColorPickerV2Component {
9395
9386
  ], ngImport: i0, template: `
9396
9387
  <p-colorPicker
9397
9388
  class="color-swatch"
9398
- [(ngModel)]="value"
9399
- (onChange)="onPickerChange()"
9389
+ [ngModel]="value()"
9390
+ (ngModelChange)="onPickerChange($event)"
9400
9391
  (onBlur)="handleBlur()"
9401
- [disabled]="disabled"
9392
+ [disabled]="disabled()"
9402
9393
  [appendTo]="'body'"
9403
9394
  ></p-colorPicker>
9404
9395
  `, isInline: true, styles: [":host ::ng-deep .color-swatch.p-colorpicker{width:35px;height:35px;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important}:host ::ng-deep .color-swatch .p-colorpicker-preview{width:35px!important;height:35px!important;border:none!important;border-radius:6px!important;box-shadow:none!important}:host ::ng-deep .color-swatch .p-colorpicker-preview:focus,:host ::ng-deep .color-swatch .p-colorpicker-preview:focus-visible{outline:none!important;box-shadow:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$7.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "inline", "format", "tabindex", "inputId", "autoZIndex", "showTransitionOptions", "hideTransitionOptions", "autofocus", "defaultColor", "appendTo"], outputs: ["onChange", "onShow", "onHide"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -9408,10 +9399,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9408
9399
  args: [{ selector: 'phoenix-meta-color-picker-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ColorPickerModule], template: `
9409
9400
  <p-colorPicker
9410
9401
  class="color-swatch"
9411
- [(ngModel)]="value"
9412
- (onChange)="onPickerChange()"
9402
+ [ngModel]="value()"
9403
+ (ngModelChange)="onPickerChange($event)"
9413
9404
  (onBlur)="handleBlur()"
9414
- [disabled]="disabled"
9405
+ [disabled]="disabled()"
9415
9406
  [appendTo]="'body'"
9416
9407
  ></p-colorPicker>
9417
9408
  `, providers: [
@@ -9421,128 +9412,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9421
9412
  multi: true,
9422
9413
  },
9423
9414
  ], styles: [":host ::ng-deep .color-swatch.p-colorpicker{width:35px;height:35px;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important}:host ::ng-deep .color-swatch .p-colorpicker-preview{width:35px!important;height:35px!important;border:none!important;border-radius:6px!important;box-shadow:none!important}:host ::ng-deep .color-swatch .p-colorpicker-preview:focus,:host ::ng-deep .color-swatch .p-colorpicker-preview:focus-visible{outline:none!important;box-shadow:none!important}\n"] }]
9424
- }], propDecorators: { disable: [{
9425
- type: Input
9426
- }] } });
9415
+ }], propDecorators: { disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9427
9416
 
9417
+ /**
9418
+ * V2 CHECKBOX_COLOR field — signal-based ControlValueAccessor (OnPush, no manual
9419
+ * CD). A colour swatch that opens a grid popover; value + focus highlight live in
9420
+ * signals.
9421
+ */
9428
9422
  class MetaCheckboxColorPickerV2Component {
9429
- /**
9430
- * 2D array of color values used to render the color grid in the popover.
9431
- * Example:
9432
- * [
9433
- * ['#ff0000', '#00ff00', '#0000ff'],
9434
- * ['#facc15', '#22c55e', '#0ea5e9']
9435
- * ]
9436
- */
9437
- options = [];
9438
- /**
9439
- * External disable flag (e.g. meta-form field-level disable).
9440
- * This is combined with CVA disabled state.
9441
- */
9442
- disable = false;
9443
- cdr = inject(ChangeDetectorRef);
9444
- /**
9445
- * Currently selected color value bound to the parent form control.
9446
- */
9447
- value = null;
9448
- /**
9449
- * Color currently focused/hovered in the UI.
9450
- * Used only for visual outline highlight in the picker grid.
9451
- */
9452
- focusedColor = null;
9453
- /**
9454
- * Disabled state coming from Angular Forms (ControlValueAccessor).
9455
- */
9456
- isDisabled = false;
9457
- /**
9458
- * CVA callback invoked when the value changes.
9459
- */
9423
+ /** 2D grid of colours rendered in the popover. */
9424
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
9425
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9426
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9427
+ /** Selected colour bound to the parent control. */
9428
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9429
+ /** Colour currently focused/hovered in the grid (visual outline only). */
9430
+ focusedColor = signal(null, ...(ngDevMode ? [{ debugName: "focusedColor" }] : []));
9431
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9432
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9460
9433
  onChange = () => { };
9461
- /**
9462
- * CVA callback invoked when the control is marked as touched.
9463
- * Standard: touched on "open/blur/close" actions, not necessarily on hover.
9464
- */
9465
9434
  onTouched = () => { };
9466
- /**
9467
- * Final disabled state combining:
9468
- * - form-level disabled state (CVA)
9469
- * - field-level disable flag (input)
9470
- */
9471
- get disabled() {
9472
- return this.disable || this.isDisabled;
9473
- }
9474
- // ----- ControlValueAccessor implementation -----
9475
- /**
9476
- * Writes a new value from the parent form into the component.
9477
- * Keeps internal value and focusedColor in sync for correct UI outline.
9478
- */
9479
9435
  writeValue(v) {
9480
- this.value = this.normalizeColor(v);
9481
- this.focusedColor = this.value;
9482
- this.cdr.markForCheck();
9436
+ const next = this.normalizeColor(v);
9437
+ this.value.set(next);
9438
+ this.focusedColor.set(next);
9483
9439
  }
9484
- /**
9485
- * Registers callback that is triggered when the value changes.
9486
- */
9487
9440
  registerOnChange(fn) {
9488
9441
  this.onChange = fn;
9489
9442
  }
9490
- /**
9491
- * Registers callback that is triggered when the control is touched.
9492
- */
9493
9443
  registerOnTouched(fn) {
9494
9444
  this.onTouched = fn;
9495
9445
  }
9496
- /**
9497
- * Receives disabled state from Angular Forms and updates local state.
9498
- */
9499
9446
  setDisabledState(isDisabled) {
9500
- this.isDisabled = isDisabled;
9501
- this.cdr.markForCheck();
9447
+ this.formDisabled.set(isDisabled);
9502
9448
  }
9503
- // ----- UI handlers -----
9504
- /**
9505
- * Toggles the popover visibility when the selected-color button is clicked.
9506
- * We mark as touched because user interacted with the control.
9507
- */
9508
9449
  toggle(popover, ev) {
9509
- if (this.disabled)
9450
+ if (this.disabled())
9510
9451
  return;
9511
9452
  this.onTouched();
9512
9453
  popover.toggle(ev);
9513
9454
  }
9514
- /**
9515
- * Handles color selection from the grid:
9516
- * - updates internal value
9517
- * - propagates value to parent form (onChange)
9518
- * - marks control as touched (selection is a meaningful interaction)
9519
- * - closes the popover
9520
- */
9521
9455
  select(color, popover) {
9522
- if (this.disabled)
9456
+ if (this.disabled())
9523
9457
  return;
9524
9458
  const next = this.normalizeColor(color);
9525
- // prevent redundant emits
9526
- if (next === this.value) {
9527
- this.focusedColor = next;
9528
- this.onTouched();
9529
- this.cdr.markForCheck();
9530
- popover.hide();
9531
- return;
9459
+ this.focusedColor.set(next);
9460
+ if (next !== this.value()) {
9461
+ this.value.set(next);
9462
+ this.onChange(next);
9532
9463
  }
9533
- this.focusedColor = next;
9534
- this.value = next;
9535
- this.onChange(next);
9536
9464
  this.onTouched();
9537
- this.cdr.markForCheck();
9538
9465
  popover.hide();
9539
9466
  }
9540
9467
  /**
9541
- * Normalizes incoming values to a safe CSS color string or null.
9542
- * For this component we keep it permissive:
9543
- * - accepts "#RRGGBB" / "#RGB"
9544
- * - also allows any non-empty string (in case someone passes "red" or "var(--x)")
9545
- * - returns null for empty values
9468
+ * Normalizes hex ("#rgb"/"#rrggbb", with/without '#') and passes through other
9469
+ * CSS colours ("red", "var(--x)"); empty null.
9546
9470
  */
9547
9471
  normalizeColor(v) {
9548
9472
  if (v === null || v === undefined)
@@ -9550,15 +9474,13 @@ class MetaCheckboxColorPickerV2Component {
9550
9474
  const s = String(v).trim();
9551
9475
  if (!s)
9552
9476
  return null;
9553
- // normalize common hex values (with/without '#')
9554
9477
  const withHash = s.startsWith('#') ? s : `#${s}`;
9555
9478
  if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(withHash))
9556
9479
  return withHash.toLowerCase();
9557
- // fallback: allow CSS colors (e.g. "red") or CSS vars
9558
9480
  return s;
9559
9481
  }
9560
9482
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaCheckboxColorPickerV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
9561
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaCheckboxColorPickerV2Component, isStandalone: true, selector: "phoenix-meta-checkbox-color-picker-v2", inputs: { options: "options", disable: "disable" }, providers: [
9483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaCheckboxColorPickerV2Component, isStandalone: true, selector: "phoenix-meta-checkbox-color-picker-v2", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
9562
9484
  {
9563
9485
  provide: NG_VALUE_ACCESSOR,
9564
9486
  useExisting: forwardRef(() => MetaCheckboxColorPickerV2Component),
@@ -9567,18 +9489,18 @@ class MetaCheckboxColorPickerV2Component {
9567
9489
  ], ngImport: i0, template: `
9568
9490
  <p-popover #popover>
9569
9491
  <div class="color-picker">
9570
- @for (row of options; track $index; let last = $last) {
9492
+ @for (row of options(); track $index; let last = $last) {
9571
9493
  <div class="color-row" [class.mb-2]="!last">
9572
9494
  @for (color of row; track color) {
9573
9495
  <button
9574
9496
  type="button"
9575
9497
  class="color-box"
9576
- [disabled]="disabled"
9498
+ [disabled]="disabled()"
9577
9499
  [style.backgroundColor]="color"
9578
- [style.outline]="focusedColor === color ? '3px solid ' + color : 'none'"
9579
- [style.outlineOffset]="focusedColor === color ? '3px' : '0'"
9500
+ [style.outline]="focusedColor() === color ? '3px solid ' + color : 'none'"
9501
+ [style.outlineOffset]="focusedColor() === color ? '3px' : '0'"
9580
9502
  (click)="select(color, popover)"
9581
- (mouseenter)="focusedColor = color"
9503
+ (mouseenter)="focusedColor.set(color)"
9582
9504
  ></button>
9583
9505
  }
9584
9506
  </div>
@@ -9589,8 +9511,8 @@ class MetaCheckboxColorPickerV2Component {
9589
9511
  <button
9590
9512
  type="button"
9591
9513
  class="selected-color"
9592
- [disabled]="disabled"
9593
- [style.backgroundColor]="value || 'transparent'"
9514
+ [disabled]="disabled()"
9515
+ [style.backgroundColor]="value() || 'transparent'"
9594
9516
  (click)="toggle(popover, $event)"
9595
9517
  aria-label="Select color"
9596
9518
  ></button>
@@ -9607,18 +9529,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9607
9529
  ], template: `
9608
9530
  <p-popover #popover>
9609
9531
  <div class="color-picker">
9610
- @for (row of options; track $index; let last = $last) {
9532
+ @for (row of options(); track $index; let last = $last) {
9611
9533
  <div class="color-row" [class.mb-2]="!last">
9612
9534
  @for (color of row; track color) {
9613
9535
  <button
9614
9536
  type="button"
9615
9537
  class="color-box"
9616
- [disabled]="disabled"
9538
+ [disabled]="disabled()"
9617
9539
  [style.backgroundColor]="color"
9618
- [style.outline]="focusedColor === color ? '3px solid ' + color : 'none'"
9619
- [style.outlineOffset]="focusedColor === color ? '3px' : '0'"
9540
+ [style.outline]="focusedColor() === color ? '3px solid ' + color : 'none'"
9541
+ [style.outlineOffset]="focusedColor() === color ? '3px' : '0'"
9620
9542
  (click)="select(color, popover)"
9621
- (mouseenter)="focusedColor = color"
9543
+ (mouseenter)="focusedColor.set(color)"
9622
9544
  ></button>
9623
9545
  }
9624
9546
  </div>
@@ -9629,174 +9551,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9629
9551
  <button
9630
9552
  type="button"
9631
9553
  class="selected-color"
9632
- [disabled]="disabled"
9633
- [style.backgroundColor]="value || 'transparent'"
9554
+ [disabled]="disabled()"
9555
+ [style.backgroundColor]="value() || 'transparent'"
9634
9556
  (click)="toggle(popover, $event)"
9635
9557
  aria-label="Select color"
9636
9558
  ></button>
9637
9559
  `, styles: [":host ::ng-deep .p-popover-content{padding:0!important}.color-picker{display:flex;flex-direction:column;padding:10px}.color-row{display:flex;justify-content:center;gap:8px}.mb-2{margin-bottom:8px}.color-box{margin:2px;width:30px;height:30px;cursor:pointer;border-radius:4px;border:0;padding:0;background:transparent}.color-box:hover:not(:disabled){transform:scale(1.1);opacity:.9;box-shadow:0 0 5px #0003}.color-box:disabled{cursor:not-allowed;opacity:.6}.selected-color{width:35px;height:35px;border:2px solid #ccc;cursor:pointer;border-radius:6px;padding:0;background:transparent}.selected-color:disabled{cursor:not-allowed;opacity:.6}\n"] }]
9638
- }], propDecorators: { options: [{
9639
- type: Input
9640
- }], disable: [{
9641
- type: Input
9642
- }] } });
9560
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9643
9561
 
9562
+ /**
9563
+ * V2 START_DUE_DATE field — signal-based ControlValueAccessor (OnPush, no manual
9564
+ * CD). Two date pickers whose composite value is propagated through CVA.
9565
+ */
9644
9566
  class MetaStartDueDateV2Component {
9645
- /**
9646
- * Optional data-cy attribute prefix for e2e testing.
9647
- * Can be used by parent components to uniquely identify this field in tests.
9648
- */
9649
- dataCy;
9650
- /**
9651
- * Optional parent-level disable flag (in addition to reactive-form disable).
9652
- * Use this when the field must be disabled due to page/business logic.
9653
- */
9654
- disable = false;
9655
- cdr = inject(ChangeDetectorRef);
9656
- /**
9657
- * Disabled state coming from Angular Forms (ControlValueAccessor).
9658
- * When true, both date pickers are non-interactive.
9659
- */
9660
- isDisabled = false;
9661
- /**
9662
- * Local UI state for the selected start date.
9663
- * Normalized to Date or null for PrimeNG DatePicker compatibility.
9664
- */
9665
- startDate = null;
9666
- /**
9667
- * Local UI state for the selected end date.
9668
- * Normalized to Date or null for PrimeNG DatePicker compatibility.
9669
- */
9670
- endDate = null;
9671
- /**
9672
- * CVA callback invoked when the composite value changes.
9673
- */
9567
+ /** Optional data-cy prefix for e2e tests. */
9568
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9569
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9570
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9571
+ /** Selected start/end dates (Date or null) for the pickers. */
9572
+ startDate = signal(null, ...(ngDevMode ? [{ debugName: "startDate" }] : []));
9573
+ endDate = signal(null, ...(ngDevMode ? [{ debugName: "endDate" }] : []));
9574
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9575
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9674
9576
  onChange = () => { };
9675
- /**
9676
- * CVA callback invoked when the control is marked as touched.
9677
- * IMPORTANT: We call this on blur (not on every change) to match standard CVA behavior.
9678
- */
9679
9577
  onTouched = () => { };
9680
- /**
9681
- * Effective disabled state used by the template.
9682
- * Combines reactive form disable + parent-level disable.
9683
- */
9684
- get disabled() {
9685
- return this.disable || this.isDisabled;
9686
- }
9687
- /**
9688
- * Writes a new value from the parent form control into the component.
9689
- * Incoming values are normalized to Date instances for the UI layer.
9690
- *
9691
- * NOTE: "YYYY-MM-DD" strings are parsed as local dates to avoid timezone day-shifts.
9692
- */
9693
9578
  writeValue(v) {
9694
- this.startDate = this.normalizeDateOnly(this.parseToDate(v?.startDate ?? null));
9695
- this.endDate = this.normalizeDateOnly(this.parseToDate(v?.endDate ?? null));
9696
- // OnPush: ensure UI reflects external value writes (patchValue/setValue)
9697
- this.cdr.markForCheck();
9579
+ this.startDate.set(this.normalizeDateOnly(this.parseToDate(v?.startDate ?? null)));
9580
+ this.endDate.set(this.normalizeDateOnly(this.parseToDate(v?.endDate ?? null)));
9698
9581
  }
9699
- /**
9700
- * Registers the callback that should be called when the value changes.
9701
- */
9702
9582
  registerOnChange(fn) {
9703
9583
  this.onChange = fn;
9704
9584
  }
9705
- /**
9706
- * Registers the callback that should be called when the control is touched.
9707
- */
9708
9585
  registerOnTouched(fn) {
9709
9586
  this.onTouched = fn;
9710
9587
  }
9711
- /**
9712
- * Receives disabled state from Angular Forms and updates local state.
9713
- */
9714
9588
  setDisabledState(isDisabled) {
9715
- this.isDisabled = isDisabled;
9716
- // OnPush: reflect disabled state changes immediately
9717
- this.cdr.markForCheck();
9589
+ this.formDisabled.set(isDisabled);
9718
9590
  }
9719
- /**
9720
- * Handler used by PrimeNG DatePicker blur events.
9721
- * Marks the control as touched without emitting a value change.
9722
- */
9723
9591
  handleBlur() {
9724
- if (this.disabled)
9592
+ if (this.disabled())
9725
9593
  return;
9726
9594
  this.onTouched();
9727
9595
  }
9728
- /**
9729
- * Handler for start date change coming from the DatePicker.
9730
- * Updates local state and propagates the composite value.
9731
- */
9732
9596
  onStartChange(d) {
9733
- if (this.disabled)
9597
+ if (this.disabled())
9734
9598
  return;
9735
- this.startDate = this.normalizeDateOnly(d ?? null);
9599
+ this.startDate.set(this.normalizeDateOnly(d ?? null));
9736
9600
  this.emitChange();
9737
9601
  }
9738
- /**
9739
- * Handler for end date change coming from the DatePicker.
9740
- * Updates local state and propagates the composite value.
9741
- */
9742
9602
  onEndChange(d) {
9743
- if (this.disabled)
9603
+ if (this.disabled())
9744
9604
  return;
9745
- this.endDate = this.normalizeDateOnly(d ?? null);
9605
+ this.endDate.set(this.normalizeDateOnly(d ?? null));
9746
9606
  this.emitChange();
9747
9607
  }
9748
- /**
9749
- * Emits the composite value to the parent form control.
9750
- * - Emits `null` when both dates are empty (cleaner semantics for required/bothDates validators).
9751
- * - Otherwise emits the `{ startDate, endDate }` object (partial values allowed; validator decides).
9752
- */
9608
+ /** Emits `null` when both dates are empty, otherwise the range object. */
9753
9609
  emitChange() {
9754
- if (!this.startDate && !this.endDate) {
9610
+ const start = this.startDate();
9611
+ const end = this.endDate();
9612
+ if (!start && !end) {
9755
9613
  this.onChange(null);
9756
- this.cdr.markForCheck();
9757
9614
  return;
9758
9615
  }
9759
- this.onChange({
9760
- startDate: this.startDate,
9761
- endDate: this.endDate,
9762
- });
9763
- this.cdr.markForCheck();
9616
+ this.onChange({ startDate: start, endDate: end });
9764
9617
  }
9765
9618
  /**
9766
- * Parses Date | string safely into a Date instance for the UI.
9767
- * Important: "YYYY-MM-DD" is treated as a local date (prevents timezone day-shift).
9619
+ * Parses Date | string to a Date. "YYYY-MM-DD" is treated as a LOCAL date to
9620
+ * avoid timezone day-shifts.
9768
9621
  */
9769
9622
  parseToDate(v) {
9770
9623
  if (!v)
9771
9624
  return null;
9772
- if (v instanceof Date) {
9625
+ if (v instanceof Date)
9773
9626
  return isNaN(v.getTime()) ? null : v;
9774
- }
9775
9627
  const s = String(v).trim();
9776
9628
  if (!s)
9777
9629
  return null;
9778
- // Date-only string -> create LOCAL date (avoid UTC shifting)
9779
9630
  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
9780
9631
  if (m) {
9781
- const y = Number(m[1]);
9782
- const mo = Number(m[2]) - 1;
9783
- const d = Number(m[3]);
9784
- const local = new Date(y, mo, d, 12, 0, 0, 0);
9632
+ const local = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), 12, 0, 0, 0);
9785
9633
  return isNaN(local.getTime()) ? null : local;
9786
9634
  }
9787
- // ISO / other -> fallback
9788
9635
  const dt = new Date(s);
9789
9636
  return isNaN(dt.getTime()) ? null : dt;
9790
9637
  }
9791
9638
  normalizeDateOnly(d) {
9792
- if (!d)
9793
- return null;
9794
- if (isNaN(d.getTime()))
9639
+ if (!d || isNaN(d.getTime()))
9795
9640
  return null;
9796
9641
  return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0);
9797
9642
  }
9798
9643
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaStartDueDateV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
9799
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: MetaStartDueDateV2Component, isStandalone: true, selector: "phoenix-meta-start-due-date-v2", inputs: { dataCy: "dataCy", disable: "disable" }, providers: [
9644
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: MetaStartDueDateV2Component, isStandalone: true, selector: "phoenix-meta-start-due-date-v2", inputs: { dataCy: { classPropertyName: "dataCy", publicName: "dataCy", isSignal: true, isRequired: false, transformFunction: null }, disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
9800
9645
  {
9801
9646
  provide: NG_VALUE_ACCESSOR,
9802
9647
  useExisting: forwardRef(() => MetaStartDueDateV2Component),
@@ -9812,8 +9657,8 @@ class MetaStartDueDateV2Component {
9812
9657
  [showButtonBar]="true"
9813
9658
  [showIcon]="true"
9814
9659
  [placeholder]="'LABELS.PLANNED_START' | translate"
9815
- [disabled]="disabled"
9816
- [ngModel]="startDate"
9660
+ [disabled]="disabled()"
9661
+ [ngModel]="startDate()"
9817
9662
  (ngModelChange)="onStartChange($event)"
9818
9663
  (onBlur)="handleBlur()"
9819
9664
  appendTo="body"
@@ -9831,8 +9676,8 @@ class MetaStartDueDateV2Component {
9831
9676
  [showButtonBar]="true"
9832
9677
  [showIcon]="true"
9833
9678
  [placeholder]="'LABELS.PLANNED_END' | translate"
9834
- [disabled]="disabled"
9835
- [ngModel]="endDate"
9679
+ [disabled]="disabled()"
9680
+ [ngModel]="endDate()"
9836
9681
  (ngModelChange)="onEndChange($event)"
9837
9682
  (onBlur)="handleBlur()"
9838
9683
  appendTo="body"
@@ -9860,8 +9705,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9860
9705
  [showButtonBar]="true"
9861
9706
  [showIcon]="true"
9862
9707
  [placeholder]="'LABELS.PLANNED_START' | translate"
9863
- [disabled]="disabled"
9864
- [ngModel]="startDate"
9708
+ [disabled]="disabled()"
9709
+ [ngModel]="startDate()"
9865
9710
  (ngModelChange)="onStartChange($event)"
9866
9711
  (onBlur)="handleBlur()"
9867
9712
  appendTo="body"
@@ -9879,8 +9724,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9879
9724
  [showButtonBar]="true"
9880
9725
  [showIcon]="true"
9881
9726
  [placeholder]="'LABELS.PLANNED_END' | translate"
9882
- [disabled]="disabled"
9883
- [ngModel]="endDate"
9727
+ [disabled]="disabled()"
9728
+ [ngModel]="endDate()"
9884
9729
  (ngModelChange)="onEndChange($event)"
9885
9730
  (onBlur)="handleBlur()"
9886
9731
  appendTo="body"
@@ -9889,116 +9734,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9889
9734
  </div>
9890
9735
  </div>
9891
9736
  `, styles: [":host{display:block}\n"] }]
9892
- }], propDecorators: { dataCy: [{
9893
- type: Input
9894
- }], disable: [{
9895
- type: Input
9896
- }] } });
9737
+ }], propDecorators: { dataCy: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataCy", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9897
9738
 
9739
+ /**
9740
+ * V2 SWITCH field — signal-based ControlValueAccessor (OnPush, no manual CD).
9741
+ * Value + disabled live in signals; the template reads them as functions.
9742
+ */
9898
9743
  class MetaSwitchV2Component {
9899
- /**
9900
- * Optional external disable flag (field-level disable coming from meta config).
9901
- * This is combined with the disabled state coming from Angular Forms (CVA).
9902
- */
9903
- disable = false;
9904
- /**
9905
- * Optional flag to hide the switch from UI (meta hidden).
9906
- * Keep in mind: hidden does not automatically disable the control.
9907
- */
9908
- hidden = false;
9909
- /**
9910
- * Optional data-cy attribute for e2e tests (e.g. "switch-singleEntity").
9911
- */
9912
- dataCy;
9913
- cdr = inject(ChangeDetectorRef);
9914
- /**
9915
- * Current boolean value stored inside the component.
9916
- * This value is synchronized with the parent FormControl through CVA.
9917
- */
9918
- value = false;
9919
- /**
9920
- * CVA callback invoked when the value changes.
9921
- */
9744
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9745
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9746
+ /** Hide the switch (meta hidden). Does not disable it. */
9747
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : []));
9748
+ /** Optional data-cy for e2e tests. */
9749
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9750
+ /** Current boolean value, synced with the parent control through CVA. */
9751
+ value = signal(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
9752
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9753
+ /** Final disabled = field-level disable OR the Forms disabled state. */
9754
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9922
9755
  onChange = () => { };
9923
- /**
9924
- * CVA callback invoked when the control is marked as touched.
9925
- * Standard: call on blur, not on every change.
9926
- */
9927
9756
  onTouched = () => { };
9928
- /**
9929
- * Disabled state coming from Angular Forms (ControlValueAccessor).
9930
- */
9931
- isDisabled = false;
9932
- /**
9933
- * Final disabled state combining:
9934
- * - form-level disabled state (CVA)
9935
- * - field-level disable flag (input)
9936
- */
9937
- get disabled() {
9938
- return this.disable || this.isDisabled;
9939
- }
9940
- /**
9941
- * Writes a new value from the parent form control into the component.
9942
- * Keep it idempotent and UI-safe.
9943
- */
9944
9757
  writeValue(v) {
9945
- this.value = this.normalizeBool(v);
9946
- this.cdr.markForCheck();
9758
+ this.value.set(this.normalizeBool(v));
9947
9759
  }
9948
- /**
9949
- * Registers callback that is triggered when the value changes.
9950
- */
9951
9760
  registerOnChange(fn) {
9952
9761
  this.onChange = fn;
9953
9762
  }
9954
- /**
9955
- * Registers callback that is triggered when the control is touched.
9956
- */
9957
9763
  registerOnTouched(fn) {
9958
9764
  this.onTouched = fn;
9959
9765
  }
9960
- /**
9961
- * Receives disabled state from Angular Forms and updates local state.
9962
- */
9963
9766
  setDisabledState(isDisabled) {
9964
- this.isDisabled = isDisabled;
9965
- this.cdr.markForCheck();
9767
+ this.formDisabled.set(isDisabled);
9966
9768
  }
9967
9769
  /**
9968
- * Handler for switch change.
9969
- * Propagates value to the parent form control.
9970
- *
9971
- * Important note:
9972
- * We intentionally use [ngModel] instead of [(ngModel)] in the template.
9973
- * Two-way binding would update the local value BEFORE this handler runs,
9974
- * which would prevent correct change detection and CVA propagation.
9770
+ * We use one-way [ngModel] + (ngModelChange) on purpose: two-way binding would
9771
+ * mutate the value before this handler runs and break CVA propagation.
9975
9772
  */
9976
9773
  onSwitchChange(next) {
9977
- if (this.disabled)
9774
+ if (this.disabled())
9978
9775
  return;
9979
9776
  const normalized = this.normalizeBool(next);
9980
- if (normalized !== this.value) {
9981
- this.value = normalized;
9982
- this.onChange(normalized);
9983
- this.cdr.markForCheck();
9984
- return;
9985
- }
9986
- // ensure the form control still receives the value
9777
+ this.value.set(normalized);
9987
9778
  this.onChange(normalized);
9988
- this.cdr.markForCheck();
9989
9779
  }
9990
- /**
9991
- * Marks control as touched when user leaves the component.
9992
- */
9993
9780
  handleBlur() {
9994
- if (this.disabled)
9781
+ if (this.disabled())
9995
9782
  return;
9996
9783
  this.onTouched();
9997
9784
  }
9998
- /**
9999
- * Normalizes incoming values to strict boolean.
10000
- * Supports: boolean, "true"/"false", 1/0, "1"/"0".
10001
- */
9785
+ /** Normalizes boolean-ish values (boolean, "true"/"false", 1/0, "1"/"0"). */
10002
9786
  normalizeBool(v) {
10003
9787
  if (v === true || v === false)
10004
9788
  return v;
@@ -10016,7 +9800,7 @@ class MetaSwitchV2Component {
10016
9800
  return !!v;
10017
9801
  }
10018
9802
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaSwitchV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
10019
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: MetaSwitchV2Component, isStandalone: true, selector: "phoenix-meta-switch-v2", inputs: { disable: "disable", hidden: "hidden", dataCy: "dataCy" }, providers: [
9803
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: MetaSwitchV2Component, isStandalone: true, selector: "phoenix-meta-switch-v2", inputs: { disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, dataCy: { classPropertyName: "dataCy", publicName: "dataCy", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
10020
9804
  {
10021
9805
  provide: NG_VALUE_ACCESSOR,
10022
9806
  useExisting: forwardRef(() => MetaSwitchV2Component),
@@ -10025,12 +9809,12 @@ class MetaSwitchV2Component {
10025
9809
  ], ngImport: i0, template: `
10026
9810
  <p-toggleSwitch
10027
9811
  class="phoenix-switch-v2"
10028
- [ngModel]="value"
9812
+ [ngModel]="value()"
10029
9813
  (ngModelChange)="onSwitchChange($event)"
10030
9814
  (onBlur)="handleBlur()"
10031
- [disabled]="disabled"
10032
- [hidden]="hidden"
10033
- [attr.data-cy]="dataCy ?? null"
9815
+ [disabled]="disabled()"
9816
+ [hidden]="hidden()"
9817
+ [attr.data-cy]="dataCy() ?? null"
10034
9818
  ></p-toggleSwitch>
10035
9819
  `, isInline: true, styles: [":host ::ng-deep .p-toggleswitch{margin-top:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i3$6.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10036
9820
  }
@@ -10039,12 +9823,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10039
9823
  args: [{ selector: 'phoenix-meta-switch-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ToggleSwitchModule], template: `
10040
9824
  <p-toggleSwitch
10041
9825
  class="phoenix-switch-v2"
10042
- [ngModel]="value"
9826
+ [ngModel]="value()"
10043
9827
  (ngModelChange)="onSwitchChange($event)"
10044
9828
  (onBlur)="handleBlur()"
10045
- [disabled]="disabled"
10046
- [hidden]="hidden"
10047
- [attr.data-cy]="dataCy ?? null"
9829
+ [disabled]="disabled()"
9830
+ [hidden]="hidden()"
9831
+ [attr.data-cy]="dataCy() ?? null"
10048
9832
  ></p-toggleSwitch>
10049
9833
  `, providers: [
10050
9834
  {
@@ -10053,178 +9837,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10053
9837
  multi: true,
10054
9838
  },
10055
9839
  ], styles: [":host ::ng-deep .p-toggleswitch{margin-top:12px}\n"] }]
10056
- }], propDecorators: { disable: [{
10057
- type: Input
10058
- }], hidden: [{
10059
- type: Input
10060
- }], dataCy: [{
10061
- type: Input
10062
- }] } });
9840
+ }], propDecorators: { disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], dataCy: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataCy", required: false }] }] } });
10063
9841
 
10064
9842
  /**
10065
- * V2 Password meta field component.
10066
- *
10067
- * Goals vs legacy component:
10068
- * - Uses a clean ControlValueAccessor implementation (no BaseMetaField inheritance).
10069
- * - OnPush + explicit markForCheck() for predictable rendering in large dynamic forms.
10070
- * - Supports "disable" (meta-level) + Angular Forms disabled state (CVA) combined.
10071
- * - Emits touched on blur (standard), not on every input.
10072
- * - Keeps value as string and normalizes null/undefined to ''.
10073
- *
10074
- * Notes:
10075
- * - PrimeNG <p-password> supports toggleMask, feedback, strength meter, etc.
10076
- * - We keep [feedback]="false" to match old behavior.
9843
+ * V2 PASSWORD field — signal-based ControlValueAccessor (OnPush, no manual CD).
9844
+ * Value + disabled live in signals; touched is emitted on blur, value kept as a
9845
+ * string (null/undefined normalized to '').
10077
9846
  */
10078
9847
  class MetaPasswordFieldV2Component {
10079
- /**
10080
- * Meta control configuration passed from dynamic forms system.
10081
- * Keep it `any` to be compatible with existing meta-form model.
10082
- *
10083
- * If you have a shared type (e.g. MetaFormControl), replace `any`.
10084
- */
10085
- control;
10086
- /**
10087
- * Underlying Angular control reference used by InlineFieldError.
10088
- * In your meta system this is typically injected/assigned by parent wrapper.
10089
- *
10090
- * If you have a strict type: AbstractControl | null
10091
- */
10092
- ctrl;
10093
- /**
10094
- * Optional external disable flag (field-level disable coming from meta config).
10095
- * This is combined with the disabled state coming from Angular Forms (CVA).
10096
- */
10097
- disable = false;
10098
- /**
10099
- * Optional flag to hide the control from UI (meta hidden).
10100
- * Keep in mind: hidden does not automatically disable the control.
10101
- */
10102
- hidden = false;
10103
- /**
10104
- * Optional data-cy attribute for e2e tests (e.g. "password-admin").
10105
- * If not provided, you can still generate it from control.id in template usage.
10106
- */
10107
- dataCy;
10108
- /**
10109
- * PrimeNG feedback (strength meter / hints). Disabled by default to match v1.
10110
- */
10111
- feedback = false;
10112
- /**
10113
- * Autocomplete attribute: by default we set to "off" (match old component).
10114
- * For login forms you might want "current-password" or "new-password".
10115
- */
10116
- autocomplete = 'off';
10117
- cdr = inject(ChangeDetectorRef);
10118
- /**
10119
- * Current password value.
10120
- * Always keep as string to avoid null/undefined edge cases with ngModel.
10121
- */
10122
- value = '';
10123
- /**
10124
- * CVA callback invoked when the value changes.
10125
- */
9848
+ /** Meta control configuration (kept for API compatibility). */
9849
+ control = input(...(ngDevMode ? [undefined, { debugName: "control" }] : []));
9850
+ /** Underlying control reference (kept for API compatibility). */
9851
+ ctrl = input(...(ngDevMode ? [undefined, { debugName: "ctrl" }] : []));
9852
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9853
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9854
+ /** Hide the control (meta hidden). Does not disable it. */
9855
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : []));
9856
+ /** Optional data-cy for e2e tests. */
9857
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9858
+ /** PrimeNG strength meter; off by default to match v1. */
9859
+ feedback = input(false, ...(ngDevMode ? [{ debugName: "feedback" }] : []));
9860
+ /** Autocomplete attribute (default "off"). */
9861
+ autocomplete = input('off', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
9862
+ /** Current password value (always a string). */
9863
+ value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : []));
9864
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9865
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
10126
9866
  onChange = () => { };
10127
- /**
10128
- * CVA callback invoked when the control is marked as touched.
10129
- * Standard: call on blur, not on every change.
10130
- */
10131
9867
  onTouched = () => { };
10132
- /**
10133
- * Disabled state coming from Angular Forms (ControlValueAccessor).
10134
- */
10135
- isDisabled = false;
10136
- /**
10137
- * Final disabled state combining:
10138
- * - form-level disabled state (CVA)
10139
- * - field-level disable flag (input)
10140
- */
10141
- get disabled() {
10142
- return this.disable || this.isDisabled;
10143
- }
10144
- /**
10145
- * Writes a new value from the parent form control into the component.
10146
- * Keep it idempotent and UI-safe.
10147
- */
10148
9868
  writeValue(v) {
10149
- const next = this.normalizeString(v);
10150
- // Prevent redundant UI updates.
10151
- if (next === this.value) {
10152
- this.cdr.markForCheck();
10153
- return;
10154
- }
10155
- this.value = next;
10156
- this.cdr.markForCheck();
9869
+ this.value.set(this.normalizeString(v));
10157
9870
  }
10158
- /**
10159
- * Registers callback that is triggered when the value changes.
10160
- */
10161
9871
  registerOnChange(fn) {
10162
9872
  this.onChange = fn;
10163
9873
  }
10164
- /**
10165
- * Registers callback that is triggered when the control is touched.
10166
- */
10167
9874
  registerOnTouched(fn) {
10168
9875
  this.onTouched = fn;
10169
9876
  }
10170
- /**
10171
- * Receives disabled state from Angular Forms and updates local state.
10172
- */
10173
9877
  setDisabledState(isDisabled) {
10174
- this.isDisabled = isDisabled;
10175
- this.cdr.markForCheck();
9878
+ this.formDisabled.set(isDisabled);
10176
9879
  }
10177
- /**
10178
- * Handler for user typing into the password input.
10179
- * Propagates current value to the parent form control.
10180
- *
10181
- * Note:
10182
- * - PrimeNG (input) gives us an event; we can also just use this.value directly.
10183
- * - We intentionally do NOT call onTouched here (touched on blur).
10184
- */
9880
+ /** Propagates every keystroke; touched is emitted on blur. */
10185
9881
  onPasswordInput(event) {
10186
- if (this.disabled)
9882
+ if (this.disabled())
10187
9883
  return;
10188
9884
  const next = this.getInputValue(event);
10189
- // Prevent redundant emits.
10190
- // if (next === this.value) {
10191
- // this.cdr.markForCheck();
10192
- // return;
10193
- // }
10194
- this.value = next;
9885
+ this.value.set(next);
10195
9886
  this.onChange(next);
10196
- this.cdr.markForCheck();
10197
9887
  }
10198
- /**
10199
- * Marks control as touched when user leaves the component.
10200
- */
10201
9888
  handleBlur() {
10202
- if (this.disabled)
9889
+ if (this.disabled())
10203
9890
  return;
10204
9891
  this.onTouched();
10205
9892
  }
10206
- /**
10207
- * PrimeNG input event -> string value.
10208
- * Supports:
10209
- * - native input event: event.target.value
10210
- * - direct string (some wrappers)
10211
- */
10212
9893
  getInputValue(event) {
10213
9894
  const v = event?.target?.value ?? event;
10214
9895
  return this.normalizeString(v);
10215
9896
  }
10216
- /**
10217
- * Normalizes incoming values to a string.
10218
- * - null/undefined -> ''
10219
- * - other -> String(v)
10220
- */
10221
9897
  normalizeString(v) {
10222
9898
  if (v === null || v === undefined)
10223
9899
  return '';
10224
9900
  return String(v);
10225
9901
  }
10226
9902
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaPasswordFieldV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
10227
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: MetaPasswordFieldV2Component, isStandalone: true, selector: "phoenix-meta-password-field-v2", inputs: { control: "control", ctrl: "ctrl", disable: "disable", hidden: "hidden", dataCy: "dataCy", feedback: "feedback", autocomplete: "autocomplete" }, providers: [
9903
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: MetaPasswordFieldV2Component, isStandalone: true, selector: "phoenix-meta-password-field-v2", inputs: { control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, ctrl: { classPropertyName: "ctrl", publicName: "ctrl", isSignal: true, isRequired: false, transformFunction: null }, disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, dataCy: { classPropertyName: "dataCy", publicName: "dataCy", isSignal: true, isRequired: false, transformFunction: null }, feedback: { classPropertyName: "feedback", publicName: "feedback", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
10228
9904
  {
10229
9905
  provide: NG_VALUE_ACCESSOR,
10230
9906
  useExisting: forwardRef(() => MetaPasswordFieldV2Component),
@@ -10232,22 +9908,16 @@ class MetaPasswordFieldV2Component {
10232
9908
  },
10233
9909
  ], ngImport: i0, template: `
10234
9910
  <div>
10235
- <!--
10236
- PrimeNG Password:
10237
- - We use ngModel for simple CVA integration (same pattern as other meta v2 fields).
10238
- - We handle change via (input) to propagate every keystroke to the parent form.
10239
- - We mark touched on blur to match standard UX (touched != changed).
10240
- -->
10241
9911
  <p-password
10242
9912
  class="phoenix-password-v2"
10243
- [(ngModel)]="value"
9913
+ [ngModel]="value()"
10244
9914
  (ngModelChange)="onPasswordInput($event)"
10245
9915
  (onBlur)="handleBlur()"
10246
- [disabled]="disabled"
10247
- [hidden]="hidden"
10248
- [feedback]="feedback"
10249
- [attr.autocomplete]="autocomplete"
10250
- [attr.data-cy]="dataCy ?? null"
9916
+ [disabled]="disabled()"
9917
+ [hidden]="hidden()"
9918
+ [feedback]="feedback()"
9919
+ [attr.autocomplete]="autocomplete()"
9920
+ [attr.data-cy]="dataCy() ?? null"
10251
9921
  ></p-password>
10252
9922
  </div>
10253
9923
  `, isInline: true, styles: [":host ::ng-deep .p-component{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: PasswordModule }, { kind: "component", type: i2$8.Password, selector: "p-password", inputs: ["ariaLabel", "ariaLabelledBy", "label", "promptLabel", "mediumRegex", "strongRegex", "weakLabel", "mediumLabel", "maxLength", "strongLabel", "inputId", "feedback", "toggleMask", "inputStyleClass", "styleClass", "inputStyle", "showTransitionOptions", "hideTransitionOptions", "autocomplete", "placeholder", "showClear", "autofocus", "tabindex", "appendTo"], outputs: ["onFocus", "onBlur", "onClear"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -10256,22 +9926,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10256
9926
  type: Component,
10257
9927
  args: [{ selector: 'phoenix-meta-password-field-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, TranslateModule, PasswordModule], template: `
10258
9928
  <div>
10259
- <!--
10260
- PrimeNG Password:
10261
- - We use ngModel for simple CVA integration (same pattern as other meta v2 fields).
10262
- - We handle change via (input) to propagate every keystroke to the parent form.
10263
- - We mark touched on blur to match standard UX (touched != changed).
10264
- -->
10265
9929
  <p-password
10266
9930
  class="phoenix-password-v2"
10267
- [(ngModel)]="value"
9931
+ [ngModel]="value()"
10268
9932
  (ngModelChange)="onPasswordInput($event)"
10269
9933
  (onBlur)="handleBlur()"
10270
- [disabled]="disabled"
10271
- [hidden]="hidden"
10272
- [feedback]="feedback"
10273
- [attr.autocomplete]="autocomplete"
10274
- [attr.data-cy]="dataCy ?? null"
9934
+ [disabled]="disabled()"
9935
+ [hidden]="hidden()"
9936
+ [feedback]="feedback()"
9937
+ [attr.autocomplete]="autocomplete()"
9938
+ [attr.data-cy]="dataCy() ?? null"
10275
9939
  ></p-password>
10276
9940
  </div>
10277
9941
  `, providers: [
@@ -10281,47 +9945,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10281
9945
  multi: true,
10282
9946
  },
10283
9947
  ], styles: [":host ::ng-deep .p-component{width:100%}\n"] }]
10284
- }], propDecorators: { control: [{
10285
- type: Input
10286
- }], ctrl: [{
10287
- type: Input
10288
- }], disable: [{
10289
- type: Input
10290
- }], hidden: [{
10291
- type: Input
10292
- }], dataCy: [{
10293
- type: Input
10294
- }], feedback: [{
10295
- type: Input
10296
- }], autocomplete: [{
10297
- type: Input
10298
- }] } });
9948
+ }], propDecorators: { control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], ctrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctrl", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], dataCy: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataCy", required: false }] }], feedback: [{ type: i0.Input, args: [{ isSignal: true, alias: "feedback", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }] } });
10299
9949
 
9950
+ /**
9951
+ * Renders one meta field. Signal-based (OnPush): every template-facing value is a
9952
+ * `computed()` so it is memoized and recomputed only when its inputs actually
9953
+ * change — instead of a plain method re-running on every change-detection pass.
9954
+ * Control state (value/status) is bridged into a signal (`controlTick`), so the
9955
+ * error helpers stay reactive without a manual `markForCheck()`.
9956
+ */
10300
9957
  class MetaFormFieldV2Component {
10301
9958
  /** Metadata definition of the field (type, key, options, styles, flags, etc.) */
10302
- field;
10303
- /** Parent FormGroup that contains the FormControl for this field */
10304
- form;
10305
- /**
10306
- * Page-level read-only flag.
10307
- * When true, the component renders ReadOnlyInputV2Component instead of editable controls.
10308
- */
10309
- readOnly = false;
10310
- /**
10311
- * Global disable flag (e.g. parent dialog toggles entire form disabled).
10312
- * This is merged with field-level disable configuration.
10313
- */
10314
- disableForm = false;
10315
- /** Used to manually trigger change detection for OnPush strategy */
10316
- cdr = inject(ChangeDetectorRef);
10317
- /** Used to automatically unsubscribe from value/status streams on destroy */
10318
- dr = inject(DestroyRef);
10319
- /** Translation service for validation and display labels */
9959
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
9960
+ /** Parent FormGroup that contains the FormControl for this field. */
9961
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
9962
+ /** Page-level read-only flag (renders the read-only view). */
9963
+ readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
9964
+ /** Global disable flag (merged with the field-level disable config). */
9965
+ disableForm = input(false, ...(ngDevMode ? [{ debugName: "disableForm" }] : []));
10320
9966
  translate = inject(TranslateService);
10321
- /**
10322
- * Exposed enum-like mapping of MetaFieldType for template usage.
10323
- * Keeps templates readable and avoids magic strings.
10324
- */
9967
+ /** Enum-like map for the template `@switch`. */
10325
9968
  MetaFieldType = Object.freeze({
10326
9969
  TEXT: 'TEXT',
10327
9970
  URL: 'URL',
@@ -10348,95 +9991,41 @@ class MetaFormFieldV2Component {
10348
9991
  LINKS_DATA: 'LINKS_DATA',
10349
9992
  SLOT: 'SLOT',
10350
9993
  });
10351
- /** Control key resolved from MetaFieldConfig */
10352
- get key() {
10353
- return this.field?.configuration?.key ?? '';
10354
- }
10355
- /** Field type resolved from MetaFieldConfig */
10356
- get type() {
10357
- return this.field?.configuration?.type ?? 'TEXT';
10358
- }
10359
- /** Column width class for grid layout (falls back to default if not provided) */
10360
- get colClass() {
10361
- return this.field?.hidden
10362
- ? 'p-0'
10363
- : (this.field?.style.colWidth ?? 'col-12 md:col-6');
10364
- }
10365
- ngOnInit() {
10366
- const ctrl = this.ctrl();
10367
- if (!ctrl)
10368
- return;
10369
- /**
10370
- * Subscribe to both valueChanges and statusChanges so the component:
10371
- * - re-renders when user changes the value
10372
- * - re-renders when validation state changes (touched/dirty/errors)
10373
- */
10374
- merge(ctrl.valueChanges, ctrl.statusChanges)
10375
- .pipe(takeUntilDestroyed(this.dr))
10376
- .subscribe(() => this.cdr.markForCheck());
10377
- }
10378
- /** Human-friendly label defined in metadata (already localized key) */
10379
- userFriendlyMessage() {
10380
- return this.field?.userFriendlyMessage ?? null;
10381
- }
10382
- /** Optional placeholder i18n key defined in metadata */
10383
- placeholderKey() {
10384
- return this.field?.configuration?.placeholderKey ?? null;
10385
- }
10386
- /**
10387
- * Resolves final read-only state for this field:
10388
- * - page-level readOnly OR field-level readOnly
10389
- */
10390
- isReadOnly() {
10391
- return !!this.readOnly || !!this.field?.readOnly;
10392
- }
10393
- /**
10394
- * Resolves final disabled state for this field:
10395
- * - page-level disable OR field-level disable
10396
- */
10397
- isDisabled() {
10398
- return !!this.disableForm || !!this.field?.disable;
10399
- }
10400
- /** Shortcut to underlying FormControl */
10401
- ctrl() {
10402
- return this.form.get(this.key);
10403
- }
10404
- /**
10405
- * Minimal value formatter for legacy read-only rendering.
10406
- * Kept intentionally simple to match V1 behavior.
10407
- */
10408
- displayValue() {
10409
- const v = this.ctrl()?.value;
10410
- if (v === null || v === undefined)
10411
- return '';
10412
- if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
10413
- return v;
10414
- // Common DTO shapes (assign, option objects, uploads, etc.)
10415
- if (typeof v === 'object') {
10416
- return v.label ?? v.name ?? v.fileName ?? JSON.stringify(v);
10417
- }
10418
- return String(v);
10419
- }
10420
- /**
10421
- * Determines whether validation error should be displayed.
10422
- * Errors are shown only after user interaction (touched or dirty).
10423
- */
10424
- showError() {
9994
+ // ---- Pure, input-derived views (memoized) ----
9995
+ key = computed(() => this.field()?.configuration?.key ?? '', ...(ngDevMode ? [{ debugName: "key" }] : []));
9996
+ type = computed(() => this.field()?.configuration?.type ?? 'TEXT', ...(ngDevMode ? [{ debugName: "type" }] : []));
9997
+ colClass = computed(() => this.field()?.hidden ? 'p-0' : (this.field()?.style.colWidth ?? 'col-12 md:col-6'), ...(ngDevMode ? [{ debugName: "colClass" }] : []));
9998
+ userFriendlyMessage = computed(() => this.field()?.userFriendlyMessage ?? null, ...(ngDevMode ? [{ debugName: "userFriendlyMessage" }] : []));
9999
+ placeholderKey = computed(() => this.field()?.configuration?.placeholderKey ?? null, ...(ngDevMode ? [{ debugName: "placeholderKey" }] : []));
10000
+ isReadOnly = computed(() => !!this.readOnly() || !!this.field()?.readOnly, ...(ngDevMode ? [{ debugName: "isReadOnly" }] : []));
10001
+ isDisabled = computed(() => !!this.disableForm() || !!this.field()?.disable, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
10002
+ isCheckbox = computed(() => this.type() === this.MetaFieldType.CHECKBOX, ...(ngDevMode ? [{ debugName: "isCheckbox" }] : []));
10003
+ /** The underlying FormControl for this field (recomputed if form/key change). */
10004
+ ctrl = computed(() => this.form().get(this.key()) ?? null, ...(ngDevMode ? [{ debugName: "ctrl" }] : []));
10005
+ /**
10006
+ * Bridge the current control's value/status stream into a signal so the error
10007
+ * helpers below recompute reactively (reactive forms aren't signal-native yet).
10008
+ * Re-subscribes when the control identity changes.
10009
+ */
10010
+ controlTick = toSignal(toObservable(this.ctrl).pipe(switchMap$1((c) => c ? merge(c.valueChanges, c.statusChanges).pipe(startWith(null)) : EMPTY)), { initialValue: null });
10011
+ /** Bumped for silent (emitEvent:false) programmatic writes, e.g. the date field. */
10012
+ manualTick = signal(0, ...(ngDevMode ? [{ debugName: "manualTick" }] : []));
10013
+ /** Track the active language so translated error text refreshes on lang change. */
10014
+ lang = toSignal(this.translate.onLangChange.pipe(startWith(null)), { initialValue: null });
10015
+ /** Show validation only after interaction (touched/dirty). */
10016
+ showError = computed(() => {
10017
+ this.controlTick();
10018
+ this.manualTick();
10425
10019
  const c = this.ctrl();
10426
10020
  return !!c && (c.touched || c.dirty) && !!c.errors;
10427
- }
10428
- /**
10429
- * Maps control error object to a normalized error key.
10430
- * Supports:
10431
- * - Angular built-in validators
10432
- * - Phoenix custom validators
10433
- * - Submit-only async validators
10434
- */
10435
- errorKey() {
10021
+ }, ...(ngDevMode ? [{ debugName: "showError" }] : []));
10022
+ /** Normalized error key for the current control. */
10023
+ errorKey = computed(() => {
10024
+ this.controlTick();
10025
+ this.manualTick();
10436
10026
  const c = this.ctrl();
10437
10027
  if (!c?.errors)
10438
10028
  return null;
10439
- // Angular built-in validators
10440
10029
  if (c.errors['required'])
10441
10030
  return 'required';
10442
10031
  if (c.errors['minlength'])
@@ -10451,7 +10040,6 @@ class MetaFormFieldV2Component {
10451
10040
  return 'min';
10452
10041
  if (c.errors['max'])
10453
10042
  return 'max';
10454
- // Phoenix custom validators
10455
10043
  if (c.errors['dangerousChars'])
10456
10044
  return 'dangerousChars';
10457
10045
  if (c.errors['timeperiod'])
@@ -10462,21 +10050,17 @@ class MetaFormFieldV2Component {
10462
10050
  return 'dueDate';
10463
10051
  if (c.errors['bothDates'])
10464
10052
  return 'bothDates';
10465
- // Submit-only async validators
10466
10053
  if (c.errors['unique'])
10467
10054
  return 'unique';
10468
10055
  if (c.errors['uniqueEntry'])
10469
10056
  return 'uniqueEntry';
10470
10057
  if (c.errors['custom'])
10471
10058
  return 'custom';
10472
- // Fallback: return first error key
10473
10059
  return Object.keys(c.errors)[0] ?? null;
10474
- }
10475
- /**
10476
- * Resolves translated error message based on errorKey().
10477
- * This is the single place responsible for validation message UX.
10478
- */
10479
- errorText() {
10060
+ }, ...(ngDevMode ? [{ debugName: "errorKey" }] : []));
10061
+ /** Translated validation message (the single place that owns error UX text). */
10062
+ errorText = computed(() => {
10063
+ this.lang();
10480
10064
  const c = this.ctrl();
10481
10065
  const k = this.errorKey();
10482
10066
  if (!c || !k)
@@ -10497,10 +10081,8 @@ class MetaFormFieldV2Component {
10497
10081
  case 'dangerousChars':
10498
10082
  return this.translate.instant('VALIDATION_MESSAGE.NO_SPECIAL_CHARS_ALLOWED');
10499
10083
  case 'custom':
10500
- // Legacy behavior: custom error can already be a translation key
10501
10084
  return this.translate.instant(c.errors?.['custom']);
10502
10085
  case 'uniqueEntry':
10503
- // Legacy behavior: uniqueEntry may already be a translated string
10504
10086
  return (c.errors?.['uniqueEntry'] ??
10505
10087
  this.translate.instant('VALIDATION_MESSAGE.VALUE_IS_ALREADY_IN_USE'));
10506
10088
  case 'unique':
@@ -10517,7 +10099,6 @@ class MetaFormFieldV2Component {
10517
10099
  upperValue: c.errors?.['max']?.max,
10518
10100
  });
10519
10101
  case 'pattern': {
10520
- // Special-case URL pattern handling (legacy InlineFieldError behavior)
10521
10102
  const re = '^(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})[/\\w .-]*/?$';
10522
10103
  const requiredPattern = c.errors?.['pattern']?.requiredPattern;
10523
10104
  if (requiredPattern === re) {
@@ -10528,54 +10109,54 @@ class MetaFormFieldV2Component {
10528
10109
  default:
10529
10110
  return this.translate.instant('VALIDATION_MESSAGE.INVALID_VALUE');
10530
10111
  }
10112
+ }, ...(ngDevMode ? [{ debugName: "errorText" }] : []));
10113
+ /** Minimal value formatter for legacy read-only rendering (not used in template). */
10114
+ displayValue() {
10115
+ const v = this.ctrl()?.value;
10116
+ if (v === null || v === undefined)
10117
+ return '';
10118
+ if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
10119
+ return v;
10120
+ if (typeof v === 'object') {
10121
+ return v.label ?? v.name ?? v.fileName ?? JSON.stringify(v);
10122
+ }
10123
+ return String(v);
10531
10124
  }
10532
- /**
10533
- * Lightweight text formatter for simple read-only display use cases.
10534
- * This is used mainly for inline displays and summary UIs.
10535
- */
10125
+ /** Lightweight text formatter for simple read-only/summary display. */
10536
10126
  valueText() {
10537
10127
  const c = this.ctrl();
10538
10128
  const v = c?.value;
10539
10129
  if (v === null || v === undefined || v === '')
10540
10130
  return '--';
10541
- // Single-select option: resolve label from options
10542
- if (this.type === 'SS_OPTION') {
10543
- const opts = this.field?.configuration?.options ?? [];
10131
+ if (this.type() === 'SS_OPTION') {
10132
+ const opts = this.field()?.configuration?.options ?? [];
10544
10133
  if (typeof v !== 'object') {
10545
10134
  const hit = opts.find((o) => o?.value === v);
10546
- const label = hit?.label ?? v;
10547
- return this.translate.instant(label);
10135
+ return this.translate.instant(hit?.label ?? v);
10548
10136
  }
10549
- // Object value fallback
10550
- const label = v.label ?? v.value;
10551
- return this.translate.instant(label);
10137
+ return this.translate.instant(v.label ?? v.value);
10552
10138
  }
10553
- // Date formatting
10554
- if (this.type === 'DATE' && v instanceof Date) {
10139
+ if (this.type() === 'DATE' && v instanceof Date) {
10555
10140
  return v.toLocaleDateString();
10556
10141
  }
10557
- // Text editor / textarea: strip basic HTML tags for compact display
10558
- if (this.type === 'TEXT_EDITOR' || this.type === 'TEXT_AREA') {
10142
+ if (this.type() === 'TEXT_EDITOR' || this.type() === 'TEXT_AREA') {
10559
10143
  return String(v).replace(/<[^>]*>/g, '').trim() || '--';
10560
10144
  }
10561
10145
  return String(v);
10562
10146
  }
10563
- isCheckbox() {
10564
- return this.type === this.MetaFieldType.CHECKBOX;
10565
- }
10566
10147
  onDateSelected(val) {
10567
10148
  const ctrl = this.ctrl();
10568
10149
  if (!ctrl)
10569
10150
  return;
10570
- const d = val instanceof Date ? val : (val ? new Date(val) : null);
10151
+ const d = val instanceof Date ? val : val ? new Date(val) : null;
10571
10152
  if (!d || isNaN(d.getTime()))
10572
10153
  return;
10573
10154
  const normalized = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0);
10574
- // set without re-triggering loops
10155
+ // Silent write (no loops); bump the tick so error helpers re-evaluate.
10575
10156
  ctrl.setValue(normalized, { emitEvent: false });
10576
10157
  ctrl.markAsDirty();
10577
10158
  ctrl.markAsTouched();
10578
- this.cdr.markForCheck();
10159
+ this.manualTick.update((v) => v + 1);
10579
10160
  }
10580
10161
  onDateCleared() {
10581
10162
  const ctrl = this.ctrl();
@@ -10584,16 +10165,14 @@ class MetaFormFieldV2Component {
10584
10165
  ctrl.setValue(null, { emitEvent: false });
10585
10166
  ctrl.markAsDirty();
10586
10167
  ctrl.markAsTouched();
10587
- this.cdr.markForCheck();
10168
+ this.manualTick.update((v) => v + 1);
10588
10169
  }
10589
10170
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormFieldV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
10590
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaFormFieldV2Component, isStandalone: true, selector: "phoenix-meta-form-field-v2", inputs: { field: "field", form: "form", readOnly: "readOnly", disableForm: "disableForm" }, ngImport: i0, template: "<div [formGroup]=\"form\">\n @if (!field.hidden) {\n <div\n class=\"meta-field flex gap-2\"\n [class.flex-column]=\"!(isCheckbox() && !isReadOnly())\"\n [class.align-items-center]=\"isCheckbox() && !isReadOnly()\"\n [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\"\n >\n\n <!-- Skip the wrapper label only where the control supplies its own:\n - an editable checkbox renders an inline label beside the box;\n - an editable TIMEPERIOD self-labels (phoenix-meta-timeperiod).\n The read-only view has no inline/self label, so it keeps the wrapper\n label (otherwise a read-only checkbox shows a bare \"Yes\" with no field\n name). -->\n @if (userFriendlyMessage() && !(isCheckbox() && !isReadOnly()) && !(type === MetaFieldType.TIMEPERIOD && !isReadOnly())) {\n <label class=\"meta-label\" [attr.for]=\"key\">\n {{ userFriendlyMessage()! | translate }}\n @if (field.mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- READ ONLY (page-level ili field-level) -->\n @if (isReadOnly()) {\n <phoenix-read-only-input-v2 [field]=\"field\" [form]=\"form\"></phoenix-read-only-input-v2>\n } @else {\n @switch (type) {\n\n @case (MetaFieldType.TEXT) {\n <input pInputText [id]=\"key\" [formControlName]=\"key\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.URL) {\n <input pInputText [id]=\"key\" [formControlName]=\"key\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.PASSWORD) {\n <phoenix-meta-password-field-v2 [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\">\n </phoenix-meta-password-field-v2>\n }\n\n @case (MetaFieldType.TEXT_AREA) {\n <textarea pTextarea class=\"meta-textarea\" [id]=\"key\" [formControlName]=\"key\" fluid [autoResize]=\"false\" rows=\"5\"\n [readonly]=\"isReadOnly()\" [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\">\n </textarea>\n }\n\n @case (MetaFieldType.NUMBER) {\n <p-inputNumber [inputId]=\"key\" [formControlName]=\"key\">\n </p-inputNumber>\n }\n\n @case (MetaFieldType.DATE) {\n <p-datepicker\n [inputId]=\"key\"\n [formControlName]=\"key\"\n [showIcon]=\"true\"\n [readonlyInput]=\"true\"\n [showButtonBar]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onDateSelected($event)\"\n (onClearClick)=\"onDateCleared()\"\n ></p-datepicker>\n }\n\n @case (MetaFieldType.SS_OPTION) {\n <p-select [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key\" [showClear]=\"false\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.SS_OPTION_OBJECT_BASED) {\n <p-select [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" [formControlName]=\"key\"\n [showClear]=\"true\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.MS_OPTION) {\n <p-multiselect [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key\" [showClear]=\"false\" display=\"chip\" appendTo=\"body\">\n </p-multiselect>\n }\n\n @case (MetaFieldType.CHECKBOX) {\n <p-checkbox\n [inputId]=\"key\"\n [binary]=\"true\"\n [formControlName]=\"key\"\n [disabled]=\"isDisabled()\"\n ></p-checkbox>\n\n <label class=\"meta-inline-label\" [attr.for]=\"key\">\n {{ (userFriendlyMessage() ?? placeholderKey() ?? '') | translate }}\n @if (field.mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- advanced: preko postoje\u0107ih komponenti -->\n @case (MetaFieldType.TIMEPERIOD) {\n <phoenix-meta-timeperiod [formControlName]=\"key\" [control]=\"field\" [parentForm]=\"form\"></phoenix-meta-timeperiod>\n }\n\n @case (MetaFieldType.CURRENCY) {\n <phoenix-meta-currency [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-currency>\n }\n\n @case (MetaFieldType.START_DUE_DATE) {\n <phoenix-meta-start-due-date-v2\n [formControlName]=\"key\"\n [attr.data-cy]=\"'start-due-' + key\">\n </phoenix-meta-start-due-date-v2>\n }\n\n @case (MetaFieldType.TEXT_EDITOR) {\n <phoenix-meta-text-editor [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-text-editor>\n }\n\n @case (MetaFieldType.CHECKBOX_COLOR) {\n <phoenix-meta-checkbox-color-picker-v2\n [formControlName]=\"key\"\n [options]=\"(field.configuration.extra?.['colorGrid'] ?? [])\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-checkbox-color-picker-v2>\n }\n\n @case (MetaFieldType.SWITCH) {\n <phoenix-meta-switch-v2 [disable]=\"isDisabled()\" [formControlName]=\"key\" [hidden]=\"field.hidden ?? false\"\n [dataCy]=\"'switch-' + key\"></phoenix-meta-switch-v2>\n }\n\n @case (MetaFieldType.SELECT_BUTTON) {\n <phoenix-meta-select-button [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-select-button>\n }\n\n @case (MetaFieldType.ASSIGN) {\n <phoenix-meta-assign-responsible-v2\n [formControlName]=\"key\"\n [items]=\"(field.configuration.extra?.['items'] ?? [])\"\n [dialogHeaderKey]=\"(field.configuration.extra?.['dialogHeaderKey'] ?? 'LABELS.ASSIGN_RESPONSIBLE')\"\n ></phoenix-meta-assign-responsible-v2>\n }\n\n <!-- @case (MetaFieldType.ASSIGN_ASSET) {\n <phoenix-meta-assign-asset [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-assign-asset>\n } -->\n\n @case (MetaFieldType.COLOR) {\n <phoenix-meta-color-picker-v2\n [formControlName]=\"key\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-color-picker-v2>\n }\n\n @case (MetaFieldType.UPLOAD) {\n <phoenix-meta-upload [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-upload>\n }\n\n @case (MetaFieldType.UPLOAD_DRAG_DROP) {\n <phoenix-meta-upload-dragdrop [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-upload-dragdrop>\n }\n\n @case (MetaFieldType.LINKS_DATA) {\n <!-- <input pInputText [id]=\"key\" [formControlName]=\"key\" [readonly]=\"true\"> -->\n }\n\n @case (MetaFieldType.SLOT) { }\n\n @default {\n <input pInputText [id]=\"key\" [formControlName]=\"key\">\n }\n }\n\n @if (field.configuration.extra?.['dividerAfter']) {\n <div\n class=\"meta-divider\"\n [style.margin]=\"field.configuration.extra?.['dividerMargin'] ?? '12px 0'\"\n ></div>\n }\n }\n\n\n @if (!readOnly && showError()) {\n <small class=\"p-error block mt-1\">\n <i class=\"pi pi-info-circle mr-1\"></i>{{ errorText() }}\n </small>\n }\n </div>\n }\n</div>", styles: [".meta-field{width:100%}.meta-required{margin-left:4px;color:#ef4444}.meta-textarea{resize:none!important}.meta-inline-label{opacity:.9;margin:0;cursor:pointer}.p-inputtext.ng-invalid.ng-dirty{border-color:var(--p-inputtext-border-color)!important}.p-select.ng-invalid.ng-dirty{border-color:var(--p-select-border-color)!important}.meta-divider{width:100%;height:1px;background:#0000001f}:host-context(.dark-theme) .meta-divider{background:#ffffff26}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type:
10171
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaFormFieldV2Component, isStandalone: true, selector: "phoenix-meta-form-field-v2", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, disableForm: { classPropertyName: "disableForm", publicName: "disableForm", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"form()\">\n @if (!field().hidden) {\n <div\n class=\"meta-field flex gap-2\"\n [class.flex-column]=\"!(isCheckbox() && !isReadOnly())\"\n [class.align-items-center]=\"isCheckbox() && !isReadOnly()\"\n [style.order]=\"field().order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key()\"\n >\n\n <!-- Skip the wrapper label only where the control supplies its own:\n - an editable checkbox renders an inline label beside the box;\n - an editable TIMEPERIOD self-labels (phoenix-meta-timeperiod).\n The read-only view has no inline/self label, so it keeps the wrapper\n label (otherwise a read-only checkbox shows a bare \"Yes\" with no field\n name). -->\n @if (userFriendlyMessage() && !(isCheckbox() && !isReadOnly())) {\n <label class=\"meta-label\" [attr.for]=\"key()\">\n {{ userFriendlyMessage()! | translate }}\n @if (field().mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- READ ONLY (page-level ili field-level) -->\n @if (isReadOnly()) {\n <phoenix-read-only-input-v2 [field]=\"field()\" [form]=\"form()\"></phoenix-read-only-input-v2>\n } @else {\n @switch (type()) {\n\n @case (MetaFieldType.TEXT) {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.URL) {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.PASSWORD) {\n <phoenix-meta-password-field-v2 [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\">\n </phoenix-meta-password-field-v2>\n }\n\n @case (MetaFieldType.TEXT_AREA) {\n <textarea pTextarea class=\"meta-textarea\" [id]=\"key()\" [formControlName]=\"key()\" fluid [autoResize]=\"false\" rows=\"5\"\n [readonly]=\"isReadOnly()\" [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\">\n </textarea>\n }\n\n @case (MetaFieldType.NUMBER) {\n <p-inputNumber [inputId]=\"key()\" [formControlName]=\"key()\">\n </p-inputNumber>\n }\n\n @case (MetaFieldType.DATE) {\n <p-datepicker\n [inputId]=\"key()\"\n [formControlName]=\"key()\"\n [showIcon]=\"true\"\n [readonlyInput]=\"true\"\n [showButtonBar]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onDateSelected($event)\"\n (onClearClick)=\"onDateCleared()\"\n ></p-datepicker>\n }\n\n @case (MetaFieldType.SS_OPTION) {\n <p-select [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key()\" [showClear]=\"false\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.SS_OPTION_OBJECT_BASED) {\n <p-select [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" [formControlName]=\"key()\"\n [showClear]=\"true\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.MS_OPTION) {\n <p-multiselect [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key()\" [showClear]=\"false\" display=\"chip\" appendTo=\"body\">\n </p-multiselect>\n }\n\n @case (MetaFieldType.CHECKBOX) {\n <p-checkbox\n [inputId]=\"key()\"\n [binary]=\"true\"\n [formControlName]=\"key()\"\n [disabled]=\"isDisabled()\"\n ></p-checkbox>\n\n <label class=\"meta-inline-label\" [attr.for]=\"key()\">\n {{ (userFriendlyMessage() ?? placeholderKey() ?? '') | translate }}\n @if (field().mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- advanced: preko postoje\u0107ih komponenti -->\n @case (MetaFieldType.TIMEPERIOD) {\n <phoenix-meta-timeperiod-v2 [formControlName]=\"key()\" [key]=\"key()\" [disable]=\"isDisabled()\"></phoenix-meta-timeperiod-v2>\n }\n\n @case (MetaFieldType.CURRENCY) {\n <phoenix-meta-currency [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-currency>\n }\n\n @case (MetaFieldType.START_DUE_DATE) {\n <phoenix-meta-start-due-date-v2\n [formControlName]=\"key()\"\n [attr.data-cy]=\"'start-due-' + key()\">\n </phoenix-meta-start-due-date-v2>\n }\n\n @case (MetaFieldType.TEXT_EDITOR) {\n <phoenix-meta-text-editor [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-text-editor>\n }\n\n @case (MetaFieldType.CHECKBOX_COLOR) {\n <phoenix-meta-checkbox-color-picker-v2\n [formControlName]=\"key()\"\n [options]=\"(field().configuration.extra?.['colorGrid'] ?? [])\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-checkbox-color-picker-v2>\n }\n\n @case (MetaFieldType.SWITCH) {\n <phoenix-meta-switch-v2 [disable]=\"isDisabled()\" [formControlName]=\"key()\" [hidden]=\"field().hidden ?? false\"\n [dataCy]=\"'switch-' + key()\"></phoenix-meta-switch-v2>\n }\n\n @case (MetaFieldType.SELECT_BUTTON) {\n <phoenix-meta-select-button [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-select-button>\n }\n\n @case (MetaFieldType.ASSIGN) {\n <phoenix-meta-assign-responsible-v2\n [formControlName]=\"key()\"\n [items]=\"(field().configuration.extra?.['items'] ?? [])\"\n [dialogHeaderKey]=\"(field().configuration.extra?.['dialogHeaderKey'] ?? 'LABELS.ASSIGN_RESPONSIBLE')\"\n ></phoenix-meta-assign-responsible-v2>\n }\n\n <!-- @case (MetaFieldType.ASSIGN_ASSET) {\n <phoenix-meta-assign-asset [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-assign-asset>\n } -->\n\n @case (MetaFieldType.COLOR) {\n <phoenix-meta-color-picker-v2\n [formControlName]=\"key()\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-color-picker-v2>\n }\n\n @case (MetaFieldType.UPLOAD) {\n <phoenix-meta-upload [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-upload>\n }\n\n @case (MetaFieldType.UPLOAD_DRAG_DROP) {\n <phoenix-meta-upload-dragdrop [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-upload-dragdrop>\n }\n\n @case (MetaFieldType.LINKS_DATA) {\n <!-- <input pInputText [id]=\"key()\" [formControlName]=\"key()\" [readonly]=\"true\"> -->\n }\n\n @case (MetaFieldType.SLOT) { }\n\n @default {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\">\n }\n }\n\n @if (field().configuration.extra?.['dividerAfter']) {\n <div\n class=\"meta-divider\"\n [style.margin]=\"field().configuration.extra?.['dividerMargin'] ?? '12px 0'\"\n ></div>\n }\n }\n\n\n <!-- TIMEPERIOD carries its own calm format helper; don't duplicate a red error here. -->\n @if (!readOnly() && showError() && type() !== MetaFieldType.TIMEPERIOD) {\n <small class=\"p-error block mt-1\">\n <i class=\"pi pi-info-circle mr-1\"></i>{{ errorText() }}\n </small>\n }\n </div>\n }\n</div>\n", styles: [".meta-field{width:100%}.meta-required{margin-left:4px;color:#ef4444}.meta-textarea{resize:none!important}.meta-inline-label{opacity:.9;margin:0;cursor:pointer}.p-inputtext.ng-invalid.ng-dirty{border-color:var(--p-inputtext-border-color)!important}.p-select.ng-invalid.ng-dirty{border-color:var(--p-select-border-color)!important}.meta-divider{width:100%;height:1px;background:#0000001f}:host-context(.dark-theme) .meta-divider{background:#ffffff26}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type:
10591
10172
  // PrimeNG 20 base inputs
10592
10173
  InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3$7.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i3$5.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$4.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i5$2.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i2$6.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type:
10593
10174
  // Advanced / custom Phoenix fields
10594
- MetaTimeperiodComponent, selector: "phoenix-meta-timeperiod", inputs: ["control", "parentForm"] }, { kind: "component", type: MetaCurrencyComponent, selector: "phoenix-meta-currency" }, { kind: "component", type: MetaStartDueDateV2Component, selector: "phoenix-meta-start-due-date-v2", inputs: ["dataCy", "disable"] }, { kind: "component", type: MetaTextEditorComponent, selector: "phoenix-meta-text-editor", inputs: ["previewMode", "hideLabel"] }, { kind: "component", type: MetaCheckboxColorPickerV2Component, selector: "phoenix-meta-checkbox-color-picker-v2", inputs: ["options", "disable"] }, { kind: "component", type: MetaSwitchV2Component, selector: "phoenix-meta-switch-v2", inputs: ["disable", "hidden", "dataCy"] }, { kind: "component", type: MetaSelectButtonComponent, selector: "phoenix-meta-select-button" }, { kind: "component", type: MetaAssignResponsibleV2Component, selector: "phoenix-meta-assign-responsible-v2", inputs: ["items", "dialogHeaderKey"] }, { kind: "component", type:
10595
- // MetaAssignAssetComponent,
10596
- MetaPasswordFieldV2Component, selector: "phoenix-meta-password-field-v2", inputs: ["control", "ctrl", "disable", "hidden", "dataCy", "feedback", "autocomplete"] }, { kind: "component", type: MetaColorPickerV2Component, selector: "phoenix-meta-color-picker-v2", inputs: ["disable"] }, { kind: "component", type: MetaUploadComponent, selector: "phoenix-meta-upload" }, { kind: "component", type: MetaUploadComponentDragDrop, selector: "phoenix-meta-upload-dragdrop" }, { kind: "component", type:
10175
+ MetaTimeperiodV2Component, selector: "phoenix-meta-timeperiod-v2", inputs: ["key", "disable"] }, { kind: "component", type: MetaCurrencyComponent, selector: "phoenix-meta-currency" }, { kind: "component", type: MetaStartDueDateV2Component, selector: "phoenix-meta-start-due-date-v2", inputs: ["dataCy", "disable"] }, { kind: "component", type: MetaTextEditorComponent, selector: "phoenix-meta-text-editor", inputs: ["previewMode", "hideLabel"] }, { kind: "component", type: MetaCheckboxColorPickerV2Component, selector: "phoenix-meta-checkbox-color-picker-v2", inputs: ["options", "disable"] }, { kind: "component", type: MetaSwitchV2Component, selector: "phoenix-meta-switch-v2", inputs: ["disable", "hidden", "dataCy"] }, { kind: "component", type: MetaSelectButtonComponent, selector: "phoenix-meta-select-button" }, { kind: "component", type: MetaAssignResponsibleV2Component, selector: "phoenix-meta-assign-responsible-v2", inputs: ["items", "dialogHeaderKey"] }, { kind: "component", type: MetaPasswordFieldV2Component, selector: "phoenix-meta-password-field-v2", inputs: ["control", "ctrl", "disable", "hidden", "dataCy", "feedback", "autocomplete"] }, { kind: "component", type: MetaColorPickerV2Component, selector: "phoenix-meta-color-picker-v2", inputs: ["disable"] }, { kind: "component", type: MetaUploadComponent, selector: "phoenix-meta-upload" }, { kind: "component", type: MetaUploadComponentDragDrop, selector: "phoenix-meta-upload-dragdrop" }, { kind: "component", type:
10597
10176
  // Read-only renderer used when page or field is in read-only mode
10598
10177
  ReadOnlyInputV2Component, selector: "phoenix-read-only-input-v2", inputs: ["field", "form"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10599
10178
  }
@@ -10613,7 +10192,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10613
10192
  DatePickerModule,
10614
10193
  MessageModule,
10615
10194
  // Advanced / custom Phoenix fields
10616
- MetaTimeperiodComponent,
10195
+ MetaTimeperiodV2Component,
10617
10196
  MetaCurrencyComponent,
10618
10197
  MetaStartDueDateV2Component,
10619
10198
  MetaTextEditorComponent,
@@ -10621,25 +10200,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10621
10200
  MetaSwitchV2Component,
10622
10201
  MetaSelectButtonComponent,
10623
10202
  MetaAssignResponsibleV2Component,
10624
- // MetaAssignAssetComponent,
10625
10203
  MetaPasswordFieldV2Component,
10626
10204
  MetaColorPickerV2Component,
10627
10205
  MetaUploadComponent,
10628
10206
  MetaUploadComponentDragDrop,
10629
10207
  // Read-only renderer used when page or field is in read-only mode
10630
10208
  ReadOnlyInputV2Component,
10631
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [formGroup]=\"form\">\n @if (!field.hidden) {\n <div\n class=\"meta-field flex gap-2\"\n [class.flex-column]=\"!(isCheckbox() && !isReadOnly())\"\n [class.align-items-center]=\"isCheckbox() && !isReadOnly()\"\n [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\"\n >\n\n <!-- Skip the wrapper label only where the control supplies its own:\n - an editable checkbox renders an inline label beside the box;\n - an editable TIMEPERIOD self-labels (phoenix-meta-timeperiod).\n The read-only view has no inline/self label, so it keeps the wrapper\n label (otherwise a read-only checkbox shows a bare \"Yes\" with no field\n name). -->\n @if (userFriendlyMessage() && !(isCheckbox() && !isReadOnly()) && !(type === MetaFieldType.TIMEPERIOD && !isReadOnly())) {\n <label class=\"meta-label\" [attr.for]=\"key\">\n {{ userFriendlyMessage()! | translate }}\n @if (field.mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- READ ONLY (page-level ili field-level) -->\n @if (isReadOnly()) {\n <phoenix-read-only-input-v2 [field]=\"field\" [form]=\"form\"></phoenix-read-only-input-v2>\n } @else {\n @switch (type) {\n\n @case (MetaFieldType.TEXT) {\n <input pInputText [id]=\"key\" [formControlName]=\"key\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.URL) {\n <input pInputText [id]=\"key\" [formControlName]=\"key\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.PASSWORD) {\n <phoenix-meta-password-field-v2 [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\">\n </phoenix-meta-password-field-v2>\n }\n\n @case (MetaFieldType.TEXT_AREA) {\n <textarea pTextarea class=\"meta-textarea\" [id]=\"key\" [formControlName]=\"key\" fluid [autoResize]=\"false\" rows=\"5\"\n [readonly]=\"isReadOnly()\" [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\">\n </textarea>\n }\n\n @case (MetaFieldType.NUMBER) {\n <p-inputNumber [inputId]=\"key\" [formControlName]=\"key\">\n </p-inputNumber>\n }\n\n @case (MetaFieldType.DATE) {\n <p-datepicker\n [inputId]=\"key\"\n [formControlName]=\"key\"\n [showIcon]=\"true\"\n [readonlyInput]=\"true\"\n [showButtonBar]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onDateSelected($event)\"\n (onClearClick)=\"onDateCleared()\"\n ></p-datepicker>\n }\n\n @case (MetaFieldType.SS_OPTION) {\n <p-select [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key\" [showClear]=\"false\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.SS_OPTION_OBJECT_BASED) {\n <p-select [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" [formControlName]=\"key\"\n [showClear]=\"true\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.MS_OPTION) {\n <p-multiselect [inputId]=\"key\" [options]=\"field.configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key\" [showClear]=\"false\" display=\"chip\" appendTo=\"body\">\n </p-multiselect>\n }\n\n @case (MetaFieldType.CHECKBOX) {\n <p-checkbox\n [inputId]=\"key\"\n [binary]=\"true\"\n [formControlName]=\"key\"\n [disabled]=\"isDisabled()\"\n ></p-checkbox>\n\n <label class=\"meta-inline-label\" [attr.for]=\"key\">\n {{ (userFriendlyMessage() ?? placeholderKey() ?? '') | translate }}\n @if (field.mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- advanced: preko postoje\u0107ih komponenti -->\n @case (MetaFieldType.TIMEPERIOD) {\n <phoenix-meta-timeperiod [formControlName]=\"key\" [control]=\"field\" [parentForm]=\"form\"></phoenix-meta-timeperiod>\n }\n\n @case (MetaFieldType.CURRENCY) {\n <phoenix-meta-currency [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-currency>\n }\n\n @case (MetaFieldType.START_DUE_DATE) {\n <phoenix-meta-start-due-date-v2\n [formControlName]=\"key\"\n [attr.data-cy]=\"'start-due-' + key\">\n </phoenix-meta-start-due-date-v2>\n }\n\n @case (MetaFieldType.TEXT_EDITOR) {\n <phoenix-meta-text-editor [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-text-editor>\n }\n\n @case (MetaFieldType.CHECKBOX_COLOR) {\n <phoenix-meta-checkbox-color-picker-v2\n [formControlName]=\"key\"\n [options]=\"(field.configuration.extra?.['colorGrid'] ?? [])\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-checkbox-color-picker-v2>\n }\n\n @case (MetaFieldType.SWITCH) {\n <phoenix-meta-switch-v2 [disable]=\"isDisabled()\" [formControlName]=\"key\" [hidden]=\"field.hidden ?? false\"\n [dataCy]=\"'switch-' + key\"></phoenix-meta-switch-v2>\n }\n\n @case (MetaFieldType.SELECT_BUTTON) {\n <phoenix-meta-select-button [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-select-button>\n }\n\n @case (MetaFieldType.ASSIGN) {\n <phoenix-meta-assign-responsible-v2\n [formControlName]=\"key\"\n [items]=\"(field.configuration.extra?.['items'] ?? [])\"\n [dialogHeaderKey]=\"(field.configuration.extra?.['dialogHeaderKey'] ?? 'LABELS.ASSIGN_RESPONSIBLE')\"\n ></phoenix-meta-assign-responsible-v2>\n }\n\n <!-- @case (MetaFieldType.ASSIGN_ASSET) {\n <phoenix-meta-assign-asset [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-assign-asset>\n } -->\n\n @case (MetaFieldType.COLOR) {\n <phoenix-meta-color-picker-v2\n [formControlName]=\"key\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-color-picker-v2>\n }\n\n @case (MetaFieldType.UPLOAD) {\n <phoenix-meta-upload [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-upload>\n }\n\n @case (MetaFieldType.UPLOAD_DRAG_DROP) {\n <phoenix-meta-upload-dragdrop [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\"\n [parentForm]=\"form\"></phoenix-meta-upload-dragdrop>\n }\n\n @case (MetaFieldType.LINKS_DATA) {\n <!-- <input pInputText [id]=\"key\" [formControlName]=\"key\" [readonly]=\"true\"> -->\n }\n\n @case (MetaFieldType.SLOT) { }\n\n @default {\n <input pInputText [id]=\"key\" [formControlName]=\"key\">\n }\n }\n\n @if (field.configuration.extra?.['dividerAfter']) {\n <div\n class=\"meta-divider\"\n [style.margin]=\"field.configuration.extra?.['dividerMargin'] ?? '12px 0'\"\n ></div>\n }\n }\n\n\n @if (!readOnly && showError()) {\n <small class=\"p-error block mt-1\">\n <i class=\"pi pi-info-circle mr-1\"></i>{{ errorText() }}\n </small>\n }\n </div>\n }\n</div>", styles: [".meta-field{width:100%}.meta-required{margin-left:4px;color:#ef4444}.meta-textarea{resize:none!important}.meta-inline-label{opacity:.9;margin:0;cursor:pointer}.p-inputtext.ng-invalid.ng-dirty{border-color:var(--p-inputtext-border-color)!important}.p-select.ng-invalid.ng-dirty{border-color:var(--p-select-border-color)!important}.meta-divider{width:100%;height:1px;background:#0000001f}:host-context(.dark-theme) .meta-divider{background:#ffffff26}\n"] }]
10632
- }], propDecorators: { field: [{
10633
- type: Input,
10634
- args: [{ required: true }]
10635
- }], form: [{
10636
- type: Input,
10637
- args: [{ required: true }]
10638
- }], readOnly: [{
10639
- type: Input
10640
- }], disableForm: [{
10641
- type: Input
10642
- }] } });
10209
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [formGroup]=\"form()\">\n @if (!field().hidden) {\n <div\n class=\"meta-field flex gap-2\"\n [class.flex-column]=\"!(isCheckbox() && !isReadOnly())\"\n [class.align-items-center]=\"isCheckbox() && !isReadOnly()\"\n [style.order]=\"field().order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key()\"\n >\n\n <!-- Skip the wrapper label only where the control supplies its own:\n - an editable checkbox renders an inline label beside the box;\n - an editable TIMEPERIOD self-labels (phoenix-meta-timeperiod).\n The read-only view has no inline/self label, so it keeps the wrapper\n label (otherwise a read-only checkbox shows a bare \"Yes\" with no field\n name). -->\n @if (userFriendlyMessage() && !(isCheckbox() && !isReadOnly())) {\n <label class=\"meta-label\" [attr.for]=\"key()\">\n {{ userFriendlyMessage()! | translate }}\n @if (field().mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- READ ONLY (page-level ili field-level) -->\n @if (isReadOnly()) {\n <phoenix-read-only-input-v2 [field]=\"field()\" [form]=\"form()\"></phoenix-read-only-input-v2>\n } @else {\n @switch (type()) {\n\n @case (MetaFieldType.TEXT) {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.URL) {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\"\n [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\" [readonly]=\"isReadOnly()\">\n }\n\n @case (MetaFieldType.PASSWORD) {\n <phoenix-meta-password-field-v2 [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\">\n </phoenix-meta-password-field-v2>\n }\n\n @case (MetaFieldType.TEXT_AREA) {\n <textarea pTextarea class=\"meta-textarea\" [id]=\"key()\" [formControlName]=\"key()\" fluid [autoResize]=\"false\" rows=\"5\"\n [readonly]=\"isReadOnly()\" [attr.placeholder]=\"placeholderKey() ? (placeholderKey()! | translate) : null\">\n </textarea>\n }\n\n @case (MetaFieldType.NUMBER) {\n <p-inputNumber [inputId]=\"key()\" [formControlName]=\"key()\">\n </p-inputNumber>\n }\n\n @case (MetaFieldType.DATE) {\n <p-datepicker\n [inputId]=\"key()\"\n [formControlName]=\"key()\"\n [showIcon]=\"true\"\n [readonlyInput]=\"true\"\n [showButtonBar]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onDateSelected($event)\"\n (onClearClick)=\"onDateCleared()\"\n ></p-datepicker>\n }\n\n @case (MetaFieldType.SS_OPTION) {\n <p-select [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key()\" [showClear]=\"false\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.SS_OPTION_OBJECT_BASED) {\n <p-select [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" [formControlName]=\"key()\"\n [showClear]=\"true\" appendTo=\"body\">\n </p-select>\n }\n\n @case (MetaFieldType.MS_OPTION) {\n <p-multiselect [inputId]=\"key()\" [options]=\"field().configuration.options ?? []\" optionLabel=\"label\" optionValue=\"value\"\n [formControlName]=\"key()\" [showClear]=\"false\" display=\"chip\" appendTo=\"body\">\n </p-multiselect>\n }\n\n @case (MetaFieldType.CHECKBOX) {\n <p-checkbox\n [inputId]=\"key()\"\n [binary]=\"true\"\n [formControlName]=\"key()\"\n [disabled]=\"isDisabled()\"\n ></p-checkbox>\n\n <label class=\"meta-inline-label\" [attr.for]=\"key()\">\n {{ (userFriendlyMessage() ?? placeholderKey() ?? '') | translate }}\n @if (field().mandatory) { <span class=\"meta-required\">*</span> }\n </label>\n }\n\n <!-- advanced: preko postoje\u0107ih komponenti -->\n @case (MetaFieldType.TIMEPERIOD) {\n <phoenix-meta-timeperiod-v2 [formControlName]=\"key()\" [key]=\"key()\" [disable]=\"isDisabled()\"></phoenix-meta-timeperiod-v2>\n }\n\n @case (MetaFieldType.CURRENCY) {\n <phoenix-meta-currency [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-currency>\n }\n\n @case (MetaFieldType.START_DUE_DATE) {\n <phoenix-meta-start-due-date-v2\n [formControlName]=\"key()\"\n [attr.data-cy]=\"'start-due-' + key()\">\n </phoenix-meta-start-due-date-v2>\n }\n\n @case (MetaFieldType.TEXT_EDITOR) {\n <phoenix-meta-text-editor [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-text-editor>\n }\n\n @case (MetaFieldType.CHECKBOX_COLOR) {\n <phoenix-meta-checkbox-color-picker-v2\n [formControlName]=\"key()\"\n [options]=\"(field().configuration.extra?.['colorGrid'] ?? [])\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-checkbox-color-picker-v2>\n }\n\n @case (MetaFieldType.SWITCH) {\n <phoenix-meta-switch-v2 [disable]=\"isDisabled()\" [formControlName]=\"key()\" [hidden]=\"field().hidden ?? false\"\n [dataCy]=\"'switch-' + key()\"></phoenix-meta-switch-v2>\n }\n\n @case (MetaFieldType.SELECT_BUTTON) {\n <phoenix-meta-select-button [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-select-button>\n }\n\n @case (MetaFieldType.ASSIGN) {\n <phoenix-meta-assign-responsible-v2\n [formControlName]=\"key()\"\n [items]=\"(field().configuration.extra?.['items'] ?? [])\"\n [dialogHeaderKey]=\"(field().configuration.extra?.['dialogHeaderKey'] ?? 'LABELS.ASSIGN_RESPONSIBLE')\"\n ></phoenix-meta-assign-responsible-v2>\n }\n\n <!-- @case (MetaFieldType.ASSIGN_ASSET) {\n <phoenix-meta-assign-asset [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-assign-asset>\n } -->\n\n @case (MetaFieldType.COLOR) {\n <phoenix-meta-color-picker-v2\n [formControlName]=\"key()\"\n [disable]=\"isDisabled()\">\n </phoenix-meta-color-picker-v2>\n }\n\n @case (MetaFieldType.UPLOAD) {\n <phoenix-meta-upload [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-upload>\n }\n\n @case (MetaFieldType.UPLOAD_DRAG_DROP) {\n <phoenix-meta-upload-dragdrop [disable]=\"isDisabled()\" [formControlName]=\"key()\" [control]=\"field()\"\n [parentForm]=\"form()\"></phoenix-meta-upload-dragdrop>\n }\n\n @case (MetaFieldType.LINKS_DATA) {\n <!-- <input pInputText [id]=\"key()\" [formControlName]=\"key()\" [readonly]=\"true\"> -->\n }\n\n @case (MetaFieldType.SLOT) { }\n\n @default {\n <input pInputText [id]=\"key()\" [formControlName]=\"key()\">\n }\n }\n\n @if (field().configuration.extra?.['dividerAfter']) {\n <div\n class=\"meta-divider\"\n [style.margin]=\"field().configuration.extra?.['dividerMargin'] ?? '12px 0'\"\n ></div>\n }\n }\n\n\n <!-- TIMEPERIOD carries its own calm format helper; don't duplicate a red error here. -->\n @if (!readOnly() && showError() && type() !== MetaFieldType.TIMEPERIOD) {\n <small class=\"p-error block mt-1\">\n <i class=\"pi pi-info-circle mr-1\"></i>{{ errorText() }}\n </small>\n }\n </div>\n }\n</div>\n", styles: [".meta-field{width:100%}.meta-required{margin-left:4px;color:#ef4444}.meta-textarea{resize:none!important}.meta-inline-label{opacity:.9;margin:0;cursor:pointer}.p-inputtext.ng-invalid.ng-dirty{border-color:var(--p-inputtext-border-color)!important}.p-select.ng-invalid.ng-dirty{border-color:var(--p-select-border-color)!important}.meta-divider{width:100%;height:1px;background:#0000001f}:host-context(.dark-theme) .meta-divider{background:#ffffff26}\n"] }]
10210
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], disableForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableForm", required: false }] }] } });
10643
10211
 
10644
10212
  /**
10645
10213
  * Splits a flat list of fields into row chunks for grid rendering.
@@ -11108,161 +10676,135 @@ function flattenControls(input) {
11108
10676
  .filter((x) => !!x?.configuration?.key);
11109
10677
  }
11110
10678
 
10679
+ /**
10680
+ * V2 form host. Signal-based (OnPush): inputs are `input()` signals, the
10681
+ * template-facing structure (`hasControls`/`isGrouped`/`groupedControls`/
10682
+ * `controlRows`) is `computed()` (so `splitControlsIntoRows` runs only when the
10683
+ * config changes, not on every CD pass), and `expandedGroupIds` is a signal.
10684
+ *
10685
+ * Reactive forms aren't signal-native, so control building stays imperative: the
10686
+ * first build runs synchronously in `ngOnInit` (controls exist before first
10687
+ * paint), and an `effect()` re-runs the same build when `config`/`form`/`readOnly`
10688
+ * change — with the same change-detection semantics the old `ngOnChanges` had.
10689
+ */
11111
10690
  class MetaFormV2Component {
11112
- /** Form instance created/owned by the parent (dialog/page) */
11113
- form;
11114
- /**
11115
- * V2 metadata/config:
11116
- * - controls (grouped or flat)
11117
- * - initialValues
11118
- * - submitValidators (run on submit only)
11119
- * - setupDependencies (optional runtime bindings)
11120
- */
11121
- config;
11122
- /**
11123
- * Page-level readOnly state controlled by parent.
11124
- * Used both for rendering and for "enter edit mode" behavior.
11125
- */
11126
- readOnly = false;
11127
- /** Optional layout customization for inner content wrapper */
11128
- contentStyle = null;
11129
- /** Optional class name(s) applied to inner content wrapper */
11130
- contentClass = null;
11131
- /** Builds/ensures controls exist (ensures validators & default values are wired) */
10691
+ /** Form instance created/owned by the parent (dialog/page). */
10692
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
10693
+ /** V2 metadata/config (controls, initialValues, submitValidators, setupDependencies). */
10694
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
10695
+ /** Page-level readOnly state (rendering + "enter edit mode" behavior). */
10696
+ readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
10697
+ /** Optional layout customization for the inner content wrapper. */
10698
+ contentStyle = input(null, ...(ngDevMode ? [{ debugName: "contentStyle" }] : []));
10699
+ /** Optional class name(s) for the inner content wrapper. */
10700
+ contentClass = input(null, ...(ngDevMode ? [{ debugName: "contentClass" }] : []));
11132
10701
  fb = inject(FormBuilder);
11133
- /** Registers and executes submit-only validators (async validation on submit) */
11134
10702
  submitValidator = inject(MetaSubmitValidatorService);
11135
- /** Used for validator localization (lang-dependent validators / messages) */
11136
10703
  translate = inject(TranslateService);
11137
- /**
11138
- * A lightweight signature of the current metadata structure.
11139
- * Used to detect when the form schema changes (and avoid unnecessary resets).
11140
- */
10704
+ /** Signature of the current schema (key+type only) to detect real schema changes. */
11141
10705
  lastSignature = '';
11142
- /**
11143
- * Cleanup function returned by setupDependencies (if any).
11144
- * Called only when metadata structure changes or on destroy.
11145
- */
10706
+ /** Cleanup from setupDependencies; called on schema change or destroy. */
11146
10707
  depCleanup;
10708
+ /** Previous input references, to reproduce the old SimpleChanges semantics. */
10709
+ prevConfig = null;
10710
+ prevForm = null;
10711
+ prevReadOnly = false;
10712
+ initialized = false;
10713
+ /** PrimeNG Accordion opened-panel ids. */
10714
+ expandedGroupIds = signal([], ...(ngDevMode ? [{ debugName: "expandedGroupIds" }] : []));
10715
+ // ---- Memoized, config-derived structure for the template ----
10716
+ hasControls = computed(() => Array.isArray(this.config()?.controls) && this.config().controls.length > 0, ...(ngDevMode ? [{ debugName: "hasControls" }] : []));
10717
+ /** Heuristic: grouped config has "ctrl" on the first element. */
10718
+ isGrouped = computed(() => {
10719
+ const c = this.config()?.controls ?? [];
10720
+ return !!c[0]?.ctrl;
10721
+ }, ...(ngDevMode ? [{ debugName: "isGrouped" }] : []));
10722
+ groupedControls = computed(() => this.config()?.controls ?? [], ...(ngDevMode ? [{ debugName: "groupedControls" }] : []));
10723
+ flatControls = computed(() => this.config()?.controls ?? [], ...(ngDevMode ? [{ debugName: "flatControls" }] : []));
10724
+ /** Flat schema split into rows, honoring `style.newRow` hard breaks. */
10725
+ controlRows = computed(() => splitControlsIntoRows(this.flatControls()), ...(ngDevMode ? [{ debugName: "controlRows" }] : []));
10726
+ constructor() {
10727
+ // Reactive updates on input changes. The first run happens during the first
10728
+ // CD (after ngOnInit's synchronous build), and no-ops via the change guard.
10729
+ effect(() => this.rebuild());
10730
+ }
10731
+ ngOnInit() {
10732
+ // First build runs synchronously so controls exist before the first paint.
10733
+ this.rebuild();
10734
+ }
11147
10735
  /**
11148
- * PrimeNG Accordion "value" for opened panels.
11149
- * For multiple panels, PrimeNG expects an array of ids.
10736
+ * Ensures controls, patches initial values, wires validators/dependencies and
10737
+ * initializes accordion state only when config/form/readOnly actually change
10738
+ * (same early-exit the old ngOnChanges had).
11150
10739
  */
11151
- expandedGroupIds = [];
11152
- ngOnChanges(changes) {
11153
- if (!this.form || !this.config)
10740
+ rebuild() {
10741
+ const form = this.form();
10742
+ const config = this.config();
10743
+ const readOnly = this.readOnly();
10744
+ if (!form || !config)
11154
10745
  return;
11155
- /**
11156
- * Flatten metadata controls to a single list for:
11157
- * - building/enforcing FormControls
11158
- * - dependency binding
11159
- * - signature calculation
11160
- */
11161
- const flat = flattenControls(this.config.controls);
11162
- /**
11163
- * Signature is used to detect real schema changes.
11164
- * We intentionally ignore labels/props and track key+type only.
11165
- */
11166
- const signature = flat.map((f) => `${f.configuration.key}:${f.configuration.type}`).join('|');
11167
- /** "config changed" includes initialValues, validators, dependencies, etc. */
11168
- const configChanged = !!changes['config'];
11169
- /** "form changed" means parent passed a different FormGroup instance */
11170
- const formChanged = !!changes['form'];
11171
- /** schema changed if the signature differs from the last one */
10746
+ const flat = flattenControls(config.controls);
10747
+ const signature = flat
10748
+ .map((f) => `${f.configuration.key}:${f.configuration.type}`)
10749
+ .join('|');
10750
+ const configChanged = config !== this.prevConfig;
10751
+ const formChanged = form !== this.prevForm;
11172
10752
  const metaChanged = signature !== this.lastSignature;
11173
- /**
11174
- * Special case: entering edit mode (readOnly true -> false).
11175
- * Used to selectively show validation for already-filled values.
11176
- */
11177
- const enteringEdit = !!changes['readOnly'] &&
11178
- changes['readOnly'].previousValue === true &&
11179
- changes['readOnly'].currentValue === false;
11180
- // If nothing relevant changed, exit early to avoid unnecessary work.
10753
+ const enteringEdit = this.initialized && this.prevReadOnly === true && readOnly === false;
10754
+ // Record inputs seen this run (before the early-exit, so the guard is stable).
10755
+ this.prevConfig = config;
10756
+ this.prevForm = form;
10757
+ this.prevReadOnly = readOnly;
10758
+ this.initialized = true;
11181
10759
  if (!configChanged && !formChanged && !metaChanged && !enteringEdit)
11182
10760
  return;
11183
- /**
11184
- * Dependencies are tied to the metadata structure.
11185
- * If schema changed, cleanup old subscriptions/bindings first.
11186
- */
11187
10761
  if (configChanged || metaChanged) {
11188
10762
  this.depCleanup?.();
11189
10763
  this.depCleanup = undefined;
11190
10764
  }
11191
- /**
11192
- * Ensure controls exist on the passed FormGroup and sync validators.
11193
- * This is where missing controls are added and validator wiring is applied.
11194
- */
11195
- const initial = this.config.initialValues ?? {};
11196
- ensureControlsV2(this.fb, this.form, flat, initial, { lang: this.translate.currentLang });
11197
- /**
11198
- * Patch initial values without emitting changes:
11199
- * - prevents loops
11200
- * - keeps create/edit initialization silent
11201
- */
11202
- this.form.patchValue(initial, { emitEvent: false });
11203
- this.form.updateValueAndValidity({ emitEvent: false });
11204
- /**
11205
- * Initialize accordion open panels ONLY when schema changes.
11206
- * IMPORTANT: do NOT do this on readOnly toggle, otherwise user-collapsed state resets.
11207
- */
10765
+ const initial = config.initialValues ?? {};
10766
+ ensureControlsV2(this.fb, form, flat, initial, { lang: this.translate.currentLang });
10767
+ // Patch initial values silently (no loops, silent create/edit init).
10768
+ form.patchValue(initial, { emitEvent: false });
10769
+ form.updateValueAndValidity({ emitEvent: false });
10770
+ // Initialize accordion open panels ONLY on schema change (don't reset the
10771
+ // user-collapsed state on a readOnly toggle).
11208
10772
  if (metaChanged) {
11209
- if (this.isGrouped) {
11210
- const groups = this.groupedControls ?? [];
11211
- this.expandedGroupIds = groups
10773
+ if (this.isGrouped()) {
10774
+ const groups = this.groupedControls() ?? [];
10775
+ this.expandedGroupIds.set(groups
11212
10776
  .filter((g) => !g?.collapsed)
11213
10777
  .map((g) => this.panelValue(g))
11214
- .filter(Boolean);
10778
+ .filter(Boolean));
11215
10779
  }
11216
10780
  else {
11217
- this.expandedGroupIds = [];
10781
+ this.expandedGroupIds.set([]);
11218
10782
  }
11219
10783
  }
11220
- /**
11221
- * Register submit-only validators.
11222
- * Safe to call even if empty; service will attach necessary structures internally.
11223
- */
11224
- this.submitValidator.register(this.form, this.config.submitValidators);
11225
- /**
11226
- * Bind dependencies ONLY when schema changes.
11227
- * setupDependencies can subscribe to valueChanges, set options, reset fields, etc.
11228
- * If it returns a function, we store it for cleanup.
11229
- */
11230
- if ((configChanged || metaChanged) && this.config.setupDependencies) {
11231
- const maybeCleanup = this.config.setupDependencies({
11232
- form: this.form,
10784
+ this.submitValidator.register(form, config.submitValidators);
10785
+ if ((configChanged || metaChanged) && config.setupDependencies) {
10786
+ const maybeCleanup = config.setupDependencies({
10787
+ form,
11233
10788
  flatControls: flat,
11234
10789
  initialValues: initial,
11235
- getControl: (k) => this.form.get(k),
10790
+ getControl: (k) => form.get(k),
11236
10791
  findField: (k) => flat.find((f) => f.configuration.key === k) ?? null,
11237
10792
  });
11238
10793
  if (typeof maybeCleanup === 'function')
11239
10794
  this.depCleanup = maybeCleanup;
11240
10795
  }
11241
- /**
11242
- * Entering edit mode:
11243
- * - mark controls as touched ONLY if they already have a meaningful value
11244
- * - expand groups that contain "visible invalid" controls (invalid + touched/dirty)
11245
- * This prevents CREATE dialogs from showing "required" errors immediately.
11246
- */
11247
10796
  if (enteringEdit) {
11248
10797
  queueMicrotask(() => {
11249
10798
  this.touchAndValidateOnlyFilledControls();
11250
10799
  this.expandVisibleInvalidGroupsUnion();
11251
10800
  });
11252
10801
  }
11253
- // Store signature for the next change detection pass
11254
10802
  this.lastSignature = signature;
11255
10803
  }
11256
- /**
11257
- * PrimeNG Accordion emits value as:
11258
- * - single id (string/number)
11259
- * - array of ids
11260
- * We normalize everything into string[] for stable internal state.
11261
- */
10804
+ /** Normalizes PrimeNG Accordion value into a stable string[]. */
11262
10805
  onAccordionValueChange(v) {
11263
- this.expandedGroupIds = this.normalizeAccordionValue(v);
10806
+ this.expandedGroupIds.set(this.normalizeAccordionValue(v));
11264
10807
  }
11265
- /** Normalizes Accordion value into a stable string[] representation */
11266
10808
  normalizeAccordionValue(v) {
11267
10809
  if (Array.isArray(v))
11268
10810
  return v.map((x) => `${x}`);
@@ -11271,74 +10813,30 @@ class MetaFormV2Component {
11271
10813
  return [`${v}`];
11272
10814
  }
11273
10815
  // ---------------- template helpers ----------------
11274
- /** True when metadata contains at least one control definition */
11275
- get hasControls() {
11276
- return Array.isArray(this.config?.controls) && this.config.controls.length > 0;
11277
- }
11278
- /**
11279
- * Heuristic: grouped config has "ctrl" on first element.
11280
- * (Keeps template simple and avoids extra schema fields.)
11281
- */
11282
- get isGrouped() {
11283
- const c = this.config?.controls ?? [];
11284
- return !!c[0]?.ctrl;
11285
- }
11286
- /** Returns grouped schema structure (accordion groups) */
11287
- get groupedControls() {
11288
- return this.config?.controls ?? [];
11289
- }
11290
- /** Returns flat schema structure (grid mode) */
11291
- get flatControls() {
11292
- return this.config?.controls ?? [];
11293
- }
11294
- /**
11295
- * Flat schema split into row chunks, honoring `style.newRow` hard breaks.
11296
- * Used by the non-grouped grid template.
11297
- */
11298
- get controlRows() {
11299
- return splitControlsIntoRows(this.flatControls);
11300
- }
11301
- /** TrackBy for group rendering */
10816
+ /** TrackBy for group rendering. */
11302
10817
  groupTrack(g, idx) {
11303
10818
  return g?.id ?? idx;
11304
10819
  }
11305
- /**
11306
- * PrimeNG accordion panel `value` must match accordion `value` type.
11307
- * We always convert group id to string for consistent behavior.
11308
- */
10820
+ /** Group id as a string (matches the accordion `value` type). */
11309
10821
  panelValue(g) {
11310
10822
  const id = g?.id;
11311
10823
  return id === null || id === undefined ? '' : `${id}`;
11312
10824
  }
11313
10825
  // ---------------- core behavior ----------------
11314
- /**
11315
- * Marks & validates ONLY controls that already have meaningful values.
11316
- * This is used when switching from readOnly -> edit mode to avoid:
11317
- * - triggering "required" errors for empty fields
11318
- * - expanding groups based on empty mandatory fields on CREATE dialogs
11319
- */
10826
+ /** Marks & validates ONLY controls that already have meaningful values. */
11320
10827
  touchAndValidateOnlyFilledControls() {
11321
- const controls = this.form?.controls ?? {};
11322
- for (const [key, ctrl] of Object.entries(controls)) {
10828
+ const controls = this.form()?.controls ?? {};
10829
+ for (const [, ctrl] of Object.entries(controls)) {
11323
10830
  if (!ctrl)
11324
10831
  continue;
11325
- const value = ctrl.value;
11326
- // Touch only if this field is already populated (edit case) or prefilled.
11327
- if (this.hasMeaningfulValue(value)) {
10832
+ if (this.hasMeaningfulValue(ctrl.value)) {
11328
10833
  ctrl.markAsTouched();
11329
10834
  ctrl.updateValueAndValidity({ emitEvent: true });
11330
10835
  }
11331
10836
  }
11332
- // Optional: keep form status consistent after selective updates
11333
- this.form.updateValueAndValidity({ emitEvent: true });
10837
+ this.form().updateValueAndValidity({ emitEvent: true });
11334
10838
  }
11335
- /**
11336
- * Defines what counts as a "meaningful" value:
11337
- * - non-empty strings
11338
- * - numbers / booleans
11339
- * - non-empty arrays
11340
- * - objects with common identifiers (key/uuid/id) or any own keys
11341
- */
10839
+ /** What counts as a "meaningful" value (non-empty string/number/boolean/array/object). */
11342
10840
  hasMeaningfulValue(v) {
11343
10841
  if (v === null || v === undefined)
11344
10842
  return false;
@@ -11351,61 +10849,46 @@ class MetaFormV2Component {
11351
10849
  if (Array.isArray(v))
11352
10850
  return v.length > 0;
11353
10851
  if (typeof v === 'object') {
11354
- // Common selection shapes: { key }, { uuid }, { id }, etc.
11355
10852
  if ('key' in v && v.key != null && `${v.key}`.trim() !== '')
11356
10853
  return true;
11357
10854
  if ('uuid' in v && v.uuid != null && `${v.uuid}`.trim() !== '')
11358
10855
  return true;
11359
10856
  if ('id' in v && v.id != null && `${v.id}`.trim() !== '')
11360
10857
  return true;
11361
- // Fallback: any own keys
11362
10858
  return Object.keys(v).length > 0;
11363
10859
  }
11364
10860
  return false;
11365
10861
  }
11366
- /**
11367
- * "Visible invalid" means:
11368
- * - invalid
11369
- * - AND user has interacted with it (touched or dirty)
11370
- * This matches typical UI behavior: show errors only after interaction.
11371
- */
10862
+ /** "Visible invalid" = invalid AND interacted with (touched/dirty). */
11372
10863
  isVisibleInvalid(ctrl) {
11373
10864
  if (!ctrl)
11374
10865
  return false;
11375
10866
  return ctrl.invalid && (ctrl.touched || ctrl.dirty);
11376
10867
  }
11377
- /**
11378
- * Checks if a group contains at least one visible invalid control.
11379
- * Used to auto-expand groups when entering edit mode.
11380
- */
11381
10868
  groupHasVisibleInvalid(g) {
11382
10869
  const keys = (g?.ctrl ?? [])
11383
10870
  .map((f) => f?.configuration?.key)
11384
10871
  .filter(Boolean);
11385
- return keys.some((k) => this.isVisibleInvalid(this.form.get(k)));
10872
+ return keys.some((k) => this.isVisibleInvalid(this.form().get(k)));
11386
10873
  }
11387
- /**
11388
- * Expands all groups that contain visible invalid controls,
11389
- * while preserving any groups already expanded by the user.
11390
- */
10874
+ /** Expands groups with visible-invalid controls, preserving user-expanded ones. */
11391
10875
  expandVisibleInvalidGroupsUnion() {
11392
- if (!this.isGrouped)
10876
+ if (!this.isGrouped())
11393
10877
  return;
11394
- const groups = this.groupedControls ?? [];
10878
+ const groups = this.groupedControls() ?? [];
11395
10879
  const invalidIds = groups
11396
10880
  .filter((g) => this.groupHasVisibleInvalid(g))
11397
10881
  .map((g) => this.panelValue(g))
11398
10882
  .filter(Boolean);
11399
10883
  if (!invalidIds.length)
11400
10884
  return;
11401
- this.expandedGroupIds = Array.from(new Set([...this.expandedGroupIds, ...invalidIds]));
10885
+ this.expandedGroupIds.set(Array.from(new Set([...this.expandedGroupIds(), ...invalidIds])));
11402
10886
  }
11403
- /** Cleanup dependency subscriptions when component is destroyed */
11404
10887
  ngOnDestroy() {
11405
10888
  this.depCleanup?.();
11406
10889
  }
11407
10890
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
11408
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaFormV2Component, isStandalone: true, selector: "phoenix-meta-form-v2", inputs: { form: "form", config: "config", readOnly: "readOnly", contentStyle: "contentStyle", contentClass: "contentClass" }, usesOnChanges: true, ngImport: i0, template: "<div [formGroup]=\"form\">\n @if (hasControls) {\n\n @if (isGrouped) {\n <p-accordion\n [multiple]=\"true\"\n [value]=\"expandedGroupIds\"\n (valueChange)=\"onAccordionValueChange($event)\"\n >\n @for (g of groupedControls; track groupTrack(g, $index)) {\n <p-accordion-panel [value]=\"panelValue(g)\">\n <p-accordion-header>\n\n <!-- plus/minus toggle icon -->\n <ng-template #toggleicon let-active=\"active\">\n @if (active) {\n <i class=\"pi pi-minus-circle\"></i>\n } @else {\n <i class=\"pi pi-plus-circle\"></i>\n }\n </ng-template>\n\n {{ g?.groupName | translate }}\n </p-accordion-header>\n \n <p-accordion-content>\n <div [ngStyle]=\"contentStyle\" [ngClass]=\"contentClass\">\n <phoenix-meta-form-group-v2 [group]=\"g\" [form]=\"form\" [readOnly]=\"readOnly\"></phoenix-meta-form-group-v2>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n } @else {\n @for (row of controlRows; track $index) {\n <div class=\"grid\">\n @for (f of row; track f.configuration.key) {\n <div [ngClass]=\"f.style.colWidth ?? 'col-12 md:col-6'\">\n <phoenix-meta-form-field-v2 [field]=\"f\" [form]=\"form\" [readOnly]=\"readOnly\"></phoenix-meta-form-field-v2>\n </div>\n }\n </div>\n }\n }\n\n }\n</div>", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i3$1.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i3$1.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i3$1.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i3$1.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: MetaFormFieldV2Component, selector: "phoenix-meta-form-field-v2", inputs: ["field", "form", "readOnly", "disableForm"] }, { kind: "component", type: MetaFormGroupV2Component, selector: "phoenix-meta-form-group-v2", inputs: ["group", "form", "readOnly"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MetaFormV2Component, isStandalone: true, selector: "phoenix-meta-form-v2", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, contentStyle: { classPropertyName: "contentStyle", publicName: "contentStyle", isSignal: true, isRequired: false, transformFunction: null }, contentClass: { classPropertyName: "contentClass", publicName: "contentClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"form()\">\n @if (hasControls()) {\n\n @if (isGrouped()) {\n <p-accordion\n [multiple]=\"true\"\n [value]=\"expandedGroupIds()\"\n (valueChange)=\"onAccordionValueChange($event)\"\n >\n @for (g of groupedControls(); track groupTrack(g, $index)) {\n <p-accordion-panel [value]=\"panelValue(g)\">\n <p-accordion-header>\n\n <!-- plus/minus toggle icon -->\n <ng-template #toggleicon let-active=\"active\">\n @if (active) {\n <i class=\"pi pi-minus-circle\"></i>\n } @else {\n <i class=\"pi pi-plus-circle\"></i>\n }\n </ng-template>\n\n {{ g?.groupName | translate }}\n </p-accordion-header>\n\n <p-accordion-content>\n <div [ngStyle]=\"contentStyle()\" [ngClass]=\"contentClass()\">\n <phoenix-meta-form-group-v2 [group]=\"g\" [form]=\"form()\" [readOnly]=\"readOnly()\"></phoenix-meta-form-group-v2>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n } @else {\n @for (row of controlRows(); track $index) {\n <div class=\"grid\">\n @for (f of row; track f.configuration.key) {\n <div [ngClass]=\"f.style.colWidth ?? 'col-12 md:col-6'\">\n <phoenix-meta-form-field-v2 [field]=\"f\" [form]=\"form()\" [readOnly]=\"readOnly()\"></phoenix-meta-form-field-v2>\n </div>\n }\n </div>\n }\n }\n\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i3$1.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i3$1.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i3$1.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i3$1.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: MetaFormFieldV2Component, selector: "phoenix-meta-form-field-v2", inputs: ["field", "form", "readOnly", "disableForm"] }, { kind: "component", type: MetaFormGroupV2Component, selector: "phoenix-meta-form-group-v2", inputs: ["group", "form", "readOnly"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11409
10892
  }
11410
10893
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormV2Component, decorators: [{
11411
10894
  type: Component,
@@ -11416,20 +10899,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
11416
10899
  TranslateModule,
11417
10900
  MetaFormFieldV2Component,
11418
10901
  MetaFormGroupV2Component,
11419
- ], template: "<div [formGroup]=\"form\">\n @if (hasControls) {\n\n @if (isGrouped) {\n <p-accordion\n [multiple]=\"true\"\n [value]=\"expandedGroupIds\"\n (valueChange)=\"onAccordionValueChange($event)\"\n >\n @for (g of groupedControls; track groupTrack(g, $index)) {\n <p-accordion-panel [value]=\"panelValue(g)\">\n <p-accordion-header>\n\n <!-- plus/minus toggle icon -->\n <ng-template #toggleicon let-active=\"active\">\n @if (active) {\n <i class=\"pi pi-minus-circle\"></i>\n } @else {\n <i class=\"pi pi-plus-circle\"></i>\n }\n </ng-template>\n\n {{ g?.groupName | translate }}\n </p-accordion-header>\n \n <p-accordion-content>\n <div [ngStyle]=\"contentStyle\" [ngClass]=\"contentClass\">\n <phoenix-meta-form-group-v2 [group]=\"g\" [form]=\"form\" [readOnly]=\"readOnly\"></phoenix-meta-form-group-v2>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n } @else {\n @for (row of controlRows; track $index) {\n <div class=\"grid\">\n @for (f of row; track f.configuration.key) {\n <div [ngClass]=\"f.style.colWidth ?? 'col-12 md:col-6'\">\n <phoenix-meta-form-field-v2 [field]=\"f\" [form]=\"form\" [readOnly]=\"readOnly\"></phoenix-meta-form-field-v2>\n </div>\n }\n </div>\n }\n }\n\n }\n</div>", styles: [":host{display:block}\n"] }]
11420
- }], propDecorators: { form: [{
11421
- type: Input,
11422
- args: [{ required: true }]
11423
- }], config: [{
11424
- type: Input,
11425
- args: [{ required: true }]
11426
- }], readOnly: [{
11427
- type: Input
11428
- }], contentStyle: [{
11429
- type: Input
11430
- }], contentClass: [{
11431
- type: Input
11432
- }] } });
10902
+ ], template: "<div [formGroup]=\"form()\">\n @if (hasControls()) {\n\n @if (isGrouped()) {\n <p-accordion\n [multiple]=\"true\"\n [value]=\"expandedGroupIds()\"\n (valueChange)=\"onAccordionValueChange($event)\"\n >\n @for (g of groupedControls(); track groupTrack(g, $index)) {\n <p-accordion-panel [value]=\"panelValue(g)\">\n <p-accordion-header>\n\n <!-- plus/minus toggle icon -->\n <ng-template #toggleicon let-active=\"active\">\n @if (active) {\n <i class=\"pi pi-minus-circle\"></i>\n } @else {\n <i class=\"pi pi-plus-circle\"></i>\n }\n </ng-template>\n\n {{ g?.groupName | translate }}\n </p-accordion-header>\n\n <p-accordion-content>\n <div [ngStyle]=\"contentStyle()\" [ngClass]=\"contentClass()\">\n <phoenix-meta-form-group-v2 [group]=\"g\" [form]=\"form()\" [readOnly]=\"readOnly()\"></phoenix-meta-form-group-v2>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n } @else {\n @for (row of controlRows(); track $index) {\n <div class=\"grid\">\n @for (f of row; track f.configuration.key) {\n <div [ngClass]=\"f.style.colWidth ?? 'col-12 md:col-6'\">\n <phoenix-meta-form-field-v2 [field]=\"f\" [form]=\"form()\" [readOnly]=\"readOnly()\"></phoenix-meta-form-field-v2>\n </div>\n }\n </div>\n }\n }\n\n }\n</div>\n", styles: [":host{display:block}\n"] }]
10903
+ }], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], contentStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentStyle", required: false }] }], contentClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentClass", required: false }] }] } });
11433
10904
 
11434
10905
  class MetaFormButtonsV2Component {
11435
10906
  /**