@fuentis/phoenix-ui 0.0.9-alpha.657 → 0.0.9-alpha.659

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,102 @@ 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.1.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
+ <small class="block mt-1 timeperiod-v2-hint">
8739
+ <i class="pi pi-info-circle mr-1"></i>{{ 'VALIDATION_MESSAGE.VALUE_SHOULD_MATCH_TIMEPATTERN' | translate }}
8740
+ </small>
8741
+ `, 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 });
8742
+ }
8743
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaTimeperiodV2Component, decorators: [{
8744
+ type: Component,
8745
+ args: [{ selector: 'phoenix-meta-timeperiod-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, InputTextModule, TranslateModule], template: `
8746
+ <input
8747
+ pInputText
8748
+ type="text"
8749
+ class="w-full"
8750
+ [attr.id]="key() ?? null"
8751
+ [attr.data-cy]="'time-period-v2-' + (key() ?? '')"
8752
+ [value]="value()"
8753
+ [disabled]="disabled()"
8754
+ (input)="onInput($event)"
8755
+ (blur)="handleBlur()"
8756
+ />
8757
+ <small class="block mt-1 timeperiod-v2-hint">
8758
+ <i class="pi pi-info-circle mr-1"></i>{{ 'VALIDATION_MESSAGE.VALUE_SHOULD_MATCH_TIMEPATTERN' | translate }}
8759
+ </small>
8760
+ `, providers: [
8761
+ {
8762
+ provide: NG_VALUE_ACCESSOR,
8763
+ useExisting: forwardRef(() => MetaTimeperiodV2Component),
8764
+ multi: true,
8765
+ },
8766
+ ], styles: [".timeperiod-v2-hint{color:var(--p-text-muted-color, #6b7280);font-size:12px}\n"] }]
8767
+ }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
8768
+
8672
8769
  class StripHtmlSafePipe {
8673
8770
  transform(value) {
8674
8771
  if (value === null || value === undefined)
@@ -8933,120 +9030,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8933
9030
  args: [{ required: true }]
8934
9031
  }] } });
8935
9032
 
9033
+ /**
9034
+ * V2 ASSIGN field — signal-based ControlValueAccessor (OnPush, no manual CD).
9035
+ * Holds the full selected assignee (downstream fields often need more than uuid);
9036
+ * a picker dialog returns the chosen row.
9037
+ */
8936
9038
  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';
9039
+ /** Selectable assignees shown in the picker dialog. */
9040
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
9041
+ /** i18n key for the dialog header title. */
9042
+ dialogHeaderKey = input('LABELS.ASSIGN_RESPONSIBLE', ...(ngDevMode ? [{ debugName: "dialogHeaderKey" }] : []));
8948
9043
  translate = inject(TranslateService);
8949
9044
  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
- */
9045
+ /** The full selected assignee bound to the parent control. */
9046
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9047
+ /** Disabled state from Angular Forms (this field has no separate field-level disable). */
9048
+ disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
8963
9049
  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
9050
  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
9051
  writeValue(value) {
8974
- this.value = value ?? null;
9052
+ this.value.set(value ?? null);
8975
9053
  }
8976
- /**
8977
- * Registers the callback that should be called when the component updates the value.
8978
- */
8979
9054
  registerOnChange(fn) {
8980
9055
  this.onChange = fn;
8981
9056
  }
8982
- /**
8983
- * Registers the callback that should be called when the control becomes "touched".
8984
- */
8985
9057
  registerOnTouched(fn) {
8986
9058
  this.onTouched = fn;
8987
9059
  }
8988
- /**
8989
- * Receives disabled state from Angular forms and updates local state.
8990
- */
8991
9060
  setDisabledState(isDisabled) {
8992
- this.disabled = isDisabled;
9061
+ this.disabled.set(isDisabled);
8993
9062
  }
8994
- /**
8995
- * Clears current assignee value.
8996
- * - emits `null`
8997
- * - marks as touched (user action)
8998
- */
9063
+ /** Clears the assignee (emits null + touched). */
8999
9064
  clear() {
9000
- if (this.disabled)
9065
+ if (this.disabled())
9001
9066
  return;
9002
- // prevent redundant emits
9003
- if (this.value === null) {
9067
+ if (this.value() === null) {
9004
9068
  this.onTouched();
9005
9069
  return;
9006
9070
  }
9007
- this.value = null;
9071
+ this.value.set(null);
9008
9072
  this.onChange(null);
9009
9073
  this.onTouched();
9010
9074
  }
9011
9075
  /**
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
9076
+ * Opens the picker dialog and applies the selected row. Touched on open
9077
+ * (interaction started); onChange only when a row is actually selected.
9018
9078
  */
9019
9079
  openDialog() {
9020
- if (this.disabled)
9080
+ if (this.disabled())
9021
9081
  return;
9022
9082
  this.onTouched();
9023
9083
  const ref = this.dialog.open(ObjectItemDialogComponent, {
9024
- header: this.translate.instant(this.dialogHeaderKey),
9084
+ header: this.translate.instant(this.dialogHeaderKey()),
9025
9085
  width: '700px',
9026
9086
  modal: true,
9027
9087
  data: {
9028
- tableData: this.items,
9088
+ tableData: this.items(),
9029
9089
  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
- },
9090
+ { field: 'name', header: 'LABELS.NAME', columnType: tableColumnType.TEXT },
9091
+ { field: 'function', header: 'LABELS.FUNCTION', columnType: tableColumnType.TEXT },
9092
+ { field: 'phone', header: 'LABELS.PHONE', columnType: tableColumnType.TEXT },
9093
+ { field: 'email', header: 'LABELS.EMAIL', columnType: tableColumnType.TEXT },
9050
9094
  ],
9051
9095
  },
9052
9096
  contentStyle: { overflow: 'auto' },
@@ -9056,47 +9100,46 @@ class MetaAssignResponsibleV2Component {
9056
9100
  ref?.onClose.subscribe((response) => {
9057
9101
  if (!response)
9058
9102
  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) {
9103
+ const current = this.value();
9104
+ const same = (current?.uuid ?? null) === (response?.uuid ?? null) &&
9105
+ JSON.stringify(current ?? null) === JSON.stringify(response ?? null);
9106
+ this.value.set(response);
9107
+ if (!same)
9064
9108
  this.onChange(response);
9065
- }
9066
9109
  this.onTouched();
9067
9110
  });
9068
9111
  }
9069
9112
  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: [
9113
+ 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
9114
  {
9072
9115
  provide: NG_VALUE_ACCESSOR,
9073
9116
  useExisting: forwardRef(() => MetaAssignResponsibleV2Component),
9074
9117
  multi: true,
9075
9118
  },
9076
9119
  ], ngImport: i0, template: `
9077
- @if (value?.uuid) {
9120
+ @if (value()?.uuid) {
9078
9121
  <div class="flex align-items-center">
9079
9122
  <div>
9080
9123
  <p-button
9081
9124
  [rounded]="true"
9082
9125
  [text]="true"
9083
9126
  (onClick)="op.toggle($event)"
9084
- [disabled]="disabled"
9127
+ [disabled]="disabled()"
9085
9128
  >
9086
9129
  <div class="person-wrap">
9087
9130
  <div class="person-avatar">
9088
- {{ (value?.name ?? '').toUpperCase().charAt(0) }}
9131
+ {{ (value()?.name ?? '').toUpperCase().charAt(0) }}
9089
9132
  </div>
9090
9133
  <div>
9091
9134
  <p
9092
9135
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9093
9136
  >
9094
- {{ value?.name ?? '--' }}
9137
+ {{ value()?.name ?? '--' }}
9095
9138
  </p>
9096
9139
  <p
9097
9140
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9098
9141
  >
9099
- {{ value?.function ?? '--' }}
9142
+ {{ value()?.function ?? '--' }}
9100
9143
  </p>
9101
9144
  </div>
9102
9145
  </div>
@@ -9107,28 +9150,28 @@ class MetaAssignResponsibleV2Component {
9107
9150
  <span
9108
9151
  class="block mb-2"
9109
9152
  pTooltip="{{
9110
- (value?.email?.length ?? 0) > 25 ? value?.email : ''
9153
+ (value()?.email?.length ?? 0) > 25 ? value()?.email : ''
9111
9154
  }}"
9112
9155
  tooltipPosition="right"
9113
9156
  >
9114
9157
  <i class="pi pi-envelope mr-1 text-500"></i>
9115
9158
  {{
9116
- (value?.email?.length ?? 0) > 25
9117
- ? value?.email?.slice(0, 25) + '...'
9118
- : (value?.email ?? '--')
9159
+ (value()?.email?.length ?? 0) > 25
9160
+ ? value()?.email?.slice(0, 25) + '...'
9161
+ : (value()?.email ?? '--')
9119
9162
  }}
9120
9163
  </span>
9121
9164
  <p
9122
9165
  pTooltip="{{
9123
- (value?.phone?.length ?? 0) > 25 ? value?.phone : ''
9166
+ (value()?.phone?.length ?? 0) > 25 ? value()?.phone : ''
9124
9167
  }}"
9125
9168
  tooltipPosition="right"
9126
9169
  >
9127
9170
  <i class="pi pi-phone mr-1 text-500"></i>
9128
9171
  {{
9129
- (value?.phone?.length ?? 0) > 25
9130
- ? value?.phone?.slice(0, 25) + '...'
9131
- : (value?.phone ?? '--')
9172
+ (value()?.phone?.length ?? 0) > 25
9173
+ ? value()?.phone?.slice(0, 25) + '...'
9174
+ : (value()?.phone ?? '--')
9132
9175
  }}
9133
9176
  </p>
9134
9177
  </div>
@@ -9140,7 +9183,7 @@ class MetaAssignResponsibleV2Component {
9140
9183
  styleClass="p-button-sm mt-1"
9141
9184
  type="button"
9142
9185
  [text]="true"
9143
- [disabled]="disabled"
9186
+ [disabled]="disabled()"
9144
9187
  [label]="'ACTION.REASSIGN' | translate"
9145
9188
  (click)="op.hide(); openDialog()"
9146
9189
  ></p-button>
@@ -9148,7 +9191,7 @@ class MetaAssignResponsibleV2Component {
9148
9191
  <p-button
9149
9192
  type="button"
9150
9193
  icon="pi pi-times"
9151
- [disabled]="disabled"
9194
+ [disabled]="disabled()"
9152
9195
  (click)="op.hide(); clear()"
9153
9196
  [text]="true"
9154
9197
  styleClass="p-button-danger p-button-sm mt-1 ml-2"
@@ -9163,14 +9206,14 @@ class MetaAssignResponsibleV2Component {
9163
9206
  [label]="'ACTION.ASSIGN' | translate"
9164
9207
  (click)="openDialog()"
9165
9208
  [text]="true"
9166
- [disabled]="disabled"
9209
+ [disabled]="disabled()"
9167
9210
  ></p-button>
9168
9211
  }
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" }] });
9212
+ `, 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
9213
  }
9171
9214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaAssignResponsibleV2Component, decorators: [{
9172
9215
  type: Component,
9173
- args: [{ selector: 'phoenix-meta-assign-responsible-v2', standalone: true, imports: [
9216
+ args: [{ selector: 'phoenix-meta-assign-responsible-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
9174
9217
  CommonModule,
9175
9218
  ButtonModule,
9176
9219
  PopoverModule,
@@ -9183,29 +9226,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9183
9226
  multi: true,
9184
9227
  },
9185
9228
  ], template: `
9186
- @if (value?.uuid) {
9229
+ @if (value()?.uuid) {
9187
9230
  <div class="flex align-items-center">
9188
9231
  <div>
9189
9232
  <p-button
9190
9233
  [rounded]="true"
9191
9234
  [text]="true"
9192
9235
  (onClick)="op.toggle($event)"
9193
- [disabled]="disabled"
9236
+ [disabled]="disabled()"
9194
9237
  >
9195
9238
  <div class="person-wrap">
9196
9239
  <div class="person-avatar">
9197
- {{ (value?.name ?? '').toUpperCase().charAt(0) }}
9240
+ {{ (value()?.name ?? '').toUpperCase().charAt(0) }}
9198
9241
  </div>
9199
9242
  <div>
9200
9243
  <p
9201
9244
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9202
9245
  >
9203
- {{ value?.name ?? '--' }}
9246
+ {{ value()?.name ?? '--' }}
9204
9247
  </p>
9205
9248
  <p
9206
9249
  class="white-space-nowrap overflow-hidden text-overflow-ellipsis"
9207
9250
  >
9208
- {{ value?.function ?? '--' }}
9251
+ {{ value()?.function ?? '--' }}
9209
9252
  </p>
9210
9253
  </div>
9211
9254
  </div>
@@ -9216,28 +9259,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9216
9259
  <span
9217
9260
  class="block mb-2"
9218
9261
  pTooltip="{{
9219
- (value?.email?.length ?? 0) > 25 ? value?.email : ''
9262
+ (value()?.email?.length ?? 0) > 25 ? value()?.email : ''
9220
9263
  }}"
9221
9264
  tooltipPosition="right"
9222
9265
  >
9223
9266
  <i class="pi pi-envelope mr-1 text-500"></i>
9224
9267
  {{
9225
- (value?.email?.length ?? 0) > 25
9226
- ? value?.email?.slice(0, 25) + '...'
9227
- : (value?.email ?? '--')
9268
+ (value()?.email?.length ?? 0) > 25
9269
+ ? value()?.email?.slice(0, 25) + '...'
9270
+ : (value()?.email ?? '--')
9228
9271
  }}
9229
9272
  </span>
9230
9273
  <p
9231
9274
  pTooltip="{{
9232
- (value?.phone?.length ?? 0) > 25 ? value?.phone : ''
9275
+ (value()?.phone?.length ?? 0) > 25 ? value()?.phone : ''
9233
9276
  }}"
9234
9277
  tooltipPosition="right"
9235
9278
  >
9236
9279
  <i class="pi pi-phone mr-1 text-500"></i>
9237
9280
  {{
9238
- (value?.phone?.length ?? 0) > 25
9239
- ? value?.phone?.slice(0, 25) + '...'
9240
- : (value?.phone ?? '--')
9281
+ (value()?.phone?.length ?? 0) > 25
9282
+ ? value()?.phone?.slice(0, 25) + '...'
9283
+ : (value()?.phone ?? '--')
9241
9284
  }}
9242
9285
  </p>
9243
9286
  </div>
@@ -9249,7 +9292,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9249
9292
  styleClass="p-button-sm mt-1"
9250
9293
  type="button"
9251
9294
  [text]="true"
9252
- [disabled]="disabled"
9295
+ [disabled]="disabled()"
9253
9296
  [label]="'ACTION.REASSIGN' | translate"
9254
9297
  (click)="op.hide(); openDialog()"
9255
9298
  ></p-button>
@@ -9257,7 +9300,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9257
9300
  <p-button
9258
9301
  type="button"
9259
9302
  icon="pi pi-times"
9260
- [disabled]="disabled"
9303
+ [disabled]="disabled()"
9261
9304
  (click)="op.hide(); clear()"
9262
9305
  [text]="true"
9263
9306
  styleClass="p-button-danger p-button-sm mt-1 ml-2"
@@ -9272,109 +9315,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9272
9315
  [label]="'ACTION.ASSIGN' | translate"
9273
9316
  (click)="openDialog()"
9274
9317
  [text]="true"
9275
- [disabled]="disabled"
9318
+ [disabled]="disabled()"
9276
9319
  ></p-button>
9277
9320
  }
9278
9321
  `, 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
- }] } });
9322
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], dialogHeaderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dialogHeaderKey", required: false }] }] } });
9284
9323
 
9324
+ /**
9325
+ * V2 COLOR field — signal-based ControlValueAccessor (OnPush, no manual CD).
9326
+ * Stores a HEX string (or null); the template reads the value/disabled signals.
9327
+ */
9285
9328
  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
- */
9329
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9330
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9331
+ /** Selected colour: HEX string (e.g. "#ff00aa") or null. */
9332
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9333
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9334
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9300
9335
  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
9336
  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
9337
  writeValue(v) {
9323
- this.value = this.normalizeHex(v);
9324
- this.cdr.markForCheck();
9338
+ this.value.set(this.normalizeHex(v));
9325
9339
  }
9326
- /**
9327
- * Registers callback that is triggered when the value changes.
9328
- */
9329
9340
  registerOnChange(fn) {
9330
9341
  this.onChange = fn;
9331
9342
  }
9332
- /**
9333
- * Registers callback that is triggered when the control is touched.
9334
- */
9335
9343
  registerOnTouched(fn) {
9336
9344
  this.onTouched = fn;
9337
9345
  }
9338
- /**
9339
- * Receives disabled state from Angular Forms and updates local state.
9340
- */
9341
9346
  setDisabledState(isDisabled) {
9342
- this.isDisabled = isDisabled;
9343
- this.cdr.markForCheck();
9347
+ this.formDisabled.set(isDisabled);
9344
9348
  }
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)
9349
+ /** Touched is emitted on blur (not on value change). */
9350
+ onPickerChange(next) {
9351
+ if (this.disabled())
9353
9352
  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();
9358
- return;
9359
- }
9360
- this.value = next;
9361
- this.onChange(next);
9362
- this.cdr.markForCheck();
9353
+ const normalized = this.normalizeHex(next);
9354
+ this.value.set(normalized);
9355
+ this.onChange(normalized);
9363
9356
  }
9364
- /**
9365
- * Marks control as touched when user leaves the component.
9366
- * (Matches "touched on blur" CVA guideline.)
9367
- */
9368
9357
  handleBlur() {
9369
- if (this.disabled)
9358
+ if (this.disabled())
9370
9359
  return;
9371
9360
  this.onTouched();
9372
9361
  }
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
- */
9362
+ /** Normalizes to "#rrggbb"/"#rgb" (adds '#', lowercases) or null. */
9378
9363
  normalizeHex(v) {
9379
9364
  if (v === null || v === undefined)
9380
9365
  return null;
@@ -9386,7 +9371,7 @@ class MetaColorPickerV2Component {
9386
9371
  return ok ? withHash.toLowerCase() : null;
9387
9372
  }
9388
9373
  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: [
9374
+ 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
9375
  {
9391
9376
  provide: NG_VALUE_ACCESSOR,
9392
9377
  useExisting: forwardRef(() => MetaColorPickerV2Component),
@@ -9395,10 +9380,10 @@ class MetaColorPickerV2Component {
9395
9380
  ], ngImport: i0, template: `
9396
9381
  <p-colorPicker
9397
9382
  class="color-swatch"
9398
- [(ngModel)]="value"
9399
- (onChange)="onPickerChange()"
9383
+ [ngModel]="value()"
9384
+ (ngModelChange)="onPickerChange($event)"
9400
9385
  (onBlur)="handleBlur()"
9401
- [disabled]="disabled"
9386
+ [disabled]="disabled()"
9402
9387
  [appendTo]="'body'"
9403
9388
  ></p-colorPicker>
9404
9389
  `, 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 +9393,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9408
9393
  args: [{ selector: 'phoenix-meta-color-picker-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ColorPickerModule], template: `
9409
9394
  <p-colorPicker
9410
9395
  class="color-swatch"
9411
- [(ngModel)]="value"
9412
- (onChange)="onPickerChange()"
9396
+ [ngModel]="value()"
9397
+ (ngModelChange)="onPickerChange($event)"
9413
9398
  (onBlur)="handleBlur()"
9414
- [disabled]="disabled"
9399
+ [disabled]="disabled()"
9415
9400
  [appendTo]="'body'"
9416
9401
  ></p-colorPicker>
9417
9402
  `, providers: [
@@ -9421,128 +9406,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9421
9406
  multi: true,
9422
9407
  },
9423
9408
  ], 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
- }] } });
9409
+ }], propDecorators: { disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9427
9410
 
9411
+ /**
9412
+ * V2 CHECKBOX_COLOR field — signal-based ControlValueAccessor (OnPush, no manual
9413
+ * CD). A colour swatch that opens a grid popover; value + focus highlight live in
9414
+ * signals.
9415
+ */
9428
9416
  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
- */
9417
+ /** 2D grid of colours rendered in the popover. */
9418
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
9419
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9420
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9421
+ /** Selected colour bound to the parent control. */
9422
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
9423
+ /** Colour currently focused/hovered in the grid (visual outline only). */
9424
+ focusedColor = signal(null, ...(ngDevMode ? [{ debugName: "focusedColor" }] : []));
9425
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9426
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9460
9427
  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
9428
  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
9429
  writeValue(v) {
9480
- this.value = this.normalizeColor(v);
9481
- this.focusedColor = this.value;
9482
- this.cdr.markForCheck();
9430
+ const next = this.normalizeColor(v);
9431
+ this.value.set(next);
9432
+ this.focusedColor.set(next);
9483
9433
  }
9484
- /**
9485
- * Registers callback that is triggered when the value changes.
9486
- */
9487
9434
  registerOnChange(fn) {
9488
9435
  this.onChange = fn;
9489
9436
  }
9490
- /**
9491
- * Registers callback that is triggered when the control is touched.
9492
- */
9493
9437
  registerOnTouched(fn) {
9494
9438
  this.onTouched = fn;
9495
9439
  }
9496
- /**
9497
- * Receives disabled state from Angular Forms and updates local state.
9498
- */
9499
9440
  setDisabledState(isDisabled) {
9500
- this.isDisabled = isDisabled;
9501
- this.cdr.markForCheck();
9441
+ this.formDisabled.set(isDisabled);
9502
9442
  }
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
9443
  toggle(popover, ev) {
9509
- if (this.disabled)
9444
+ if (this.disabled())
9510
9445
  return;
9511
9446
  this.onTouched();
9512
9447
  popover.toggle(ev);
9513
9448
  }
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
9449
  select(color, popover) {
9522
- if (this.disabled)
9450
+ if (this.disabled())
9523
9451
  return;
9524
9452
  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;
9453
+ this.focusedColor.set(next);
9454
+ if (next !== this.value()) {
9455
+ this.value.set(next);
9456
+ this.onChange(next);
9532
9457
  }
9533
- this.focusedColor = next;
9534
- this.value = next;
9535
- this.onChange(next);
9536
9458
  this.onTouched();
9537
- this.cdr.markForCheck();
9538
9459
  popover.hide();
9539
9460
  }
9540
9461
  /**
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
9462
+ * Normalizes hex ("#rgb"/"#rrggbb", with/without '#') and passes through other
9463
+ * CSS colours ("red", "var(--x)"); empty null.
9546
9464
  */
9547
9465
  normalizeColor(v) {
9548
9466
  if (v === null || v === undefined)
@@ -9550,15 +9468,13 @@ class MetaCheckboxColorPickerV2Component {
9550
9468
  const s = String(v).trim();
9551
9469
  if (!s)
9552
9470
  return null;
9553
- // normalize common hex values (with/without '#')
9554
9471
  const withHash = s.startsWith('#') ? s : `#${s}`;
9555
9472
  if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(withHash))
9556
9473
  return withHash.toLowerCase();
9557
- // fallback: allow CSS colors (e.g. "red") or CSS vars
9558
9474
  return s;
9559
9475
  }
9560
9476
  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: [
9477
+ 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
9478
  {
9563
9479
  provide: NG_VALUE_ACCESSOR,
9564
9480
  useExisting: forwardRef(() => MetaCheckboxColorPickerV2Component),
@@ -9567,18 +9483,18 @@ class MetaCheckboxColorPickerV2Component {
9567
9483
  ], ngImport: i0, template: `
9568
9484
  <p-popover #popover>
9569
9485
  <div class="color-picker">
9570
- @for (row of options; track $index; let last = $last) {
9486
+ @for (row of options(); track $index; let last = $last) {
9571
9487
  <div class="color-row" [class.mb-2]="!last">
9572
9488
  @for (color of row; track color) {
9573
9489
  <button
9574
9490
  type="button"
9575
9491
  class="color-box"
9576
- [disabled]="disabled"
9492
+ [disabled]="disabled()"
9577
9493
  [style.backgroundColor]="color"
9578
- [style.outline]="focusedColor === color ? '3px solid ' + color : 'none'"
9579
- [style.outlineOffset]="focusedColor === color ? '3px' : '0'"
9494
+ [style.outline]="focusedColor() === color ? '3px solid ' + color : 'none'"
9495
+ [style.outlineOffset]="focusedColor() === color ? '3px' : '0'"
9580
9496
  (click)="select(color, popover)"
9581
- (mouseenter)="focusedColor = color"
9497
+ (mouseenter)="focusedColor.set(color)"
9582
9498
  ></button>
9583
9499
  }
9584
9500
  </div>
@@ -9589,8 +9505,8 @@ class MetaCheckboxColorPickerV2Component {
9589
9505
  <button
9590
9506
  type="button"
9591
9507
  class="selected-color"
9592
- [disabled]="disabled"
9593
- [style.backgroundColor]="value || 'transparent'"
9508
+ [disabled]="disabled()"
9509
+ [style.backgroundColor]="value() || 'transparent'"
9594
9510
  (click)="toggle(popover, $event)"
9595
9511
  aria-label="Select color"
9596
9512
  ></button>
@@ -9607,18 +9523,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9607
9523
  ], template: `
9608
9524
  <p-popover #popover>
9609
9525
  <div class="color-picker">
9610
- @for (row of options; track $index; let last = $last) {
9526
+ @for (row of options(); track $index; let last = $last) {
9611
9527
  <div class="color-row" [class.mb-2]="!last">
9612
9528
  @for (color of row; track color) {
9613
9529
  <button
9614
9530
  type="button"
9615
9531
  class="color-box"
9616
- [disabled]="disabled"
9532
+ [disabled]="disabled()"
9617
9533
  [style.backgroundColor]="color"
9618
- [style.outline]="focusedColor === color ? '3px solid ' + color : 'none'"
9619
- [style.outlineOffset]="focusedColor === color ? '3px' : '0'"
9534
+ [style.outline]="focusedColor() === color ? '3px solid ' + color : 'none'"
9535
+ [style.outlineOffset]="focusedColor() === color ? '3px' : '0'"
9620
9536
  (click)="select(color, popover)"
9621
- (mouseenter)="focusedColor = color"
9537
+ (mouseenter)="focusedColor.set(color)"
9622
9538
  ></button>
9623
9539
  }
9624
9540
  </div>
@@ -9629,174 +9545,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9629
9545
  <button
9630
9546
  type="button"
9631
9547
  class="selected-color"
9632
- [disabled]="disabled"
9633
- [style.backgroundColor]="value || 'transparent'"
9548
+ [disabled]="disabled()"
9549
+ [style.backgroundColor]="value() || 'transparent'"
9634
9550
  (click)="toggle(popover, $event)"
9635
9551
  aria-label="Select color"
9636
9552
  ></button>
9637
9553
  `, 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
- }] } });
9554
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9643
9555
 
9556
+ /**
9557
+ * V2 START_DUE_DATE field — signal-based ControlValueAccessor (OnPush, no manual
9558
+ * CD). Two date pickers whose composite value is propagated through CVA.
9559
+ */
9644
9560
  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
- */
9561
+ /** Optional data-cy prefix for e2e tests. */
9562
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9563
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9564
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9565
+ /** Selected start/end dates (Date or null) for the pickers. */
9566
+ startDate = signal(null, ...(ngDevMode ? [{ debugName: "startDate" }] : []));
9567
+ endDate = signal(null, ...(ngDevMode ? [{ debugName: "endDate" }] : []));
9568
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9569
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9674
9570
  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
9571
  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
9572
  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();
9573
+ this.startDate.set(this.normalizeDateOnly(this.parseToDate(v?.startDate ?? null)));
9574
+ this.endDate.set(this.normalizeDateOnly(this.parseToDate(v?.endDate ?? null)));
9698
9575
  }
9699
- /**
9700
- * Registers the callback that should be called when the value changes.
9701
- */
9702
9576
  registerOnChange(fn) {
9703
9577
  this.onChange = fn;
9704
9578
  }
9705
- /**
9706
- * Registers the callback that should be called when the control is touched.
9707
- */
9708
9579
  registerOnTouched(fn) {
9709
9580
  this.onTouched = fn;
9710
9581
  }
9711
- /**
9712
- * Receives disabled state from Angular Forms and updates local state.
9713
- */
9714
9582
  setDisabledState(isDisabled) {
9715
- this.isDisabled = isDisabled;
9716
- // OnPush: reflect disabled state changes immediately
9717
- this.cdr.markForCheck();
9583
+ this.formDisabled.set(isDisabled);
9718
9584
  }
9719
- /**
9720
- * Handler used by PrimeNG DatePicker blur events.
9721
- * Marks the control as touched without emitting a value change.
9722
- */
9723
9585
  handleBlur() {
9724
- if (this.disabled)
9586
+ if (this.disabled())
9725
9587
  return;
9726
9588
  this.onTouched();
9727
9589
  }
9728
- /**
9729
- * Handler for start date change coming from the DatePicker.
9730
- * Updates local state and propagates the composite value.
9731
- */
9732
9590
  onStartChange(d) {
9733
- if (this.disabled)
9591
+ if (this.disabled())
9734
9592
  return;
9735
- this.startDate = this.normalizeDateOnly(d ?? null);
9593
+ this.startDate.set(this.normalizeDateOnly(d ?? null));
9736
9594
  this.emitChange();
9737
9595
  }
9738
- /**
9739
- * Handler for end date change coming from the DatePicker.
9740
- * Updates local state and propagates the composite value.
9741
- */
9742
9596
  onEndChange(d) {
9743
- if (this.disabled)
9597
+ if (this.disabled())
9744
9598
  return;
9745
- this.endDate = this.normalizeDateOnly(d ?? null);
9599
+ this.endDate.set(this.normalizeDateOnly(d ?? null));
9746
9600
  this.emitChange();
9747
9601
  }
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
- */
9602
+ /** Emits `null` when both dates are empty, otherwise the range object. */
9753
9603
  emitChange() {
9754
- if (!this.startDate && !this.endDate) {
9604
+ const start = this.startDate();
9605
+ const end = this.endDate();
9606
+ if (!start && !end) {
9755
9607
  this.onChange(null);
9756
- this.cdr.markForCheck();
9757
9608
  return;
9758
9609
  }
9759
- this.onChange({
9760
- startDate: this.startDate,
9761
- endDate: this.endDate,
9762
- });
9763
- this.cdr.markForCheck();
9610
+ this.onChange({ startDate: start, endDate: end });
9764
9611
  }
9765
9612
  /**
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).
9613
+ * Parses Date | string to a Date. "YYYY-MM-DD" is treated as a LOCAL date to
9614
+ * avoid timezone day-shifts.
9768
9615
  */
9769
9616
  parseToDate(v) {
9770
9617
  if (!v)
9771
9618
  return null;
9772
- if (v instanceof Date) {
9619
+ if (v instanceof Date)
9773
9620
  return isNaN(v.getTime()) ? null : v;
9774
- }
9775
9621
  const s = String(v).trim();
9776
9622
  if (!s)
9777
9623
  return null;
9778
- // Date-only string -> create LOCAL date (avoid UTC shifting)
9779
9624
  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
9780
9625
  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);
9626
+ const local = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), 12, 0, 0, 0);
9785
9627
  return isNaN(local.getTime()) ? null : local;
9786
9628
  }
9787
- // ISO / other -> fallback
9788
9629
  const dt = new Date(s);
9789
9630
  return isNaN(dt.getTime()) ? null : dt;
9790
9631
  }
9791
9632
  normalizeDateOnly(d) {
9792
- if (!d)
9793
- return null;
9794
- if (isNaN(d.getTime()))
9633
+ if (!d || isNaN(d.getTime()))
9795
9634
  return null;
9796
9635
  return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0);
9797
9636
  }
9798
9637
  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: [
9638
+ 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
9639
  {
9801
9640
  provide: NG_VALUE_ACCESSOR,
9802
9641
  useExisting: forwardRef(() => MetaStartDueDateV2Component),
@@ -9812,8 +9651,8 @@ class MetaStartDueDateV2Component {
9812
9651
  [showButtonBar]="true"
9813
9652
  [showIcon]="true"
9814
9653
  [placeholder]="'LABELS.PLANNED_START' | translate"
9815
- [disabled]="disabled"
9816
- [ngModel]="startDate"
9654
+ [disabled]="disabled()"
9655
+ [ngModel]="startDate()"
9817
9656
  (ngModelChange)="onStartChange($event)"
9818
9657
  (onBlur)="handleBlur()"
9819
9658
  appendTo="body"
@@ -9831,8 +9670,8 @@ class MetaStartDueDateV2Component {
9831
9670
  [showButtonBar]="true"
9832
9671
  [showIcon]="true"
9833
9672
  [placeholder]="'LABELS.PLANNED_END' | translate"
9834
- [disabled]="disabled"
9835
- [ngModel]="endDate"
9673
+ [disabled]="disabled()"
9674
+ [ngModel]="endDate()"
9836
9675
  (ngModelChange)="onEndChange($event)"
9837
9676
  (onBlur)="handleBlur()"
9838
9677
  appendTo="body"
@@ -9860,8 +9699,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9860
9699
  [showButtonBar]="true"
9861
9700
  [showIcon]="true"
9862
9701
  [placeholder]="'LABELS.PLANNED_START' | translate"
9863
- [disabled]="disabled"
9864
- [ngModel]="startDate"
9702
+ [disabled]="disabled()"
9703
+ [ngModel]="startDate()"
9865
9704
  (ngModelChange)="onStartChange($event)"
9866
9705
  (onBlur)="handleBlur()"
9867
9706
  appendTo="body"
@@ -9879,8 +9718,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9879
9718
  [showButtonBar]="true"
9880
9719
  [showIcon]="true"
9881
9720
  [placeholder]="'LABELS.PLANNED_END' | translate"
9882
- [disabled]="disabled"
9883
- [ngModel]="endDate"
9721
+ [disabled]="disabled()"
9722
+ [ngModel]="endDate()"
9884
9723
  (ngModelChange)="onEndChange($event)"
9885
9724
  (onBlur)="handleBlur()"
9886
9725
  appendTo="body"
@@ -9889,116 +9728,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9889
9728
  </div>
9890
9729
  </div>
9891
9730
  `, styles: [":host{display:block}\n"] }]
9892
- }], propDecorators: { dataCy: [{
9893
- type: Input
9894
- }], disable: [{
9895
- type: Input
9896
- }] } });
9731
+ }], propDecorators: { dataCy: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataCy", required: false }] }], disable: [{ type: i0.Input, args: [{ isSignal: true, alias: "disable", required: false }] }] } });
9897
9732
 
9733
+ /**
9734
+ * V2 SWITCH field — signal-based ControlValueAccessor (OnPush, no manual CD).
9735
+ * Value + disabled live in signals; the template reads them as functions.
9736
+ */
9898
9737
  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
- */
9738
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9739
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9740
+ /** Hide the switch (meta hidden). Does not disable it. */
9741
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : []));
9742
+ /** Optional data-cy for e2e tests. */
9743
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9744
+ /** Current boolean value, synced with the parent control through CVA. */
9745
+ value = signal(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
9746
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9747
+ /** Final disabled = field-level disable OR the Forms disabled state. */
9748
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9922
9749
  onChange = () => { };
9923
- /**
9924
- * CVA callback invoked when the control is marked as touched.
9925
- * Standard: call on blur, not on every change.
9926
- */
9927
9750
  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
9751
  writeValue(v) {
9945
- this.value = this.normalizeBool(v);
9946
- this.cdr.markForCheck();
9752
+ this.value.set(this.normalizeBool(v));
9947
9753
  }
9948
- /**
9949
- * Registers callback that is triggered when the value changes.
9950
- */
9951
9754
  registerOnChange(fn) {
9952
9755
  this.onChange = fn;
9953
9756
  }
9954
- /**
9955
- * Registers callback that is triggered when the control is touched.
9956
- */
9957
9757
  registerOnTouched(fn) {
9958
9758
  this.onTouched = fn;
9959
9759
  }
9960
- /**
9961
- * Receives disabled state from Angular Forms and updates local state.
9962
- */
9963
9760
  setDisabledState(isDisabled) {
9964
- this.isDisabled = isDisabled;
9965
- this.cdr.markForCheck();
9761
+ this.formDisabled.set(isDisabled);
9966
9762
  }
9967
9763
  /**
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.
9764
+ * We use one-way [ngModel] + (ngModelChange) on purpose: two-way binding would
9765
+ * mutate the value before this handler runs and break CVA propagation.
9975
9766
  */
9976
9767
  onSwitchChange(next) {
9977
- if (this.disabled)
9768
+ if (this.disabled())
9978
9769
  return;
9979
9770
  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
9771
+ this.value.set(normalized);
9987
9772
  this.onChange(normalized);
9988
- this.cdr.markForCheck();
9989
9773
  }
9990
- /**
9991
- * Marks control as touched when user leaves the component.
9992
- */
9993
9774
  handleBlur() {
9994
- if (this.disabled)
9775
+ if (this.disabled())
9995
9776
  return;
9996
9777
  this.onTouched();
9997
9778
  }
9998
- /**
9999
- * Normalizes incoming values to strict boolean.
10000
- * Supports: boolean, "true"/"false", 1/0, "1"/"0".
10001
- */
9779
+ /** Normalizes boolean-ish values (boolean, "true"/"false", 1/0, "1"/"0"). */
10002
9780
  normalizeBool(v) {
10003
9781
  if (v === true || v === false)
10004
9782
  return v;
@@ -10016,7 +9794,7 @@ class MetaSwitchV2Component {
10016
9794
  return !!v;
10017
9795
  }
10018
9796
  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: [
9797
+ 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
9798
  {
10021
9799
  provide: NG_VALUE_ACCESSOR,
10022
9800
  useExisting: forwardRef(() => MetaSwitchV2Component),
@@ -10025,12 +9803,12 @@ class MetaSwitchV2Component {
10025
9803
  ], ngImport: i0, template: `
10026
9804
  <p-toggleSwitch
10027
9805
  class="phoenix-switch-v2"
10028
- [ngModel]="value"
9806
+ [ngModel]="value()"
10029
9807
  (ngModelChange)="onSwitchChange($event)"
10030
9808
  (onBlur)="handleBlur()"
10031
- [disabled]="disabled"
10032
- [hidden]="hidden"
10033
- [attr.data-cy]="dataCy ?? null"
9809
+ [disabled]="disabled()"
9810
+ [hidden]="hidden()"
9811
+ [attr.data-cy]="dataCy() ?? null"
10034
9812
  ></p-toggleSwitch>
10035
9813
  `, 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
9814
  }
@@ -10039,12 +9817,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10039
9817
  args: [{ selector: 'phoenix-meta-switch-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ToggleSwitchModule], template: `
10040
9818
  <p-toggleSwitch
10041
9819
  class="phoenix-switch-v2"
10042
- [ngModel]="value"
9820
+ [ngModel]="value()"
10043
9821
  (ngModelChange)="onSwitchChange($event)"
10044
9822
  (onBlur)="handleBlur()"
10045
- [disabled]="disabled"
10046
- [hidden]="hidden"
10047
- [attr.data-cy]="dataCy ?? null"
9823
+ [disabled]="disabled()"
9824
+ [hidden]="hidden()"
9825
+ [attr.data-cy]="dataCy() ?? null"
10048
9826
  ></p-toggleSwitch>
10049
9827
  `, providers: [
10050
9828
  {
@@ -10053,178 +9831,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10053
9831
  multi: true,
10054
9832
  },
10055
9833
  ], 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
- }] } });
9834
+ }], 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
9835
 
10064
9836
  /**
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.
9837
+ * V2 PASSWORD field — signal-based ControlValueAccessor (OnPush, no manual CD).
9838
+ * Value + disabled live in signals; touched is emitted on blur, value kept as a
9839
+ * string (null/undefined normalized to '').
10077
9840
  */
10078
9841
  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
- */
9842
+ /** Meta control configuration (kept for API compatibility). */
9843
+ control = input(...(ngDevMode ? [undefined, { debugName: "control" }] : []));
9844
+ /** Underlying control reference (kept for API compatibility). */
9845
+ ctrl = input(...(ngDevMode ? [undefined, { debugName: "ctrl" }] : []));
9846
+ /** Field-level disable (meta config), combined with the Angular Forms state. */
9847
+ disable = input(false, ...(ngDevMode ? [{ debugName: "disable" }] : []));
9848
+ /** Hide the control (meta hidden). Does not disable it. */
9849
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : []));
9850
+ /** Optional data-cy for e2e tests. */
9851
+ dataCy = input(...(ngDevMode ? [undefined, { debugName: "dataCy" }] : []));
9852
+ /** PrimeNG strength meter; off by default to match v1. */
9853
+ feedback = input(false, ...(ngDevMode ? [{ debugName: "feedback" }] : []));
9854
+ /** Autocomplete attribute (default "off"). */
9855
+ autocomplete = input('off', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
9856
+ /** Current password value (always a string). */
9857
+ value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : []));
9858
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9859
+ disabled = computed(() => this.disable() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
10126
9860
  onChange = () => { };
10127
- /**
10128
- * CVA callback invoked when the control is marked as touched.
10129
- * Standard: call on blur, not on every change.
10130
- */
10131
9861
  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
9862
  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();
9863
+ this.value.set(this.normalizeString(v));
10157
9864
  }
10158
- /**
10159
- * Registers callback that is triggered when the value changes.
10160
- */
10161
9865
  registerOnChange(fn) {
10162
9866
  this.onChange = fn;
10163
9867
  }
10164
- /**
10165
- * Registers callback that is triggered when the control is touched.
10166
- */
10167
9868
  registerOnTouched(fn) {
10168
9869
  this.onTouched = fn;
10169
9870
  }
10170
- /**
10171
- * Receives disabled state from Angular Forms and updates local state.
10172
- */
10173
9871
  setDisabledState(isDisabled) {
10174
- this.isDisabled = isDisabled;
10175
- this.cdr.markForCheck();
9872
+ this.formDisabled.set(isDisabled);
10176
9873
  }
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
- */
9874
+ /** Propagates every keystroke; touched is emitted on blur. */
10185
9875
  onPasswordInput(event) {
10186
- if (this.disabled)
9876
+ if (this.disabled())
10187
9877
  return;
10188
9878
  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;
9879
+ this.value.set(next);
10195
9880
  this.onChange(next);
10196
- this.cdr.markForCheck();
10197
9881
  }
10198
- /**
10199
- * Marks control as touched when user leaves the component.
10200
- */
10201
9882
  handleBlur() {
10202
- if (this.disabled)
9883
+ if (this.disabled())
10203
9884
  return;
10204
9885
  this.onTouched();
10205
9886
  }
10206
- /**
10207
- * PrimeNG input event -> string value.
10208
- * Supports:
10209
- * - native input event: event.target.value
10210
- * - direct string (some wrappers)
10211
- */
10212
9887
  getInputValue(event) {
10213
9888
  const v = event?.target?.value ?? event;
10214
9889
  return this.normalizeString(v);
10215
9890
  }
10216
- /**
10217
- * Normalizes incoming values to a string.
10218
- * - null/undefined -> ''
10219
- * - other -> String(v)
10220
- */
10221
9891
  normalizeString(v) {
10222
9892
  if (v === null || v === undefined)
10223
9893
  return '';
10224
9894
  return String(v);
10225
9895
  }
10226
9896
  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: [
9897
+ 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
9898
  {
10229
9899
  provide: NG_VALUE_ACCESSOR,
10230
9900
  useExisting: forwardRef(() => MetaPasswordFieldV2Component),
@@ -10232,22 +9902,16 @@ class MetaPasswordFieldV2Component {
10232
9902
  },
10233
9903
  ], ngImport: i0, template: `
10234
9904
  <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
9905
  <p-password
10242
9906
  class="phoenix-password-v2"
10243
- [(ngModel)]="value"
9907
+ [ngModel]="value()"
10244
9908
  (ngModelChange)="onPasswordInput($event)"
10245
9909
  (onBlur)="handleBlur()"
10246
- [disabled]="disabled"
10247
- [hidden]="hidden"
10248
- [feedback]="feedback"
10249
- [attr.autocomplete]="autocomplete"
10250
- [attr.data-cy]="dataCy ?? null"
9910
+ [disabled]="disabled()"
9911
+ [hidden]="hidden()"
9912
+ [feedback]="feedback()"
9913
+ [attr.autocomplete]="autocomplete()"
9914
+ [attr.data-cy]="dataCy() ?? null"
10251
9915
  ></p-password>
10252
9916
  </div>
10253
9917
  `, 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 +9920,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10256
9920
  type: Component,
10257
9921
  args: [{ selector: 'phoenix-meta-password-field-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, TranslateModule, PasswordModule], template: `
10258
9922
  <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
9923
  <p-password
10266
9924
  class="phoenix-password-v2"
10267
- [(ngModel)]="value"
9925
+ [ngModel]="value()"
10268
9926
  (ngModelChange)="onPasswordInput($event)"
10269
9927
  (onBlur)="handleBlur()"
10270
- [disabled]="disabled"
10271
- [hidden]="hidden"
10272
- [feedback]="feedback"
10273
- [attr.autocomplete]="autocomplete"
10274
- [attr.data-cy]="dataCy ?? null"
9928
+ [disabled]="disabled()"
9929
+ [hidden]="hidden()"
9930
+ [feedback]="feedback()"
9931
+ [attr.autocomplete]="autocomplete()"
9932
+ [attr.data-cy]="dataCy() ?? null"
10275
9933
  ></p-password>
10276
9934
  </div>
10277
9935
  `, providers: [
@@ -10281,47 +9939,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10281
9939
  multi: true,
10282
9940
  },
10283
9941
  ], 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
- }] } });
9942
+ }], 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
9943
 
9944
+ /**
9945
+ * Renders one meta field. Signal-based (OnPush): every template-facing value is a
9946
+ * `computed()` so it is memoized and recomputed only when its inputs actually
9947
+ * change — instead of a plain method re-running on every change-detection pass.
9948
+ * Control state (value/status) is bridged into a signal (`controlTick`), so the
9949
+ * error helpers stay reactive without a manual `markForCheck()`.
9950
+ */
10300
9951
  class MetaFormFieldV2Component {
10301
9952
  /** 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 */
9953
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
9954
+ /** Parent FormGroup that contains the FormControl for this field. */
9955
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
9956
+ /** Page-level read-only flag (renders the read-only view). */
9957
+ readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
9958
+ /** Global disable flag (merged with the field-level disable config). */
9959
+ disableForm = input(false, ...(ngDevMode ? [{ debugName: "disableForm" }] : []));
10320
9960
  translate = inject(TranslateService);
10321
- /**
10322
- * Exposed enum-like mapping of MetaFieldType for template usage.
10323
- * Keeps templates readable and avoids magic strings.
10324
- */
9961
+ /** Enum-like map for the template `@switch`. */
10325
9962
  MetaFieldType = Object.freeze({
10326
9963
  TEXT: 'TEXT',
10327
9964
  URL: 'URL',
@@ -10348,95 +9985,41 @@ class MetaFormFieldV2Component {
10348
9985
  LINKS_DATA: 'LINKS_DATA',
10349
9986
  SLOT: 'SLOT',
10350
9987
  });
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() {
9988
+ // ---- Pure, input-derived views (memoized) ----
9989
+ key = computed(() => this.field()?.configuration?.key ?? '', ...(ngDevMode ? [{ debugName: "key" }] : []));
9990
+ type = computed(() => this.field()?.configuration?.type ?? 'TEXT', ...(ngDevMode ? [{ debugName: "type" }] : []));
9991
+ colClass = computed(() => this.field()?.hidden ? 'p-0' : (this.field()?.style.colWidth ?? 'col-12 md:col-6'), ...(ngDevMode ? [{ debugName: "colClass" }] : []));
9992
+ userFriendlyMessage = computed(() => this.field()?.userFriendlyMessage ?? null, ...(ngDevMode ? [{ debugName: "userFriendlyMessage" }] : []));
9993
+ placeholderKey = computed(() => this.field()?.configuration?.placeholderKey ?? null, ...(ngDevMode ? [{ debugName: "placeholderKey" }] : []));
9994
+ isReadOnly = computed(() => !!this.readOnly() || !!this.field()?.readOnly, ...(ngDevMode ? [{ debugName: "isReadOnly" }] : []));
9995
+ isDisabled = computed(() => !!this.disableForm() || !!this.field()?.disable, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
9996
+ isCheckbox = computed(() => this.type() === this.MetaFieldType.CHECKBOX, ...(ngDevMode ? [{ debugName: "isCheckbox" }] : []));
9997
+ /** The underlying FormControl for this field (recomputed if form/key change). */
9998
+ ctrl = computed(() => this.form().get(this.key()) ?? null, ...(ngDevMode ? [{ debugName: "ctrl" }] : []));
9999
+ /**
10000
+ * Bridge the current control's value/status stream into a signal so the error
10001
+ * helpers below recompute reactively (reactive forms aren't signal-native yet).
10002
+ * Re-subscribes when the control identity changes.
10003
+ */
10004
+ controlTick = toSignal(toObservable(this.ctrl).pipe(switchMap$1((c) => c ? merge(c.valueChanges, c.statusChanges).pipe(startWith(null)) : EMPTY)), { initialValue: null });
10005
+ /** Bumped for silent (emitEvent:false) programmatic writes, e.g. the date field. */
10006
+ manualTick = signal(0, ...(ngDevMode ? [{ debugName: "manualTick" }] : []));
10007
+ /** Track the active language so translated error text refreshes on lang change. */
10008
+ lang = toSignal(this.translate.onLangChange.pipe(startWith(null)), { initialValue: null });
10009
+ /** Show validation only after interaction (touched/dirty). */
10010
+ showError = computed(() => {
10011
+ this.controlTick();
10012
+ this.manualTick();
10425
10013
  const c = this.ctrl();
10426
10014
  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() {
10015
+ }, ...(ngDevMode ? [{ debugName: "showError" }] : []));
10016
+ /** Normalized error key for the current control. */
10017
+ errorKey = computed(() => {
10018
+ this.controlTick();
10019
+ this.manualTick();
10436
10020
  const c = this.ctrl();
10437
10021
  if (!c?.errors)
10438
10022
  return null;
10439
- // Angular built-in validators
10440
10023
  if (c.errors['required'])
10441
10024
  return 'required';
10442
10025
  if (c.errors['minlength'])
@@ -10451,7 +10034,6 @@ class MetaFormFieldV2Component {
10451
10034
  return 'min';
10452
10035
  if (c.errors['max'])
10453
10036
  return 'max';
10454
- // Phoenix custom validators
10455
10037
  if (c.errors['dangerousChars'])
10456
10038
  return 'dangerousChars';
10457
10039
  if (c.errors['timeperiod'])
@@ -10462,21 +10044,17 @@ class MetaFormFieldV2Component {
10462
10044
  return 'dueDate';
10463
10045
  if (c.errors['bothDates'])
10464
10046
  return 'bothDates';
10465
- // Submit-only async validators
10466
10047
  if (c.errors['unique'])
10467
10048
  return 'unique';
10468
10049
  if (c.errors['uniqueEntry'])
10469
10050
  return 'uniqueEntry';
10470
10051
  if (c.errors['custom'])
10471
10052
  return 'custom';
10472
- // Fallback: return first error key
10473
10053
  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() {
10054
+ }, ...(ngDevMode ? [{ debugName: "errorKey" }] : []));
10055
+ /** Translated validation message (the single place that owns error UX text). */
10056
+ errorText = computed(() => {
10057
+ this.lang();
10480
10058
  const c = this.ctrl();
10481
10059
  const k = this.errorKey();
10482
10060
  if (!c || !k)
@@ -10497,10 +10075,8 @@ class MetaFormFieldV2Component {
10497
10075
  case 'dangerousChars':
10498
10076
  return this.translate.instant('VALIDATION_MESSAGE.NO_SPECIAL_CHARS_ALLOWED');
10499
10077
  case 'custom':
10500
- // Legacy behavior: custom error can already be a translation key
10501
10078
  return this.translate.instant(c.errors?.['custom']);
10502
10079
  case 'uniqueEntry':
10503
- // Legacy behavior: uniqueEntry may already be a translated string
10504
10080
  return (c.errors?.['uniqueEntry'] ??
10505
10081
  this.translate.instant('VALIDATION_MESSAGE.VALUE_IS_ALREADY_IN_USE'));
10506
10082
  case 'unique':
@@ -10517,7 +10093,6 @@ class MetaFormFieldV2Component {
10517
10093
  upperValue: c.errors?.['max']?.max,
10518
10094
  });
10519
10095
  case 'pattern': {
10520
- // Special-case URL pattern handling (legacy InlineFieldError behavior)
10521
10096
  const re = '^(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})[/\\w .-]*/?$';
10522
10097
  const requiredPattern = c.errors?.['pattern']?.requiredPattern;
10523
10098
  if (requiredPattern === re) {
@@ -10528,54 +10103,54 @@ class MetaFormFieldV2Component {
10528
10103
  default:
10529
10104
  return this.translate.instant('VALIDATION_MESSAGE.INVALID_VALUE');
10530
10105
  }
10106
+ }, ...(ngDevMode ? [{ debugName: "errorText" }] : []));
10107
+ /** Minimal value formatter for legacy read-only rendering (not used in template). */
10108
+ displayValue() {
10109
+ const v = this.ctrl()?.value;
10110
+ if (v === null || v === undefined)
10111
+ return '';
10112
+ if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
10113
+ return v;
10114
+ if (typeof v === 'object') {
10115
+ return v.label ?? v.name ?? v.fileName ?? JSON.stringify(v);
10116
+ }
10117
+ return String(v);
10531
10118
  }
10532
- /**
10533
- * Lightweight text formatter for simple read-only display use cases.
10534
- * This is used mainly for inline displays and summary UIs.
10535
- */
10119
+ /** Lightweight text formatter for simple read-only/summary display. */
10536
10120
  valueText() {
10537
10121
  const c = this.ctrl();
10538
10122
  const v = c?.value;
10539
10123
  if (v === null || v === undefined || v === '')
10540
10124
  return '--';
10541
- // Single-select option: resolve label from options
10542
- if (this.type === 'SS_OPTION') {
10543
- const opts = this.field?.configuration?.options ?? [];
10125
+ if (this.type() === 'SS_OPTION') {
10126
+ const opts = this.field()?.configuration?.options ?? [];
10544
10127
  if (typeof v !== 'object') {
10545
10128
  const hit = opts.find((o) => o?.value === v);
10546
- const label = hit?.label ?? v;
10547
- return this.translate.instant(label);
10129
+ return this.translate.instant(hit?.label ?? v);
10548
10130
  }
10549
- // Object value fallback
10550
- const label = v.label ?? v.value;
10551
- return this.translate.instant(label);
10131
+ return this.translate.instant(v.label ?? v.value);
10552
10132
  }
10553
- // Date formatting
10554
- if (this.type === 'DATE' && v instanceof Date) {
10133
+ if (this.type() === 'DATE' && v instanceof Date) {
10555
10134
  return v.toLocaleDateString();
10556
10135
  }
10557
- // Text editor / textarea: strip basic HTML tags for compact display
10558
- if (this.type === 'TEXT_EDITOR' || this.type === 'TEXT_AREA') {
10136
+ if (this.type() === 'TEXT_EDITOR' || this.type() === 'TEXT_AREA') {
10559
10137
  return String(v).replace(/<[^>]*>/g, '').trim() || '--';
10560
10138
  }
10561
10139
  return String(v);
10562
10140
  }
10563
- isCheckbox() {
10564
- return this.type === this.MetaFieldType.CHECKBOX;
10565
- }
10566
10141
  onDateSelected(val) {
10567
10142
  const ctrl = this.ctrl();
10568
10143
  if (!ctrl)
10569
10144
  return;
10570
- const d = val instanceof Date ? val : (val ? new Date(val) : null);
10145
+ const d = val instanceof Date ? val : val ? new Date(val) : null;
10571
10146
  if (!d || isNaN(d.getTime()))
10572
10147
  return;
10573
10148
  const normalized = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0);
10574
- // set without re-triggering loops
10149
+ // Silent write (no loops); bump the tick so error helpers re-evaluate.
10575
10150
  ctrl.setValue(normalized, { emitEvent: false });
10576
10151
  ctrl.markAsDirty();
10577
10152
  ctrl.markAsTouched();
10578
- this.cdr.markForCheck();
10153
+ this.manualTick.update((v) => v + 1);
10579
10154
  }
10580
10155
  onDateCleared() {
10581
10156
  const ctrl = this.ctrl();
@@ -10584,16 +10159,14 @@ class MetaFormFieldV2Component {
10584
10159
  ctrl.setValue(null, { emitEvent: false });
10585
10160
  ctrl.markAsDirty();
10586
10161
  ctrl.markAsTouched();
10587
- this.cdr.markForCheck();
10162
+ this.manualTick.update((v) => v + 1);
10588
10163
  }
10589
10164
  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\">\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\">\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\">\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:
10165
+ 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
10166
  // PrimeNG 20 base inputs
10592
10167
  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
10168
  // 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:
10169
+ 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
10170
  // Read-only renderer used when page or field is in read-only mode
10598
10171
  ReadOnlyInputV2Component, selector: "phoenix-read-only-input-v2", inputs: ["field", "form"] }, { kind: "pipe", type: i4$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10599
10172
  }
@@ -10613,7 +10186,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10613
10186
  DatePickerModule,
10614
10187
  MessageModule,
10615
10188
  // Advanced / custom Phoenix fields
10616
- MetaTimeperiodComponent,
10189
+ MetaTimeperiodV2Component,
10617
10190
  MetaCurrencyComponent,
10618
10191
  MetaStartDueDateV2Component,
10619
10192
  MetaTextEditorComponent,
@@ -10621,25 +10194,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10621
10194
  MetaSwitchV2Component,
10622
10195
  MetaSelectButtonComponent,
10623
10196
  MetaAssignResponsibleV2Component,
10624
- // MetaAssignAssetComponent,
10625
10197
  MetaPasswordFieldV2Component,
10626
10198
  MetaColorPickerV2Component,
10627
10199
  MetaUploadComponent,
10628
10200
  MetaUploadComponentDragDrop,
10629
10201
  // Read-only renderer used when page or field is in read-only mode
10630
10202
  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\">\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\">\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\">\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
- }] } });
10203
+ ], 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"] }]
10204
+ }], 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
10205
 
10644
10206
  /**
10645
10207
  * Splits a flat list of fields into row chunks for grid rendering.
@@ -11108,161 +10670,135 @@ function flattenControls(input) {
11108
10670
  .filter((x) => !!x?.configuration?.key);
11109
10671
  }
11110
10672
 
10673
+ /**
10674
+ * V2 form host. Signal-based (OnPush): inputs are `input()` signals, the
10675
+ * template-facing structure (`hasControls`/`isGrouped`/`groupedControls`/
10676
+ * `controlRows`) is `computed()` (so `splitControlsIntoRows` runs only when the
10677
+ * config changes, not on every CD pass), and `expandedGroupIds` is a signal.
10678
+ *
10679
+ * Reactive forms aren't signal-native, so control building stays imperative: the
10680
+ * first build runs synchronously in `ngOnInit` (controls exist before first
10681
+ * paint), and an `effect()` re-runs the same build when `config`/`form`/`readOnly`
10682
+ * change — with the same change-detection semantics the old `ngOnChanges` had.
10683
+ */
11111
10684
  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) */
10685
+ /** Form instance created/owned by the parent (dialog/page). */
10686
+ form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
10687
+ /** V2 metadata/config (controls, initialValues, submitValidators, setupDependencies). */
10688
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
10689
+ /** Page-level readOnly state (rendering + "enter edit mode" behavior). */
10690
+ readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
10691
+ /** Optional layout customization for the inner content wrapper. */
10692
+ contentStyle = input(null, ...(ngDevMode ? [{ debugName: "contentStyle" }] : []));
10693
+ /** Optional class name(s) for the inner content wrapper. */
10694
+ contentClass = input(null, ...(ngDevMode ? [{ debugName: "contentClass" }] : []));
11132
10695
  fb = inject(FormBuilder);
11133
- /** Registers and executes submit-only validators (async validation on submit) */
11134
10696
  submitValidator = inject(MetaSubmitValidatorService);
11135
- /** Used for validator localization (lang-dependent validators / messages) */
11136
10697
  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
- */
10698
+ /** Signature of the current schema (key+type only) to detect real schema changes. */
11141
10699
  lastSignature = '';
11142
- /**
11143
- * Cleanup function returned by setupDependencies (if any).
11144
- * Called only when metadata structure changes or on destroy.
11145
- */
10700
+ /** Cleanup from setupDependencies; called on schema change or destroy. */
11146
10701
  depCleanup;
10702
+ /** Previous input references, to reproduce the old SimpleChanges semantics. */
10703
+ prevConfig = null;
10704
+ prevForm = null;
10705
+ prevReadOnly = false;
10706
+ initialized = false;
10707
+ /** PrimeNG Accordion opened-panel ids. */
10708
+ expandedGroupIds = signal([], ...(ngDevMode ? [{ debugName: "expandedGroupIds" }] : []));
10709
+ // ---- Memoized, config-derived structure for the template ----
10710
+ hasControls = computed(() => Array.isArray(this.config()?.controls) && this.config().controls.length > 0, ...(ngDevMode ? [{ debugName: "hasControls" }] : []));
10711
+ /** Heuristic: grouped config has "ctrl" on the first element. */
10712
+ isGrouped = computed(() => {
10713
+ const c = this.config()?.controls ?? [];
10714
+ return !!c[0]?.ctrl;
10715
+ }, ...(ngDevMode ? [{ debugName: "isGrouped" }] : []));
10716
+ groupedControls = computed(() => this.config()?.controls ?? [], ...(ngDevMode ? [{ debugName: "groupedControls" }] : []));
10717
+ flatControls = computed(() => this.config()?.controls ?? [], ...(ngDevMode ? [{ debugName: "flatControls" }] : []));
10718
+ /** Flat schema split into rows, honoring `style.newRow` hard breaks. */
10719
+ controlRows = computed(() => splitControlsIntoRows(this.flatControls()), ...(ngDevMode ? [{ debugName: "controlRows" }] : []));
10720
+ constructor() {
10721
+ // Reactive updates on input changes. The first run happens during the first
10722
+ // CD (after ngOnInit's synchronous build), and no-ops via the change guard.
10723
+ effect(() => this.rebuild());
10724
+ }
10725
+ ngOnInit() {
10726
+ // First build runs synchronously so controls exist before the first paint.
10727
+ this.rebuild();
10728
+ }
11147
10729
  /**
11148
- * PrimeNG Accordion "value" for opened panels.
11149
- * For multiple panels, PrimeNG expects an array of ids.
10730
+ * Ensures controls, patches initial values, wires validators/dependencies and
10731
+ * initializes accordion state only when config/form/readOnly actually change
10732
+ * (same early-exit the old ngOnChanges had).
11150
10733
  */
11151
- expandedGroupIds = [];
11152
- ngOnChanges(changes) {
11153
- if (!this.form || !this.config)
10734
+ rebuild() {
10735
+ const form = this.form();
10736
+ const config = this.config();
10737
+ const readOnly = this.readOnly();
10738
+ if (!form || !config)
11154
10739
  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 */
10740
+ const flat = flattenControls(config.controls);
10741
+ const signature = flat
10742
+ .map((f) => `${f.configuration.key}:${f.configuration.type}`)
10743
+ .join('|');
10744
+ const configChanged = config !== this.prevConfig;
10745
+ const formChanged = form !== this.prevForm;
11172
10746
  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.
10747
+ const enteringEdit = this.initialized && this.prevReadOnly === true && readOnly === false;
10748
+ // Record inputs seen this run (before the early-exit, so the guard is stable).
10749
+ this.prevConfig = config;
10750
+ this.prevForm = form;
10751
+ this.prevReadOnly = readOnly;
10752
+ this.initialized = true;
11181
10753
  if (!configChanged && !formChanged && !metaChanged && !enteringEdit)
11182
10754
  return;
11183
- /**
11184
- * Dependencies are tied to the metadata structure.
11185
- * If schema changed, cleanup old subscriptions/bindings first.
11186
- */
11187
10755
  if (configChanged || metaChanged) {
11188
10756
  this.depCleanup?.();
11189
10757
  this.depCleanup = undefined;
11190
10758
  }
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
- */
10759
+ const initial = config.initialValues ?? {};
10760
+ ensureControlsV2(this.fb, form, flat, initial, { lang: this.translate.currentLang });
10761
+ // Patch initial values silently (no loops, silent create/edit init).
10762
+ form.patchValue(initial, { emitEvent: false });
10763
+ form.updateValueAndValidity({ emitEvent: false });
10764
+ // Initialize accordion open panels ONLY on schema change (don't reset the
10765
+ // user-collapsed state on a readOnly toggle).
11208
10766
  if (metaChanged) {
11209
- if (this.isGrouped) {
11210
- const groups = this.groupedControls ?? [];
11211
- this.expandedGroupIds = groups
10767
+ if (this.isGrouped()) {
10768
+ const groups = this.groupedControls() ?? [];
10769
+ this.expandedGroupIds.set(groups
11212
10770
  .filter((g) => !g?.collapsed)
11213
10771
  .map((g) => this.panelValue(g))
11214
- .filter(Boolean);
10772
+ .filter(Boolean));
11215
10773
  }
11216
10774
  else {
11217
- this.expandedGroupIds = [];
10775
+ this.expandedGroupIds.set([]);
11218
10776
  }
11219
10777
  }
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,
10778
+ this.submitValidator.register(form, config.submitValidators);
10779
+ if ((configChanged || metaChanged) && config.setupDependencies) {
10780
+ const maybeCleanup = config.setupDependencies({
10781
+ form,
11233
10782
  flatControls: flat,
11234
10783
  initialValues: initial,
11235
- getControl: (k) => this.form.get(k),
10784
+ getControl: (k) => form.get(k),
11236
10785
  findField: (k) => flat.find((f) => f.configuration.key === k) ?? null,
11237
10786
  });
11238
10787
  if (typeof maybeCleanup === 'function')
11239
10788
  this.depCleanup = maybeCleanup;
11240
10789
  }
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
10790
  if (enteringEdit) {
11248
10791
  queueMicrotask(() => {
11249
10792
  this.touchAndValidateOnlyFilledControls();
11250
10793
  this.expandVisibleInvalidGroupsUnion();
11251
10794
  });
11252
10795
  }
11253
- // Store signature for the next change detection pass
11254
10796
  this.lastSignature = signature;
11255
10797
  }
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
- */
10798
+ /** Normalizes PrimeNG Accordion value into a stable string[]. */
11262
10799
  onAccordionValueChange(v) {
11263
- this.expandedGroupIds = this.normalizeAccordionValue(v);
10800
+ this.expandedGroupIds.set(this.normalizeAccordionValue(v));
11264
10801
  }
11265
- /** Normalizes Accordion value into a stable string[] representation */
11266
10802
  normalizeAccordionValue(v) {
11267
10803
  if (Array.isArray(v))
11268
10804
  return v.map((x) => `${x}`);
@@ -11271,74 +10807,30 @@ class MetaFormV2Component {
11271
10807
  return [`${v}`];
11272
10808
  }
11273
10809
  // ---------------- 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 */
10810
+ /** TrackBy for group rendering. */
11302
10811
  groupTrack(g, idx) {
11303
10812
  return g?.id ?? idx;
11304
10813
  }
11305
- /**
11306
- * PrimeNG accordion panel `value` must match accordion `value` type.
11307
- * We always convert group id to string for consistent behavior.
11308
- */
10814
+ /** Group id as a string (matches the accordion `value` type). */
11309
10815
  panelValue(g) {
11310
10816
  const id = g?.id;
11311
10817
  return id === null || id === undefined ? '' : `${id}`;
11312
10818
  }
11313
10819
  // ---------------- 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
- */
10820
+ /** Marks & validates ONLY controls that already have meaningful values. */
11320
10821
  touchAndValidateOnlyFilledControls() {
11321
- const controls = this.form?.controls ?? {};
11322
- for (const [key, ctrl] of Object.entries(controls)) {
10822
+ const controls = this.form()?.controls ?? {};
10823
+ for (const [, ctrl] of Object.entries(controls)) {
11323
10824
  if (!ctrl)
11324
10825
  continue;
11325
- const value = ctrl.value;
11326
- // Touch only if this field is already populated (edit case) or prefilled.
11327
- if (this.hasMeaningfulValue(value)) {
10826
+ if (this.hasMeaningfulValue(ctrl.value)) {
11328
10827
  ctrl.markAsTouched();
11329
10828
  ctrl.updateValueAndValidity({ emitEvent: true });
11330
10829
  }
11331
10830
  }
11332
- // Optional: keep form status consistent after selective updates
11333
- this.form.updateValueAndValidity({ emitEvent: true });
10831
+ this.form().updateValueAndValidity({ emitEvent: true });
11334
10832
  }
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
- */
10833
+ /** What counts as a "meaningful" value (non-empty string/number/boolean/array/object). */
11342
10834
  hasMeaningfulValue(v) {
11343
10835
  if (v === null || v === undefined)
11344
10836
  return false;
@@ -11351,61 +10843,46 @@ class MetaFormV2Component {
11351
10843
  if (Array.isArray(v))
11352
10844
  return v.length > 0;
11353
10845
  if (typeof v === 'object') {
11354
- // Common selection shapes: { key }, { uuid }, { id }, etc.
11355
10846
  if ('key' in v && v.key != null && `${v.key}`.trim() !== '')
11356
10847
  return true;
11357
10848
  if ('uuid' in v && v.uuid != null && `${v.uuid}`.trim() !== '')
11358
10849
  return true;
11359
10850
  if ('id' in v && v.id != null && `${v.id}`.trim() !== '')
11360
10851
  return true;
11361
- // Fallback: any own keys
11362
10852
  return Object.keys(v).length > 0;
11363
10853
  }
11364
10854
  return false;
11365
10855
  }
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
- */
10856
+ /** "Visible invalid" = invalid AND interacted with (touched/dirty). */
11372
10857
  isVisibleInvalid(ctrl) {
11373
10858
  if (!ctrl)
11374
10859
  return false;
11375
10860
  return ctrl.invalid && (ctrl.touched || ctrl.dirty);
11376
10861
  }
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
10862
  groupHasVisibleInvalid(g) {
11382
10863
  const keys = (g?.ctrl ?? [])
11383
10864
  .map((f) => f?.configuration?.key)
11384
10865
  .filter(Boolean);
11385
- return keys.some((k) => this.isVisibleInvalid(this.form.get(k)));
10866
+ return keys.some((k) => this.isVisibleInvalid(this.form().get(k)));
11386
10867
  }
11387
- /**
11388
- * Expands all groups that contain visible invalid controls,
11389
- * while preserving any groups already expanded by the user.
11390
- */
10868
+ /** Expands groups with visible-invalid controls, preserving user-expanded ones. */
11391
10869
  expandVisibleInvalidGroupsUnion() {
11392
- if (!this.isGrouped)
10870
+ if (!this.isGrouped())
11393
10871
  return;
11394
- const groups = this.groupedControls ?? [];
10872
+ const groups = this.groupedControls() ?? [];
11395
10873
  const invalidIds = groups
11396
10874
  .filter((g) => this.groupHasVisibleInvalid(g))
11397
10875
  .map((g) => this.panelValue(g))
11398
10876
  .filter(Boolean);
11399
10877
  if (!invalidIds.length)
11400
10878
  return;
11401
- this.expandedGroupIds = Array.from(new Set([...this.expandedGroupIds, ...invalidIds]));
10879
+ this.expandedGroupIds.set(Array.from(new Set([...this.expandedGroupIds(), ...invalidIds])));
11402
10880
  }
11403
- /** Cleanup dependency subscriptions when component is destroyed */
11404
10881
  ngOnDestroy() {
11405
10882
  this.depCleanup?.();
11406
10883
  }
11407
10884
  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 });
10885
+ 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
10886
  }
11410
10887
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormV2Component, decorators: [{
11411
10888
  type: Component,
@@ -11416,20 +10893,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
11416
10893
  TranslateModule,
11417
10894
  MetaFormFieldV2Component,
11418
10895
  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
- }] } });
10896
+ ], 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"] }]
10897
+ }], 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
10898
 
11434
10899
  class MetaFormButtonsV2Component {
11435
10900
  /**