@fuentis/phoenix-ui 0.0.9-alpha.551 → 0.0.9-alpha.553
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/fuentis-phoenix-ui.mjs +1005 -133
- package/fesm2022/fuentis-phoenix-ui.mjs.map +1 -1
- package/index.d.ts +170 -16
- package/package.json +1 -1
|
@@ -8079,29 +8079,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8079
8079
|
}] });
|
|
8080
8080
|
|
|
8081
8081
|
class ReadOnlyInputV2Component {
|
|
8082
|
+
/** Field metadata (type, key, options, labels, etc.) */
|
|
8082
8083
|
field;
|
|
8084
|
+
/** Parent FormGroup that contains the control referenced by field.configuration.key */
|
|
8083
8085
|
form;
|
|
8086
|
+
/** Used to automatically unsubscribe from valueChanges on destroy */
|
|
8084
8087
|
dr = inject(DestroyRef);
|
|
8088
|
+
/**
|
|
8089
|
+
* Internal reactive signal holding the current control value.
|
|
8090
|
+
* We keep a local signal to optimize OnPush change detection and computed selectors.
|
|
8091
|
+
*/
|
|
8085
8092
|
_v = signal(null, ...(ngDevMode ? [{ debugName: "_v" }] : []));
|
|
8086
8093
|
ngOnInit() {
|
|
8094
|
+
// Initial sync of the control value into the local signal
|
|
8087
8095
|
this.sync();
|
|
8096
|
+
// Keep local signal in sync with the form control value
|
|
8088
8097
|
this.ctrl()?.valueChanges
|
|
8089
8098
|
.pipe(takeUntilDestroyed(this.dr))
|
|
8090
8099
|
.subscribe(() => this.sync());
|
|
8091
8100
|
}
|
|
8101
|
+
/** Control key resolved from MetaFieldConfig */
|
|
8092
8102
|
get key() {
|
|
8093
8103
|
return this.field?.configuration?.key ?? '';
|
|
8094
8104
|
}
|
|
8105
|
+
/** Meta field type (TEXT, DATE, ASSIGN, UPLOAD, etc.) */
|
|
8095
8106
|
get type() {
|
|
8096
8107
|
return this.field?.configuration?.type ?? 'TEXT';
|
|
8097
8108
|
}
|
|
8109
|
+
/** Shortcut to the underlying FormControl */
|
|
8098
8110
|
ctrl() {
|
|
8099
8111
|
return this.form.get(this.key);
|
|
8100
8112
|
}
|
|
8113
|
+
/**
|
|
8114
|
+
* Synchronizes the FormControl value into the local signal.
|
|
8115
|
+
* This keeps computed properties reactive and OnPush-friendly.
|
|
8116
|
+
*/
|
|
8101
8117
|
sync() {
|
|
8102
8118
|
this._v.set(this.ctrl()?.value ?? null);
|
|
8103
8119
|
}
|
|
8104
8120
|
// ---------- helpers ----------
|
|
8121
|
+
/**
|
|
8122
|
+
* Resolves language suffix used by backend DTOs (labelKeyValEn/De/Sr).
|
|
8123
|
+
* This is used to pick the correct localized label for option objects.
|
|
8124
|
+
*/
|
|
8105
8125
|
getLangSuffix() {
|
|
8106
8126
|
const lang = (localStorage.getItem('language') ?? 'en').toLowerCase();
|
|
8107
8127
|
if (lang.startsWith('de'))
|
|
@@ -8110,12 +8130,19 @@ class ReadOnlyInputV2Component {
|
|
|
8110
8130
|
return 'Sr';
|
|
8111
8131
|
return 'En';
|
|
8112
8132
|
}
|
|
8113
|
-
/**
|
|
8133
|
+
/**
|
|
8134
|
+
* Boolean renderer (for SWITCH / CHECKBOX).
|
|
8135
|
+
* Returns boolean value or null if the value is not a boolean.
|
|
8136
|
+
*/
|
|
8114
8137
|
bool = computed(() => {
|
|
8115
8138
|
const v = this._v();
|
|
8116
8139
|
return typeof v === 'boolean' ? v : null;
|
|
8117
8140
|
}, ...(ngDevMode ? [{ debugName: "bool" }] : []));
|
|
8118
|
-
/**
|
|
8141
|
+
/**
|
|
8142
|
+
* DATE renderer.
|
|
8143
|
+
* Supports both Date instances and ISO-like string values.
|
|
8144
|
+
* Returns a valid Date object or null if parsing fails.
|
|
8145
|
+
*/
|
|
8119
8146
|
dateValue = computed(() => {
|
|
8120
8147
|
const v = this._v();
|
|
8121
8148
|
if (!v)
|
|
@@ -8125,7 +8152,11 @@ class ReadOnlyInputV2Component {
|
|
|
8125
8152
|
const d = new Date(String(v));
|
|
8126
8153
|
return isNaN(d.getTime()) ? null : d;
|
|
8127
8154
|
}, ...(ngDevMode ? [{ debugName: "dateValue" }] : []));
|
|
8128
|
-
/**
|
|
8155
|
+
/**
|
|
8156
|
+
* START_DUE_DATE renderer.
|
|
8157
|
+
* Expects shape: { startDate, endDate } (Date or string).
|
|
8158
|
+
* Returns normalized Date objects or null if invalid.
|
|
8159
|
+
*/
|
|
8129
8160
|
startDue = computed(() => {
|
|
8130
8161
|
const v = this._v();
|
|
8131
8162
|
if (!v || typeof v !== 'object')
|
|
@@ -8141,7 +8172,10 @@ class ReadOnlyInputV2Component {
|
|
|
8141
8172
|
endDate: ed && !isNaN(ed.getTime()) ? ed : null,
|
|
8142
8173
|
};
|
|
8143
8174
|
}, ...(ngDevMode ? [{ debugName: "startDue" }] : []));
|
|
8144
|
-
/**
|
|
8175
|
+
/**
|
|
8176
|
+
* ASSIGN renderer.
|
|
8177
|
+
* Normalizes various backend DTO shapes into a unified view model.
|
|
8178
|
+
*/
|
|
8145
8179
|
assign = computed(() => {
|
|
8146
8180
|
const v = this._v();
|
|
8147
8181
|
if (!v || typeof v !== 'object')
|
|
@@ -8153,7 +8187,10 @@ class ReadOnlyInputV2Component {
|
|
|
8153
8187
|
phone: v?.phone ?? null,
|
|
8154
8188
|
};
|
|
8155
8189
|
}, ...(ngDevMode ? [{ debugName: "assign" }] : []));
|
|
8156
|
-
/**
|
|
8190
|
+
/**
|
|
8191
|
+
* UPLOAD renderer.
|
|
8192
|
+
* Supports common upload DTO shapes (fileName/name, size, type).
|
|
8193
|
+
*/
|
|
8157
8194
|
upload = computed(() => {
|
|
8158
8195
|
const v = this._v();
|
|
8159
8196
|
if (!v || typeof v !== 'object')
|
|
@@ -8164,16 +8201,21 @@ class ReadOnlyInputV2Component {
|
|
|
8164
8201
|
type: v?.type ?? null,
|
|
8165
8202
|
};
|
|
8166
8203
|
}, ...(ngDevMode ? [{ debugName: "upload" }] : []));
|
|
8167
|
-
/**
|
|
8204
|
+
/**
|
|
8205
|
+
* Single-select option label resolver (SS_OPTION / SS_OPTION_OBJECT_BASED).
|
|
8206
|
+
* - If value is primitive -> find matching option in configuration.options
|
|
8207
|
+
* - If value is object -> resolve best display label using common DTO fields
|
|
8208
|
+
*/
|
|
8168
8209
|
ssLabel = computed(() => {
|
|
8169
8210
|
const v = this._v();
|
|
8170
8211
|
if (v === null || v === undefined || v === '')
|
|
8171
8212
|
return null;
|
|
8172
|
-
//
|
|
8213
|
+
// Primitive value: resolve label from options list
|
|
8173
8214
|
if (typeof v !== 'object') {
|
|
8174
8215
|
const opt = (this.field.configuration.options ?? []).find((x) => x?.value === v);
|
|
8175
8216
|
return opt?.label ?? String(v);
|
|
8176
8217
|
}
|
|
8218
|
+
// Object value: resolve localized label from DTO
|
|
8177
8219
|
const suffix = this.getLangSuffix();
|
|
8178
8220
|
const key = `labelKeyVal${suffix}`;
|
|
8179
8221
|
return (v?.label ??
|
|
@@ -8183,7 +8225,10 @@ class ReadOnlyInputV2Component {
|
|
|
8183
8225
|
v?.value ??
|
|
8184
8226
|
null);
|
|
8185
8227
|
}, ...(ngDevMode ? [{ debugName: "ssLabel" }] : []));
|
|
8186
|
-
/**
|
|
8228
|
+
/**
|
|
8229
|
+
* Multi-select option label resolver (MS_OPTION).
|
|
8230
|
+
* Joins multiple labels into a single string with truncation after maxItems.
|
|
8231
|
+
*/
|
|
8187
8232
|
msLabel = computed(() => {
|
|
8188
8233
|
const v = this._v();
|
|
8189
8234
|
if (!Array.isArray(v) || v.length === 0)
|
|
@@ -8201,7 +8246,10 @@ class ReadOnlyInputV2Component {
|
|
|
8201
8246
|
? labels.join(', ')
|
|
8202
8247
|
: `${labels.slice(0, maxItems).join(', ')} ...`;
|
|
8203
8248
|
}, ...(ngDevMode ? [{ debugName: "msLabel" }] : []));
|
|
8204
|
-
/**
|
|
8249
|
+
/**
|
|
8250
|
+
* Generic fallback display value.
|
|
8251
|
+
* Ensures that every field type has a safe string representation.
|
|
8252
|
+
*/
|
|
8205
8253
|
displayText = computed(() => {
|
|
8206
8254
|
const v = this._v();
|
|
8207
8255
|
if (v === null || v === undefined || v === '')
|
|
@@ -8217,7 +8265,7 @@ class ReadOnlyInputV2Component {
|
|
|
8217
8265
|
if (this.type === 'ASSIGN' || this.type === 'ASSIGN_ASSET') {
|
|
8218
8266
|
return this.assign()?.name ?? null;
|
|
8219
8267
|
}
|
|
8220
|
-
//
|
|
8268
|
+
// Text areas and editors often contain HTML
|
|
8221
8269
|
if (this.type === 'TEXT_AREA' || this.type === 'TEXT_EDITOR') {
|
|
8222
8270
|
return typeof v === 'string' ? v : String(v);
|
|
8223
8271
|
}
|
|
@@ -8248,37 +8296,92 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8248
8296
|
}] } });
|
|
8249
8297
|
|
|
8250
8298
|
class MetaAssignResponsibleV2Component {
|
|
8251
|
-
/**
|
|
8252
|
-
|
|
8299
|
+
/**
|
|
8300
|
+
* List of available assignees used as table data inside the selection dialog.
|
|
8301
|
+
* This replaces legacy `control.configuration.items` access and keeps the CVA independent
|
|
8302
|
+
* from meta-form internals.
|
|
8303
|
+
*/
|
|
8304
|
+
items = [];
|
|
8305
|
+
/**
|
|
8306
|
+
* Translation key for the dialog header title.
|
|
8307
|
+
* Kept as an input so different contexts can reuse this field with a custom title.
|
|
8308
|
+
*/
|
|
8253
8309
|
dialogHeaderKey = 'LABELS.ASSIGN_RESPONSIBLE';
|
|
8254
8310
|
translate = inject(TranslateService);
|
|
8255
8311
|
dialog = inject(DialogService);
|
|
8312
|
+
/**
|
|
8313
|
+
* Currently selected assignee value bound to the parent form control.
|
|
8314
|
+
* We store the full object (row) because downstream fields often need more than uuid.
|
|
8315
|
+
*/
|
|
8256
8316
|
value = null;
|
|
8317
|
+
/**
|
|
8318
|
+
* Disabled state propagated from Angular forms via `setDisabledState`.
|
|
8319
|
+
* (If you later add a field-level `@Input() disable`, combine them like in other CVAs.)
|
|
8320
|
+
*/
|
|
8257
8321
|
disabled = false;
|
|
8322
|
+
/**
|
|
8323
|
+
* CVA callback invoked when the value changes (selection/clear).
|
|
8324
|
+
*/
|
|
8258
8325
|
onChange = () => { };
|
|
8326
|
+
/**
|
|
8327
|
+
* CVA callback invoked when the control is marked as touched (user interaction).
|
|
8328
|
+
* Standard: call on meaningful interactions (open dialog, select, clear).
|
|
8329
|
+
*/
|
|
8259
8330
|
onTouched = () => { };
|
|
8331
|
+
/**
|
|
8332
|
+
* Called by Angular forms when the model value changes programmatically.
|
|
8333
|
+
* Keep it side-effect free: do not call `onChange`/`onTouched` from here.
|
|
8334
|
+
*/
|
|
8260
8335
|
writeValue(value) {
|
|
8261
8336
|
this.value = value ?? null;
|
|
8262
8337
|
}
|
|
8338
|
+
/**
|
|
8339
|
+
* Registers the callback that should be called when the component updates the value.
|
|
8340
|
+
*/
|
|
8263
8341
|
registerOnChange(fn) {
|
|
8264
8342
|
this.onChange = fn;
|
|
8265
8343
|
}
|
|
8344
|
+
/**
|
|
8345
|
+
* Registers the callback that should be called when the control becomes "touched".
|
|
8346
|
+
*/
|
|
8266
8347
|
registerOnTouched(fn) {
|
|
8267
8348
|
this.onTouched = fn;
|
|
8268
8349
|
}
|
|
8350
|
+
/**
|
|
8351
|
+
* Receives disabled state from Angular forms and updates local state.
|
|
8352
|
+
*/
|
|
8269
8353
|
setDisabledState(isDisabled) {
|
|
8270
8354
|
this.disabled = isDisabled;
|
|
8271
8355
|
}
|
|
8356
|
+
/**
|
|
8357
|
+
* Clears current assignee value.
|
|
8358
|
+
* - emits `null`
|
|
8359
|
+
* - marks as touched (user action)
|
|
8360
|
+
*/
|
|
8272
8361
|
clear() {
|
|
8273
8362
|
if (this.disabled)
|
|
8274
8363
|
return;
|
|
8364
|
+
// prevent redundant emits
|
|
8365
|
+
if (this.value === null) {
|
|
8366
|
+
this.onTouched();
|
|
8367
|
+
return;
|
|
8368
|
+
}
|
|
8275
8369
|
this.value = null;
|
|
8276
8370
|
this.onChange(null);
|
|
8277
8371
|
this.onTouched();
|
|
8278
8372
|
}
|
|
8373
|
+
/**
|
|
8374
|
+
* Opens object selection dialog for choosing a new assignee.
|
|
8375
|
+
* The dialog renders a generic table and returns the selected row on close.
|
|
8376
|
+
*
|
|
8377
|
+
* Standard for CVA:
|
|
8378
|
+
* - mark as touched when user opens the picker (interaction started)
|
|
8379
|
+
* - emit onChange only when a row is actually selected
|
|
8380
|
+
*/
|
|
8279
8381
|
openDialog() {
|
|
8280
8382
|
if (this.disabled)
|
|
8281
8383
|
return;
|
|
8384
|
+
this.onTouched();
|
|
8282
8385
|
const ref = this.dialog.open(ObjectItemDialogComponent, {
|
|
8283
8386
|
header: this.translate.instant(this.dialogHeaderKey),
|
|
8284
8387
|
width: '700px',
|
|
@@ -8299,8 +8402,13 @@ class MetaAssignResponsibleV2Component {
|
|
|
8299
8402
|
ref?.onClose.subscribe((response) => {
|
|
8300
8403
|
if (!response)
|
|
8301
8404
|
return;
|
|
8405
|
+
// prevent redundant emits (same selection)
|
|
8406
|
+
const same = (this.value?.uuid ?? null) === (response?.uuid ?? null) &&
|
|
8407
|
+
JSON.stringify(this.value ?? null) === JSON.stringify(response ?? null);
|
|
8302
8408
|
this.value = response;
|
|
8303
|
-
|
|
8409
|
+
if (!same) {
|
|
8410
|
+
this.onChange(response);
|
|
8411
|
+
}
|
|
8304
8412
|
this.onTouched();
|
|
8305
8413
|
});
|
|
8306
8414
|
}
|
|
@@ -8461,32 +8569,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8461
8569
|
}] } });
|
|
8462
8570
|
|
|
8463
8571
|
class MetaColorPickerV2Component {
|
|
8464
|
-
/**
|
|
8572
|
+
/**
|
|
8573
|
+
* Optional external disable flag (field-level disable coming from meta config).
|
|
8574
|
+
* This is combined with the disabled state coming from Angular Forms (CVA).
|
|
8575
|
+
*/
|
|
8465
8576
|
disable = false;
|
|
8466
|
-
|
|
8577
|
+
cdr = inject(ChangeDetectorRef);
|
|
8578
|
+
/**
|
|
8579
|
+
* Currently selected color value.
|
|
8580
|
+
* Expected format: HEX string (e.g. "#ff00aa") or null.
|
|
8581
|
+
*/
|
|
8467
8582
|
value = null;
|
|
8583
|
+
/**
|
|
8584
|
+
* CVA callback invoked when the value changes.
|
|
8585
|
+
*/
|
|
8468
8586
|
onChange = () => { };
|
|
8587
|
+
/**
|
|
8588
|
+
* CVA callback invoked when the control is marked as touched.
|
|
8589
|
+
* Standard: call on blur / close, not on every value change.
|
|
8590
|
+
*/
|
|
8469
8591
|
onTouched = () => { };
|
|
8592
|
+
/**
|
|
8593
|
+
* Disabled state coming from Angular Forms (ControlValueAccessor).
|
|
8594
|
+
*/
|
|
8470
8595
|
isDisabled = false;
|
|
8596
|
+
/**
|
|
8597
|
+
* Final disabled state combining:
|
|
8598
|
+
* - form-level disabled state (CVA)
|
|
8599
|
+
* - field-level disable flag (input)
|
|
8600
|
+
*/
|
|
8601
|
+
get disabled() {
|
|
8602
|
+
return this.disable || this.isDisabled;
|
|
8603
|
+
}
|
|
8604
|
+
/**
|
|
8605
|
+
* Writes a new value from the parent form control into the component.
|
|
8606
|
+
* Keep it idempotent and UI-safe.
|
|
8607
|
+
*/
|
|
8471
8608
|
writeValue(v) {
|
|
8472
|
-
this.value = v
|
|
8609
|
+
this.value = this.normalizeHex(v);
|
|
8610
|
+
this.cdr.markForCheck();
|
|
8473
8611
|
}
|
|
8612
|
+
/**
|
|
8613
|
+
* Registers callback that is triggered when the value changes.
|
|
8614
|
+
*/
|
|
8474
8615
|
registerOnChange(fn) {
|
|
8475
8616
|
this.onChange = fn;
|
|
8476
8617
|
}
|
|
8618
|
+
/**
|
|
8619
|
+
* Registers callback that is triggered when the control is touched.
|
|
8620
|
+
*/
|
|
8477
8621
|
registerOnTouched(fn) {
|
|
8478
8622
|
this.onTouched = fn;
|
|
8479
8623
|
}
|
|
8624
|
+
/**
|
|
8625
|
+
* Receives disabled state from Angular Forms and updates local state.
|
|
8626
|
+
*/
|
|
8480
8627
|
setDisabledState(isDisabled) {
|
|
8481
8628
|
this.isDisabled = isDisabled;
|
|
8629
|
+
this.cdr.markForCheck();
|
|
8482
8630
|
}
|
|
8483
|
-
|
|
8631
|
+
/**
|
|
8632
|
+
* Handler for PrimeNG color picker change event.
|
|
8633
|
+
* Propagates the currently selected color value to the parent form control.
|
|
8634
|
+
*
|
|
8635
|
+
* Note: we intentionally do NOT call onTouched here (our standard is: touched on blur).
|
|
8636
|
+
*/
|
|
8484
8637
|
onPickerChange() {
|
|
8485
|
-
|
|
8638
|
+
if (this.disabled)
|
|
8639
|
+
return;
|
|
8640
|
+
const next = this.normalizeHex(this.value);
|
|
8641
|
+
// prevent redundant emits (useful when PrimeNG fires multiple times)
|
|
8642
|
+
if (next === this.value) {
|
|
8643
|
+
this.cdr.markForCheck();
|
|
8644
|
+
return;
|
|
8645
|
+
}
|
|
8646
|
+
this.value = next;
|
|
8647
|
+
this.onChange(next);
|
|
8648
|
+
this.cdr.markForCheck();
|
|
8649
|
+
}
|
|
8650
|
+
/**
|
|
8651
|
+
* Marks control as touched when user leaves the component.
|
|
8652
|
+
* (Matches "touched on blur" CVA guideline.)
|
|
8653
|
+
*/
|
|
8654
|
+
handleBlur() {
|
|
8655
|
+
if (this.disabled)
|
|
8656
|
+
return;
|
|
8486
8657
|
this.onTouched();
|
|
8487
8658
|
}
|
|
8488
|
-
|
|
8489
|
-
|
|
8659
|
+
/**
|
|
8660
|
+
* Normalizes incoming values to a safe HEX string or null.
|
|
8661
|
+
* - accepts "#RRGGBB" / "#RGB" / "RRGGBB"
|
|
8662
|
+
* - returns null for empty/invalid inputs
|
|
8663
|
+
*/
|
|
8664
|
+
normalizeHex(v) {
|
|
8665
|
+
if (v === null || v === undefined)
|
|
8666
|
+
return null;
|
|
8667
|
+
const s = String(v).trim();
|
|
8668
|
+
if (!s)
|
|
8669
|
+
return null;
|
|
8670
|
+
const withHash = s.startsWith('#') ? s : `#${s}`;
|
|
8671
|
+
const ok = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(withHash);
|
|
8672
|
+
return ok ? withHash.toLowerCase() : null;
|
|
8490
8673
|
}
|
|
8491
8674
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaColorPickerV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8492
8675
|
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: [
|
|
@@ -8496,28 +8679,26 @@ class MetaColorPickerV2Component {
|
|
|
8496
8679
|
multi: true,
|
|
8497
8680
|
},
|
|
8498
8681
|
], ngImport: i0, template: `
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
[appendTo]="'body'"
|
|
8682
|
+
<p-colorPicker
|
|
8683
|
+
class="color-swatch"
|
|
8684
|
+
[(ngModel)]="value"
|
|
8685
|
+
(onChange)="onPickerChange()"
|
|
8686
|
+
(onBlur)="handleBlur()"
|
|
8687
|
+
[disabled]="disabled"
|
|
8688
|
+
[appendTo]="'body'"
|
|
8507
8689
|
></p-colorPicker>
|
|
8508
8690
|
`, 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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$8.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 });
|
|
8509
8691
|
}
|
|
8510
8692
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaColorPickerV2Component, decorators: [{
|
|
8511
8693
|
type: Component,
|
|
8512
8694
|
args: [{ selector: 'phoenix-meta-color-picker-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ColorPickerModule], template: `
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
[appendTo]="'body'"
|
|
8695
|
+
<p-colorPicker
|
|
8696
|
+
class="color-swatch"
|
|
8697
|
+
[(ngModel)]="value"
|
|
8698
|
+
(onChange)="onPickerChange()"
|
|
8699
|
+
(onBlur)="handleBlur()"
|
|
8700
|
+
[disabled]="disabled"
|
|
8701
|
+
[appendTo]="'body'"
|
|
8521
8702
|
></p-colorPicker>
|
|
8522
8703
|
`, providers: [
|
|
8523
8704
|
{
|
|
@@ -8531,50 +8712,137 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8531
8712
|
}] } });
|
|
8532
8713
|
|
|
8533
8714
|
class MetaCheckboxColorPickerV2Component {
|
|
8715
|
+
/**
|
|
8716
|
+
* 2D array of color values used to render the color grid in the popover.
|
|
8717
|
+
* Example:
|
|
8718
|
+
* [
|
|
8719
|
+
* ['#ff0000', '#00ff00', '#0000ff'],
|
|
8720
|
+
* ['#facc15', '#22c55e', '#0ea5e9']
|
|
8721
|
+
* ]
|
|
8722
|
+
*/
|
|
8534
8723
|
options = [];
|
|
8724
|
+
/**
|
|
8725
|
+
* External disable flag (e.g. meta-form field-level disable).
|
|
8726
|
+
* This is combined with CVA disabled state.
|
|
8727
|
+
*/
|
|
8535
8728
|
disable = false;
|
|
8536
8729
|
cdr = inject(ChangeDetectorRef);
|
|
8730
|
+
/**
|
|
8731
|
+
* Currently selected color value bound to the parent form control.
|
|
8732
|
+
*/
|
|
8537
8733
|
value = null;
|
|
8734
|
+
/**
|
|
8735
|
+
* Color currently focused/hovered in the UI.
|
|
8736
|
+
* Used only for visual outline highlight in the picker grid.
|
|
8737
|
+
*/
|
|
8538
8738
|
focusedColor = null;
|
|
8739
|
+
/**
|
|
8740
|
+
* Disabled state coming from Angular Forms (ControlValueAccessor).
|
|
8741
|
+
*/
|
|
8539
8742
|
isDisabled = false;
|
|
8743
|
+
/**
|
|
8744
|
+
* CVA callback invoked when the value changes.
|
|
8745
|
+
*/
|
|
8540
8746
|
onChange = () => { };
|
|
8747
|
+
/**
|
|
8748
|
+
* CVA callback invoked when the control is marked as touched.
|
|
8749
|
+
* Standard: touched on "open/blur/close" actions, not necessarily on hover.
|
|
8750
|
+
*/
|
|
8541
8751
|
onTouched = () => { };
|
|
8542
|
-
|
|
8752
|
+
/**
|
|
8753
|
+
* Final disabled state combining:
|
|
8754
|
+
* - form-level disabled state (CVA)
|
|
8755
|
+
* - field-level disable flag (input)
|
|
8756
|
+
*/
|
|
8757
|
+
get disabled() {
|
|
8758
|
+
return this.disable || this.isDisabled;
|
|
8759
|
+
}
|
|
8760
|
+
// ----- ControlValueAccessor implementation -----
|
|
8761
|
+
/**
|
|
8762
|
+
* Writes a new value from the parent form into the component.
|
|
8763
|
+
* Keeps internal value and focusedColor in sync for correct UI outline.
|
|
8764
|
+
*/
|
|
8543
8765
|
writeValue(v) {
|
|
8544
|
-
this.value = v
|
|
8766
|
+
this.value = this.normalizeColor(v);
|
|
8545
8767
|
this.focusedColor = this.value;
|
|
8546
8768
|
this.cdr.markForCheck();
|
|
8547
8769
|
}
|
|
8770
|
+
/**
|
|
8771
|
+
* Registers callback that is triggered when the value changes.
|
|
8772
|
+
*/
|
|
8548
8773
|
registerOnChange(fn) {
|
|
8549
8774
|
this.onChange = fn;
|
|
8550
8775
|
}
|
|
8776
|
+
/**
|
|
8777
|
+
* Registers callback that is triggered when the control is touched.
|
|
8778
|
+
*/
|
|
8551
8779
|
registerOnTouched(fn) {
|
|
8552
8780
|
this.onTouched = fn;
|
|
8553
8781
|
}
|
|
8782
|
+
/**
|
|
8783
|
+
* Receives disabled state from Angular Forms and updates local state.
|
|
8784
|
+
*/
|
|
8554
8785
|
setDisabledState(isDisabled) {
|
|
8555
8786
|
this.isDisabled = isDisabled;
|
|
8556
8787
|
this.cdr.markForCheck();
|
|
8557
8788
|
}
|
|
8558
|
-
get disabled() {
|
|
8559
|
-
return this.disable || this.isDisabled;
|
|
8560
|
-
}
|
|
8561
8789
|
// ----- UI handlers -----
|
|
8790
|
+
/**
|
|
8791
|
+
* Toggles the popover visibility when the selected-color button is clicked.
|
|
8792
|
+
* We mark as touched because user interacted with the control.
|
|
8793
|
+
*/
|
|
8562
8794
|
toggle(popover, ev) {
|
|
8563
8795
|
if (this.disabled)
|
|
8564
8796
|
return;
|
|
8565
8797
|
this.onTouched();
|
|
8566
8798
|
popover.toggle(ev);
|
|
8567
8799
|
}
|
|
8800
|
+
/**
|
|
8801
|
+
* Handles color selection from the grid:
|
|
8802
|
+
* - updates internal value
|
|
8803
|
+
* - propagates value to parent form (onChange)
|
|
8804
|
+
* - marks control as touched (selection is a meaningful interaction)
|
|
8805
|
+
* - closes the popover
|
|
8806
|
+
*/
|
|
8568
8807
|
select(color, popover) {
|
|
8569
8808
|
if (this.disabled)
|
|
8570
8809
|
return;
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
this.
|
|
8810
|
+
const next = this.normalizeColor(color);
|
|
8811
|
+
// prevent redundant emits
|
|
8812
|
+
if (next === this.value) {
|
|
8813
|
+
this.focusedColor = next;
|
|
8814
|
+
this.onTouched();
|
|
8815
|
+
this.cdr.markForCheck();
|
|
8816
|
+
popover.hide();
|
|
8817
|
+
return;
|
|
8818
|
+
}
|
|
8819
|
+
this.focusedColor = next;
|
|
8820
|
+
this.value = next;
|
|
8821
|
+
this.onChange(next);
|
|
8574
8822
|
this.onTouched();
|
|
8575
8823
|
this.cdr.markForCheck();
|
|
8576
8824
|
popover.hide();
|
|
8577
8825
|
}
|
|
8826
|
+
/**
|
|
8827
|
+
* Normalizes incoming values to a safe CSS color string or null.
|
|
8828
|
+
* For this component we keep it permissive:
|
|
8829
|
+
* - accepts "#RRGGBB" / "#RGB"
|
|
8830
|
+
* - also allows any non-empty string (in case someone passes "red" or "var(--x)")
|
|
8831
|
+
* - returns null for empty values
|
|
8832
|
+
*/
|
|
8833
|
+
normalizeColor(v) {
|
|
8834
|
+
if (v === null || v === undefined)
|
|
8835
|
+
return null;
|
|
8836
|
+
const s = String(v).trim();
|
|
8837
|
+
if (!s)
|
|
8838
|
+
return null;
|
|
8839
|
+
// normalize common hex values (with/without '#')
|
|
8840
|
+
const withHash = s.startsWith('#') ? s : `#${s}`;
|
|
8841
|
+
if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(withHash))
|
|
8842
|
+
return withHash.toLowerCase();
|
|
8843
|
+
// fallback: allow CSS colors (e.g. "red") or CSS vars
|
|
8844
|
+
return s;
|
|
8845
|
+
}
|
|
8578
8846
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaCheckboxColorPickerV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8579
8847
|
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: [
|
|
8580
8848
|
{
|
|
@@ -8660,50 +8928,154 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8660
8928
|
}] } });
|
|
8661
8929
|
|
|
8662
8930
|
class MetaStartDueDateV2Component {
|
|
8663
|
-
/**
|
|
8931
|
+
/**
|
|
8932
|
+
* Optional data-cy attribute prefix for e2e testing.
|
|
8933
|
+
* Can be used by parent components to uniquely identify this field in tests.
|
|
8934
|
+
*/
|
|
8664
8935
|
dataCy;
|
|
8665
|
-
|
|
8936
|
+
/**
|
|
8937
|
+
* Optional parent-level disable flag (in addition to reactive-form disable).
|
|
8938
|
+
* Use this when the field must be disabled due to page/business logic.
|
|
8939
|
+
*/
|
|
8940
|
+
disable = false;
|
|
8941
|
+
cdr = inject(ChangeDetectorRef);
|
|
8942
|
+
/**
|
|
8943
|
+
* Disabled state coming from Angular Forms (ControlValueAccessor).
|
|
8944
|
+
* When true, both date pickers are non-interactive.
|
|
8945
|
+
*/
|
|
8946
|
+
isDisabled = false;
|
|
8947
|
+
/**
|
|
8948
|
+
* Local UI state for the selected start date.
|
|
8949
|
+
* Normalized to Date or null for PrimeNG DatePicker compatibility.
|
|
8950
|
+
*/
|
|
8666
8951
|
startDate = null;
|
|
8952
|
+
/**
|
|
8953
|
+
* Local UI state for the selected end date.
|
|
8954
|
+
* Normalized to Date or null for PrimeNG DatePicker compatibility.
|
|
8955
|
+
*/
|
|
8667
8956
|
endDate = null;
|
|
8957
|
+
/**
|
|
8958
|
+
* CVA callback invoked when the composite value changes.
|
|
8959
|
+
*/
|
|
8668
8960
|
onChange = () => { };
|
|
8961
|
+
/**
|
|
8962
|
+
* CVA callback invoked when the control is marked as touched.
|
|
8963
|
+
* IMPORTANT: We call this on blur (not on every change) to match standard CVA behavior.
|
|
8964
|
+
*/
|
|
8669
8965
|
onTouched = () => { };
|
|
8966
|
+
/**
|
|
8967
|
+
* Effective disabled state used by the template.
|
|
8968
|
+
* Combines reactive form disable + parent-level disable.
|
|
8969
|
+
*/
|
|
8970
|
+
get disabled() {
|
|
8971
|
+
return this.disable || this.isDisabled;
|
|
8972
|
+
}
|
|
8973
|
+
/**
|
|
8974
|
+
* Writes a new value from the parent form control into the component.
|
|
8975
|
+
* Incoming values are normalized to Date instances for the UI layer.
|
|
8976
|
+
*
|
|
8977
|
+
* NOTE: "YYYY-MM-DD" strings are parsed as local dates to avoid timezone day-shifts.
|
|
8978
|
+
*/
|
|
8670
8979
|
writeValue(v) {
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
this.
|
|
8980
|
+
this.startDate = this.parseToDate(v?.startDate ?? null);
|
|
8981
|
+
this.endDate = this.parseToDate(v?.endDate ?? null);
|
|
8982
|
+
// OnPush: ensure UI reflects external value writes (patchValue/setValue)
|
|
8983
|
+
this.cdr.markForCheck();
|
|
8675
8984
|
}
|
|
8985
|
+
/**
|
|
8986
|
+
* Registers the callback that should be called when the value changes.
|
|
8987
|
+
*/
|
|
8676
8988
|
registerOnChange(fn) {
|
|
8677
8989
|
this.onChange = fn;
|
|
8678
8990
|
}
|
|
8991
|
+
/**
|
|
8992
|
+
* Registers the callback that should be called when the control is touched.
|
|
8993
|
+
*/
|
|
8679
8994
|
registerOnTouched(fn) {
|
|
8680
8995
|
this.onTouched = fn;
|
|
8681
8996
|
}
|
|
8997
|
+
/**
|
|
8998
|
+
* Receives disabled state from Angular Forms and updates local state.
|
|
8999
|
+
*/
|
|
8682
9000
|
setDisabledState(isDisabled) {
|
|
8683
|
-
this.
|
|
9001
|
+
this.isDisabled = isDisabled;
|
|
9002
|
+
// OnPush: reflect disabled state changes immediately
|
|
9003
|
+
this.cdr.markForCheck();
|
|
9004
|
+
}
|
|
9005
|
+
/**
|
|
9006
|
+
* Handler used by PrimeNG DatePicker blur events.
|
|
9007
|
+
* Marks the control as touched without emitting a value change.
|
|
9008
|
+
*/
|
|
9009
|
+
handleBlur() {
|
|
9010
|
+
if (this.disabled)
|
|
9011
|
+
return;
|
|
9012
|
+
this.onTouched();
|
|
8684
9013
|
}
|
|
9014
|
+
/**
|
|
9015
|
+
* Handler for start date change coming from the DatePicker.
|
|
9016
|
+
* Updates local state and propagates the composite value.
|
|
9017
|
+
*/
|
|
8685
9018
|
onStartChange(d) {
|
|
9019
|
+
if (this.disabled)
|
|
9020
|
+
return;
|
|
8686
9021
|
this.startDate = d ?? null;
|
|
8687
|
-
this.
|
|
9022
|
+
this.emitChange();
|
|
8688
9023
|
}
|
|
9024
|
+
/**
|
|
9025
|
+
* Handler for end date change coming from the DatePicker.
|
|
9026
|
+
* Updates local state and propagates the composite value.
|
|
9027
|
+
*/
|
|
8689
9028
|
onEndChange(d) {
|
|
9029
|
+
if (this.disabled)
|
|
9030
|
+
return;
|
|
8690
9031
|
this.endDate = d ?? null;
|
|
8691
|
-
this.
|
|
9032
|
+
this.emitChange();
|
|
8692
9033
|
}
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
9034
|
+
/**
|
|
9035
|
+
* Emits the composite value to the parent form control.
|
|
9036
|
+
* - Emits `null` when both dates are empty (cleaner semantics for required/bothDates validators).
|
|
9037
|
+
* - Otherwise emits the `{ startDate, endDate }` object (partial values allowed; validator decides).
|
|
9038
|
+
*/
|
|
9039
|
+
emitChange() {
|
|
8696
9040
|
if (!this.startDate && !this.endDate) {
|
|
8697
9041
|
this.onChange(null);
|
|
9042
|
+
this.cdr.markForCheck();
|
|
8698
9043
|
return;
|
|
8699
9044
|
}
|
|
8700
9045
|
this.onChange({
|
|
8701
9046
|
startDate: this.startDate,
|
|
8702
9047
|
endDate: this.endDate,
|
|
8703
9048
|
});
|
|
9049
|
+
this.cdr.markForCheck();
|
|
9050
|
+
}
|
|
9051
|
+
/**
|
|
9052
|
+
* Parses Date | string safely into a Date instance for the UI.
|
|
9053
|
+
* Important: "YYYY-MM-DD" is treated as a local date (prevents timezone day-shift).
|
|
9054
|
+
*/
|
|
9055
|
+
parseToDate(v) {
|
|
9056
|
+
if (!v)
|
|
9057
|
+
return null;
|
|
9058
|
+
if (v instanceof Date) {
|
|
9059
|
+
return isNaN(v.getTime()) ? null : v;
|
|
9060
|
+
}
|
|
9061
|
+
const s = String(v).trim();
|
|
9062
|
+
if (!s)
|
|
9063
|
+
return null;
|
|
9064
|
+
// Date-only string -> create LOCAL date (avoid UTC shifting)
|
|
9065
|
+
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
9066
|
+
if (m) {
|
|
9067
|
+
const y = Number(m[1]);
|
|
9068
|
+
const mo = Number(m[2]) - 1;
|
|
9069
|
+
const d = Number(m[3]);
|
|
9070
|
+
const local = new Date(y, mo, d);
|
|
9071
|
+
return isNaN(local.getTime()) ? null : local;
|
|
9072
|
+
}
|
|
9073
|
+
// ISO / other -> fallback
|
|
9074
|
+
const dt = new Date(s);
|
|
9075
|
+
return isNaN(dt.getTime()) ? null : dt;
|
|
8704
9076
|
}
|
|
8705
9077
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaStartDueDateV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8706
|
-
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" }, providers: [
|
|
9078
|
+
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: [
|
|
8707
9079
|
{
|
|
8708
9080
|
provide: NG_VALUE_ACCESSOR,
|
|
8709
9081
|
useExisting: forwardRef(() => MetaStartDueDateV2Component),
|
|
@@ -8722,6 +9094,7 @@ class MetaStartDueDateV2Component {
|
|
|
8722
9094
|
[disabled]="disabled"
|
|
8723
9095
|
[ngModel]="startDate"
|
|
8724
9096
|
(ngModelChange)="onStartChange($event)"
|
|
9097
|
+
(onBlur)="handleBlur()"
|
|
8725
9098
|
appendTo="body"
|
|
8726
9099
|
></p-datepicker>
|
|
8727
9100
|
</div>
|
|
@@ -8740,16 +9113,17 @@ class MetaStartDueDateV2Component {
|
|
|
8740
9113
|
[disabled]="disabled"
|
|
8741
9114
|
[ngModel]="endDate"
|
|
8742
9115
|
(ngModelChange)="onEndChange($event)"
|
|
9116
|
+
(onBlur)="handleBlur()"
|
|
8743
9117
|
appendTo="body"
|
|
8744
9118
|
></p-datepicker>
|
|
8745
9119
|
</div>
|
|
8746
9120
|
</div>
|
|
8747
9121
|
</div>
|
|
8748
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i2$7.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: "pipe", type: i3$2.TranslatePipe, name: "translate" }] });
|
|
9122
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i2$7.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: "pipe", type: i3$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8749
9123
|
}
|
|
8750
9124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaStartDueDateV2Component, decorators: [{
|
|
8751
9125
|
type: Component,
|
|
8752
|
-
args: [{ selector: 'phoenix-meta-start-due-date-v2', standalone: true, imports: [CommonModule, FormsModule, TranslateModule, DatePickerModule], providers: [
|
|
9126
|
+
args: [{ selector: 'phoenix-meta-start-due-date-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, TranslateModule, DatePickerModule], providers: [
|
|
8753
9127
|
{
|
|
8754
9128
|
provide: NG_VALUE_ACCESSOR,
|
|
8755
9129
|
useExisting: forwardRef(() => MetaStartDueDateV2Component),
|
|
@@ -8768,6 +9142,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8768
9142
|
[disabled]="disabled"
|
|
8769
9143
|
[ngModel]="startDate"
|
|
8770
9144
|
(ngModelChange)="onStartChange($event)"
|
|
9145
|
+
(onBlur)="handleBlur()"
|
|
8771
9146
|
appendTo="body"
|
|
8772
9147
|
></p-datepicker>
|
|
8773
9148
|
</div>
|
|
@@ -8786,6 +9161,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8786
9161
|
[disabled]="disabled"
|
|
8787
9162
|
[ngModel]="endDate"
|
|
8788
9163
|
(ngModelChange)="onEndChange($event)"
|
|
9164
|
+
(onBlur)="handleBlur()"
|
|
8789
9165
|
appendTo="body"
|
|
8790
9166
|
></p-datepicker>
|
|
8791
9167
|
</div>
|
|
@@ -8794,18 +9170,197 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
8794
9170
|
`, styles: [":host{display:block}\n"] }]
|
|
8795
9171
|
}], propDecorators: { dataCy: [{
|
|
8796
9172
|
type: Input
|
|
9173
|
+
}], disable: [{
|
|
9174
|
+
type: Input
|
|
9175
|
+
}] } });
|
|
9176
|
+
|
|
9177
|
+
class MetaSwitchV2Component {
|
|
9178
|
+
/**
|
|
9179
|
+
* Optional external disable flag (field-level disable coming from meta config).
|
|
9180
|
+
* This is combined with the disabled state coming from Angular Forms (CVA).
|
|
9181
|
+
*/
|
|
9182
|
+
disable = false;
|
|
9183
|
+
/**
|
|
9184
|
+
* Optional flag to hide the switch from UI (meta hidden).
|
|
9185
|
+
* Keep in mind: hidden does not automatically disable the control.
|
|
9186
|
+
*/
|
|
9187
|
+
hidden = false;
|
|
9188
|
+
/**
|
|
9189
|
+
* Optional data-cy attribute for e2e tests (e.g. "switch-singleEntity").
|
|
9190
|
+
*/
|
|
9191
|
+
dataCy;
|
|
9192
|
+
cdr = inject(ChangeDetectorRef);
|
|
9193
|
+
/**
|
|
9194
|
+
* Current boolean value.
|
|
9195
|
+
* We keep it strictly boolean to avoid "true"/"false" string issues.
|
|
9196
|
+
*/
|
|
9197
|
+
value = false;
|
|
9198
|
+
/**
|
|
9199
|
+
* CVA callback invoked when the value changes.
|
|
9200
|
+
*/
|
|
9201
|
+
onChange = () => { };
|
|
9202
|
+
/**
|
|
9203
|
+
* CVA callback invoked when the control is marked as touched.
|
|
9204
|
+
* Standard: call on blur, not on every change.
|
|
9205
|
+
*/
|
|
9206
|
+
onTouched = () => { };
|
|
9207
|
+
/**
|
|
9208
|
+
* Disabled state coming from Angular Forms (ControlValueAccessor).
|
|
9209
|
+
*/
|
|
9210
|
+
isDisabled = false;
|
|
9211
|
+
/**
|
|
9212
|
+
* Final disabled state combining:
|
|
9213
|
+
* - form-level disabled state (CVA)
|
|
9214
|
+
* - field-level disable flag (input)
|
|
9215
|
+
*/
|
|
9216
|
+
get disabled() {
|
|
9217
|
+
return this.disable || this.isDisabled;
|
|
9218
|
+
}
|
|
9219
|
+
/**
|
|
9220
|
+
* Writes a new value from the parent form control into the component.
|
|
9221
|
+
* Keep it idempotent and UI-safe.
|
|
9222
|
+
*/
|
|
9223
|
+
writeValue(v) {
|
|
9224
|
+
this.value = this.normalizeBool(v);
|
|
9225
|
+
this.cdr.markForCheck();
|
|
9226
|
+
}
|
|
9227
|
+
/**
|
|
9228
|
+
* Registers callback that is triggered when the value changes.
|
|
9229
|
+
*/
|
|
9230
|
+
registerOnChange(fn) {
|
|
9231
|
+
this.onChange = fn;
|
|
9232
|
+
}
|
|
9233
|
+
/**
|
|
9234
|
+
* Registers callback that is triggered when the control is touched.
|
|
9235
|
+
*/
|
|
9236
|
+
registerOnTouched(fn) {
|
|
9237
|
+
this.onTouched = fn;
|
|
9238
|
+
}
|
|
9239
|
+
/**
|
|
9240
|
+
* Receives disabled state from Angular Forms and updates local state.
|
|
9241
|
+
*/
|
|
9242
|
+
setDisabledState(isDisabled) {
|
|
9243
|
+
this.isDisabled = isDisabled;
|
|
9244
|
+
this.cdr.markForCheck();
|
|
9245
|
+
}
|
|
9246
|
+
/**
|
|
9247
|
+
* Handler for switch change.
|
|
9248
|
+
* Propagates value to the parent form control.
|
|
9249
|
+
*
|
|
9250
|
+
* Note: we intentionally do NOT call onTouched here (our standard is: touched on blur).
|
|
9251
|
+
*/
|
|
9252
|
+
onSwitchChange(next) {
|
|
9253
|
+
if (this.disabled)
|
|
9254
|
+
return;
|
|
9255
|
+
const normalized = this.normalizeBool(next);
|
|
9256
|
+
// prevent redundant emits
|
|
9257
|
+
if (normalized === this.value) {
|
|
9258
|
+
this.cdr.markForCheck();
|
|
9259
|
+
return;
|
|
9260
|
+
}
|
|
9261
|
+
this.value = normalized;
|
|
9262
|
+
this.onChange(normalized);
|
|
9263
|
+
this.cdr.markForCheck();
|
|
9264
|
+
}
|
|
9265
|
+
/**
|
|
9266
|
+
* Marks control as touched when user leaves the component.
|
|
9267
|
+
*/
|
|
9268
|
+
handleBlur() {
|
|
9269
|
+
if (this.disabled)
|
|
9270
|
+
return;
|
|
9271
|
+
this.onTouched();
|
|
9272
|
+
}
|
|
9273
|
+
/**
|
|
9274
|
+
* Normalizes incoming values to strict boolean.
|
|
9275
|
+
* Supports: boolean, "true"/"false", 1/0, "1"/"0".
|
|
9276
|
+
*/
|
|
9277
|
+
normalizeBool(v) {
|
|
9278
|
+
if (v === true || v === false)
|
|
9279
|
+
return v;
|
|
9280
|
+
if (v === 1 || v === '1')
|
|
9281
|
+
return true;
|
|
9282
|
+
if (v === 0 || v === '0')
|
|
9283
|
+
return false;
|
|
9284
|
+
if (typeof v === 'string') {
|
|
9285
|
+
const s = v.trim().toLowerCase();
|
|
9286
|
+
if (s === 'true')
|
|
9287
|
+
return true;
|
|
9288
|
+
if (s === 'false')
|
|
9289
|
+
return false;
|
|
9290
|
+
}
|
|
9291
|
+
return !!v;
|
|
9292
|
+
}
|
|
9293
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaSwitchV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9294
|
+
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: [
|
|
9295
|
+
{
|
|
9296
|
+
provide: NG_VALUE_ACCESSOR,
|
|
9297
|
+
useExisting: forwardRef(() => MetaSwitchV2Component),
|
|
9298
|
+
multi: true,
|
|
9299
|
+
},
|
|
9300
|
+
], ngImport: i0, template: `
|
|
9301
|
+
<p-toggleSwitch
|
|
9302
|
+
class="phoenix-switch-v2"
|
|
9303
|
+
[(ngModel)]="value"
|
|
9304
|
+
(ngModelChange)="onSwitchChange($event)"
|
|
9305
|
+
(onBlur)="handleBlur()"
|
|
9306
|
+
[disabled]="disabled"
|
|
9307
|
+
[hidden]="hidden"
|
|
9308
|
+
[attr.data-cy]="dataCy ?? null"
|
|
9309
|
+
></p-toggleSwitch>
|
|
9310
|
+
`, 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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i3$7.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 });
|
|
9311
|
+
}
|
|
9312
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaSwitchV2Component, decorators: [{
|
|
9313
|
+
type: Component,
|
|
9314
|
+
args: [{ selector: 'phoenix-meta-switch-v2', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, ToggleSwitchModule], template: `
|
|
9315
|
+
<p-toggleSwitch
|
|
9316
|
+
class="phoenix-switch-v2"
|
|
9317
|
+
[(ngModel)]="value"
|
|
9318
|
+
(ngModelChange)="onSwitchChange($event)"
|
|
9319
|
+
(onBlur)="handleBlur()"
|
|
9320
|
+
[disabled]="disabled"
|
|
9321
|
+
[hidden]="hidden"
|
|
9322
|
+
[attr.data-cy]="dataCy ?? null"
|
|
9323
|
+
></p-toggleSwitch>
|
|
9324
|
+
`, providers: [
|
|
9325
|
+
{
|
|
9326
|
+
provide: NG_VALUE_ACCESSOR,
|
|
9327
|
+
useExisting: forwardRef(() => MetaSwitchV2Component),
|
|
9328
|
+
multi: true,
|
|
9329
|
+
},
|
|
9330
|
+
], styles: [":host ::ng-deep .p-toggleswitch{margin-top:12px}\n"] }]
|
|
9331
|
+
}], propDecorators: { disable: [{
|
|
9332
|
+
type: Input
|
|
9333
|
+
}], hidden: [{
|
|
9334
|
+
type: Input
|
|
9335
|
+
}], dataCy: [{
|
|
9336
|
+
type: Input
|
|
8797
9337
|
}] } });
|
|
8798
9338
|
|
|
8799
9339
|
class MetaFormFieldV2Component {
|
|
9340
|
+
/** Metadata definition of the field (type, key, options, styles, flags, etc.) */
|
|
8800
9341
|
field;
|
|
9342
|
+
/** Parent FormGroup that contains the FormControl for this field */
|
|
8801
9343
|
form;
|
|
8802
|
-
/**
|
|
9344
|
+
/**
|
|
9345
|
+
* Page-level read-only flag.
|
|
9346
|
+
* When true, the component renders ReadOnlyInputV2Component instead of editable controls.
|
|
9347
|
+
*/
|
|
8803
9348
|
readOnly = false;
|
|
8804
|
-
|
|
9349
|
+
/**
|
|
9350
|
+
* Global disable flag (e.g. parent dialog toggles entire form disabled).
|
|
9351
|
+
* This is merged with field-level disable configuration.
|
|
9352
|
+
*/
|
|
8805
9353
|
disableForm = false;
|
|
9354
|
+
/** Used to manually trigger change detection for OnPush strategy */
|
|
8806
9355
|
cdr = inject(ChangeDetectorRef);
|
|
9356
|
+
/** Used to automatically unsubscribe from value/status streams on destroy */
|
|
8807
9357
|
dr = inject(DestroyRef);
|
|
9358
|
+
/** Translation service for validation and display labels */
|
|
8808
9359
|
translate = inject(TranslateService);
|
|
9360
|
+
/**
|
|
9361
|
+
* Exposed enum-like mapping of MetaFieldType for template usage.
|
|
9362
|
+
* Keeps templates readable and avoids magic strings.
|
|
9363
|
+
*/
|
|
8809
9364
|
MetaFieldType = Object.freeze({
|
|
8810
9365
|
TEXT: 'TEXT',
|
|
8811
9366
|
NUMBER: 'NUMBER',
|
|
@@ -8831,61 +9386,95 @@ class MetaFormFieldV2Component {
|
|
|
8831
9386
|
LINKS_DATA: 'LINKS_DATA',
|
|
8832
9387
|
SLOT: 'SLOT',
|
|
8833
9388
|
});
|
|
9389
|
+
/** Control key resolved from MetaFieldConfig */
|
|
8834
9390
|
get key() {
|
|
8835
9391
|
return this.field?.configuration?.key ?? '';
|
|
8836
9392
|
}
|
|
9393
|
+
/** Field type resolved from MetaFieldConfig */
|
|
8837
9394
|
get type() {
|
|
8838
9395
|
return this.field?.configuration?.type ?? 'TEXT';
|
|
8839
9396
|
}
|
|
9397
|
+
/** Column width class for grid layout (falls back to default if not provided) */
|
|
8840
9398
|
get colClass() {
|
|
8841
|
-
return this.field?.hidden
|
|
9399
|
+
return this.field?.hidden
|
|
9400
|
+
? 'p-0'
|
|
9401
|
+
: (this.field?.style.colWidth ?? 'col-12 md:col-6');
|
|
8842
9402
|
}
|
|
8843
9403
|
ngOnInit() {
|
|
8844
9404
|
const ctrl = this.ctrl();
|
|
8845
9405
|
if (!ctrl)
|
|
8846
9406
|
return;
|
|
9407
|
+
/**
|
|
9408
|
+
* Subscribe to both valueChanges and statusChanges so the component:
|
|
9409
|
+
* - re-renders when user changes the value
|
|
9410
|
+
* - re-renders when validation state changes (touched/dirty/errors)
|
|
9411
|
+
*/
|
|
8847
9412
|
merge(ctrl.valueChanges, ctrl.statusChanges)
|
|
8848
9413
|
.pipe(takeUntilDestroyed(this.dr))
|
|
8849
9414
|
.subscribe(() => this.cdr.markForCheck());
|
|
8850
9415
|
}
|
|
9416
|
+
/** Human-friendly label defined in metadata (already localized key) */
|
|
8851
9417
|
userFriendlyMessage() {
|
|
8852
9418
|
return this.field?.userFriendlyMessage ?? null;
|
|
8853
9419
|
}
|
|
9420
|
+
/** Optional placeholder i18n key defined in metadata */
|
|
8854
9421
|
placeholderKey() {
|
|
8855
9422
|
return this.field?.configuration?.placeholderKey ?? null;
|
|
8856
9423
|
}
|
|
9424
|
+
/**
|
|
9425
|
+
* Resolves final read-only state for this field:
|
|
9426
|
+
* - page-level readOnly OR field-level readOnly
|
|
9427
|
+
*/
|
|
8857
9428
|
isReadOnly() {
|
|
8858
9429
|
return !!this.readOnly || !!this.field?.readOnly;
|
|
8859
9430
|
}
|
|
9431
|
+
/**
|
|
9432
|
+
* Resolves final disabled state for this field:
|
|
9433
|
+
* - page-level disable OR field-level disable
|
|
9434
|
+
*/
|
|
8860
9435
|
isDisabled() {
|
|
8861
9436
|
return !!this.disableForm || !!this.field?.disable;
|
|
8862
9437
|
}
|
|
9438
|
+
/** Shortcut to underlying FormControl */
|
|
8863
9439
|
ctrl() {
|
|
8864
9440
|
return this.form.get(this.key);
|
|
8865
9441
|
}
|
|
8866
|
-
/**
|
|
9442
|
+
/**
|
|
9443
|
+
* Minimal value formatter for legacy read-only rendering.
|
|
9444
|
+
* Kept intentionally simple to match V1 behavior.
|
|
9445
|
+
*/
|
|
8867
9446
|
displayValue() {
|
|
8868
9447
|
const v = this.ctrl()?.value;
|
|
8869
9448
|
if (v === null || v === undefined)
|
|
8870
9449
|
return '';
|
|
8871
9450
|
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
|
|
8872
9451
|
return v;
|
|
8873
|
-
//
|
|
9452
|
+
// Common DTO shapes (assign, option objects, uploads, etc.)
|
|
8874
9453
|
if (typeof v === 'object') {
|
|
8875
9454
|
return v.label ?? v.name ?? v.fileName ?? JSON.stringify(v);
|
|
8876
9455
|
}
|
|
8877
9456
|
return String(v);
|
|
8878
9457
|
}
|
|
9458
|
+
/**
|
|
9459
|
+
* Determines whether validation error should be displayed.
|
|
9460
|
+
* Errors are shown only after user interaction (touched or dirty).
|
|
9461
|
+
*/
|
|
8879
9462
|
showError() {
|
|
8880
9463
|
const c = this.ctrl();
|
|
8881
9464
|
return !!c && (c.touched || c.dirty) && !!c.errors;
|
|
8882
9465
|
}
|
|
8883
|
-
/**
|
|
9466
|
+
/**
|
|
9467
|
+
* Maps control error object to a normalized error key.
|
|
9468
|
+
* Supports:
|
|
9469
|
+
* - Angular built-in validators
|
|
9470
|
+
* - Phoenix custom validators
|
|
9471
|
+
* - Submit-only async validators
|
|
9472
|
+
*/
|
|
8884
9473
|
errorKey() {
|
|
8885
9474
|
const c = this.ctrl();
|
|
8886
9475
|
if (!c?.errors)
|
|
8887
9476
|
return null;
|
|
8888
|
-
//
|
|
9477
|
+
// Angular built-in validators
|
|
8889
9478
|
if (c.errors['required'])
|
|
8890
9479
|
return 'required';
|
|
8891
9480
|
if (c.errors['minlength'])
|
|
@@ -8900,27 +9489,31 @@ class MetaFormFieldV2Component {
|
|
|
8900
9489
|
return 'min';
|
|
8901
9490
|
if (c.errors['max'])
|
|
8902
9491
|
return 'max';
|
|
8903
|
-
// custom validators
|
|
9492
|
+
// Phoenix custom validators
|
|
8904
9493
|
if (c.errors['dangerousChars'])
|
|
8905
9494
|
return 'dangerousChars';
|
|
8906
9495
|
if (c.errors['timeperiod'])
|
|
8907
9496
|
return 'timeperiod';
|
|
8908
9497
|
if (c.errors['invalidDate'])
|
|
8909
|
-
return 'invalidDate';
|
|
9498
|
+
return 'invalidDate';
|
|
8910
9499
|
if (c.errors['dueDate'])
|
|
8911
9500
|
return 'dueDate';
|
|
8912
9501
|
if (c.errors['bothDates'])
|
|
8913
9502
|
return 'bothDates';
|
|
8914
|
-
//
|
|
9503
|
+
// Submit-only async validators
|
|
8915
9504
|
if (c.errors['unique'])
|
|
8916
9505
|
return 'unique';
|
|
8917
9506
|
if (c.errors['uniqueEntry'])
|
|
8918
9507
|
return 'uniqueEntry';
|
|
8919
9508
|
if (c.errors['custom'])
|
|
8920
9509
|
return 'custom';
|
|
9510
|
+
// Fallback: return first error key
|
|
8921
9511
|
return Object.keys(c.errors)[0] ?? null;
|
|
8922
9512
|
}
|
|
8923
|
-
/**
|
|
9513
|
+
/**
|
|
9514
|
+
* Resolves translated error message based on errorKey().
|
|
9515
|
+
* This is the single place responsible for validation message UX.
|
|
9516
|
+
*/
|
|
8924
9517
|
errorText() {
|
|
8925
9518
|
const c = this.ctrl();
|
|
8926
9519
|
const k = this.errorKey();
|
|
@@ -8942,11 +9535,12 @@ class MetaFormFieldV2Component {
|
|
|
8942
9535
|
case 'dangerousChars':
|
|
8943
9536
|
return this.translate.instant('VALIDATION_MESSAGE.NO_SPECIAL_CHARS_ALLOWED');
|
|
8944
9537
|
case 'custom':
|
|
8945
|
-
//
|
|
9538
|
+
// Legacy behavior: custom error can already be a translation key
|
|
8946
9539
|
return this.translate.instant(c.errors?.['custom']);
|
|
8947
9540
|
case 'uniqueEntry':
|
|
8948
|
-
//
|
|
8949
|
-
return c.errors?.['uniqueEntry'] ??
|
|
9541
|
+
// Legacy behavior: uniqueEntry may already be a translated string
|
|
9542
|
+
return (c.errors?.['uniqueEntry'] ??
|
|
9543
|
+
this.translate.instant('VALIDATION_MESSAGE.VALUE_IS_ALREADY_IN_USE'));
|
|
8950
9544
|
case 'unique':
|
|
8951
9545
|
return this.translate.instant('VALIDATION_MESSAGE.VALUE_IS_ALREADY_IN_USE');
|
|
8952
9546
|
case 'timeperiod':
|
|
@@ -8961,7 +9555,7 @@ class MetaFormFieldV2Component {
|
|
|
8961
9555
|
upperValue: c.errors?.['max']?.max,
|
|
8962
9556
|
});
|
|
8963
9557
|
case 'pattern': {
|
|
8964
|
-
//
|
|
9558
|
+
// Special-case URL pattern handling (legacy InlineFieldError behavior)
|
|
8965
9559
|
const re = '^(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})[/\\w .-]*/?$';
|
|
8966
9560
|
const requiredPattern = c.errors?.['pattern']?.requiredPattern;
|
|
8967
9561
|
if (requiredPattern === re) {
|
|
@@ -8973,42 +9567,47 @@ class MetaFormFieldV2Component {
|
|
|
8973
9567
|
return this.translate.instant('VALIDATION_MESSAGE.INVALID_VALUE');
|
|
8974
9568
|
}
|
|
8975
9569
|
}
|
|
9570
|
+
/**
|
|
9571
|
+
* Lightweight text formatter for simple read-only display use cases.
|
|
9572
|
+
* This is used mainly for inline displays and summary UIs.
|
|
9573
|
+
*/
|
|
8976
9574
|
valueText() {
|
|
8977
9575
|
const c = this.ctrl();
|
|
8978
9576
|
const v = c?.value;
|
|
8979
9577
|
if (v === null || v === undefined || v === '')
|
|
8980
9578
|
return '--';
|
|
8981
|
-
//
|
|
9579
|
+
// Single-select option: resolve label from options
|
|
8982
9580
|
if (this.type === 'SS_OPTION') {
|
|
8983
9581
|
const opts = this.field?.configuration?.options ?? [];
|
|
8984
9582
|
if (typeof v !== 'object') {
|
|
8985
9583
|
const hit = opts.find((o) => o?.value === v);
|
|
8986
9584
|
const label = hit?.label ?? v;
|
|
8987
|
-
// ako je label i18n key
|
|
8988
9585
|
return this.translate.instant(label);
|
|
8989
9586
|
}
|
|
8990
|
-
//
|
|
9587
|
+
// Object value fallback
|
|
8991
9588
|
const label = v.label ?? v.value;
|
|
8992
9589
|
return this.translate.instant(label);
|
|
8993
9590
|
}
|
|
8994
|
-
//
|
|
9591
|
+
// Date formatting
|
|
8995
9592
|
if (this.type === 'DATE' && v instanceof Date) {
|
|
8996
9593
|
return v.toLocaleDateString();
|
|
8997
9594
|
}
|
|
8998
|
-
//
|
|
9595
|
+
// Text editor / textarea: strip basic HTML tags for compact display
|
|
8999
9596
|
if (this.type === 'TEXT_EDITOR' || this.type === 'TEXT_AREA') {
|
|
9000
9597
|
return String(v).replace(/<[^>]*>/g, '').trim() || '--';
|
|
9001
9598
|
}
|
|
9002
9599
|
return String(v);
|
|
9003
9600
|
}
|
|
9004
9601
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormFieldV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9005
|
-
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 class=\"meta-field flex flex-column gap-2\" [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\">\n\n @if (userFriendlyMessage()) {\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\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.PASSWORD) {\n
|
|
9006
|
-
// PrimeNG 20
|
|
9602
|
+
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 class=\"meta-field flex flex-column gap-2\" [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\">\n\n @if (userFriendlyMessage()) {\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\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.PASSWORD) {\n <phoenix-meta-password-feild [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\" [parentForm]=\"form\">\n </phoenix-meta-password-feild>\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 [inputId]=\"key\" [formControlName]=\"key\" [showIcon]=\"true\">\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 <div class=\"flex align-items-center gap-2\">\n <p-checkbox [inputId]=\"key\" [binary]=\"true\" [formControlName]=\"key\">\n </p-checkbox>\n\n @if (placeholderKey()) {\n <label [attr.for]=\"key\" class=\"meta-inline-label\">\n {{ placeholderKey()! | translate }}\n </label>\n }\n </div>\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\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}.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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type:
|
|
9603
|
+
// PrimeNG 20 base inputs
|
|
9007
9604
|
InputTextModule }, { kind: "directive", type: i3$4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3$8.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i3$6.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.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: i10.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$5.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$7.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:
|
|
9008
|
-
//
|
|
9009
|
-
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"] }, { 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:
|
|
9605
|
+
// Advanced / custom Phoenix fields
|
|
9606
|
+
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:
|
|
9010
9607
|
// MetaAssignAssetComponent,
|
|
9011
|
-
MetaPasswordFeildComponent, selector: "phoenix-meta-password-feild" }, { 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:
|
|
9608
|
+
MetaPasswordFeildComponent, selector: "phoenix-meta-password-feild" }, { 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:
|
|
9609
|
+
// Read-only renderer used when page or field is in read-only mode
|
|
9610
|
+
ReadOnlyInputV2Component, selector: "phoenix-read-only-input-v2", inputs: ["field", "form"] }, { kind: "pipe", type: i3$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9012
9611
|
}
|
|
9013
9612
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormFieldV2Component, decorators: [{
|
|
9014
9613
|
type: Component,
|
|
@@ -9016,7 +9615,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
9016
9615
|
CommonModule,
|
|
9017
9616
|
ReactiveFormsModule,
|
|
9018
9617
|
TranslateModule,
|
|
9019
|
-
// PrimeNG 20
|
|
9618
|
+
// PrimeNG 20 base inputs
|
|
9020
9619
|
InputTextModule,
|
|
9021
9620
|
TextareaModule,
|
|
9022
9621
|
InputNumberModule,
|
|
@@ -9025,13 +9624,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
9025
9624
|
SelectModule,
|
|
9026
9625
|
DatePickerModule,
|
|
9027
9626
|
MessageModule,
|
|
9028
|
-
//
|
|
9627
|
+
// Advanced / custom Phoenix fields
|
|
9029
9628
|
MetaTimeperiodComponent,
|
|
9030
9629
|
MetaCurrencyComponent,
|
|
9031
9630
|
MetaStartDueDateV2Component,
|
|
9032
9631
|
MetaTextEditorComponent,
|
|
9033
9632
|
MetaCheckboxColorPickerV2Component,
|
|
9034
|
-
|
|
9633
|
+
MetaSwitchV2Component,
|
|
9035
9634
|
MetaSelectButtonComponent,
|
|
9036
9635
|
MetaAssignResponsibleV2Component,
|
|
9037
9636
|
// MetaAssignAssetComponent,
|
|
@@ -9039,8 +9638,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
9039
9638
|
MetaColorPickerV2Component,
|
|
9040
9639
|
MetaUploadComponent,
|
|
9041
9640
|
MetaUploadComponentDragDrop,
|
|
9641
|
+
// Read-only renderer used when page or field is in read-only mode
|
|
9042
9642
|
ReadOnlyInputV2Component,
|
|
9043
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [formGroup]=\"form\">\n @if (!field.hidden) {\n <div class=\"meta-field flex flex-column gap-2\" [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\">\n\n @if (userFriendlyMessage()) {\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\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.PASSWORD) {\n
|
|
9643
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [formGroup]=\"form\">\n @if (!field.hidden) {\n <div class=\"meta-field flex flex-column gap-2\" [style.order]=\"field.order ?? null\"\n [attr.data-cy]=\"'meta-field-' + key\">\n\n @if (userFriendlyMessage()) {\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\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.PASSWORD) {\n <phoenix-meta-password-feild [disable]=\"isDisabled()\" [formControlName]=\"key\" [control]=\"field\" [parentForm]=\"form\">\n </phoenix-meta-password-feild>\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 [inputId]=\"key\" [formControlName]=\"key\" [showIcon]=\"true\">\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 <div class=\"flex align-items-center gap-2\">\n <p-checkbox [inputId]=\"key\" [binary]=\"true\" [formControlName]=\"key\">\n </p-checkbox>\n\n @if (placeholderKey()) {\n <label [attr.for]=\"key\" class=\"meta-inline-label\">\n {{ placeholderKey()! | translate }}\n </label>\n }\n </div>\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\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}.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}\n"] }]
|
|
9044
9644
|
}], propDecorators: { field: [{
|
|
9045
9645
|
type: Input,
|
|
9046
9646
|
args: [{ required: true }]
|
|
@@ -9079,22 +9679,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
9079
9679
|
*/
|
|
9080
9680
|
const META_FORM_ASYNC_EXECUTOR = new InjectionToken('META_FORM_ASYNC_EXECUTOR');
|
|
9081
9681
|
|
|
9682
|
+
// Symbols used to attach submit-only validators and cleanup subscriptions
|
|
9683
|
+
// directly onto the FormGroup instance without polluting its public API.
|
|
9082
9684
|
const SUBMIT_VALIDATORS = Symbol('META_FORM_V2_SUBMIT_VALIDATORS');
|
|
9083
9685
|
const SUBMIT_CLEAR_SUBS = Symbol('META_FORM_V2_SUBMIT_CLEAR_SUBS');
|
|
9084
9686
|
class MetaSubmitValidatorService {
|
|
9085
9687
|
executor;
|
|
9688
|
+
/** Used to automatically clean up subscriptions when the service is destroyed */
|
|
9086
9689
|
dr = inject(DestroyRef);
|
|
9087
|
-
constructor(
|
|
9690
|
+
constructor(
|
|
9691
|
+
/**
|
|
9692
|
+
* Optional async executor abstraction used to perform server-side validation
|
|
9693
|
+
* (e.g. uniqueness checks).
|
|
9694
|
+
*
|
|
9695
|
+
* If not provided, submit-only validators will be skipped gracefully.
|
|
9696
|
+
*/
|
|
9697
|
+
executor) {
|
|
9088
9698
|
this.executor = executor;
|
|
9089
9699
|
}
|
|
9700
|
+
/**
|
|
9701
|
+
* Registers submit-only validators on the given form.
|
|
9702
|
+
*
|
|
9703
|
+
* - Attaches validator metadata to the form instance (via Symbols)
|
|
9704
|
+
* - Cleans up any previous subscriptions
|
|
9705
|
+
* - Subscribes to valueChanges in order to auto-clear submit-only errors
|
|
9706
|
+
* when the user modifies the field after a failed submit
|
|
9707
|
+
*/
|
|
9090
9708
|
register(form, validators) {
|
|
9091
9709
|
const list = validators ?? [];
|
|
9092
9710
|
form[SUBMIT_VALIDATORS] = list;
|
|
9093
|
-
//
|
|
9711
|
+
// Cleanup old auto-clear subscriptions
|
|
9094
9712
|
const old = form[SUBMIT_CLEAR_SUBS] ?? [];
|
|
9095
9713
|
old.forEach((s) => s.unsubscribe());
|
|
9096
9714
|
form[SUBMIT_CLEAR_SUBS] = [];
|
|
9097
|
-
//
|
|
9715
|
+
// Subscribe to valueChanges in order to remove submit-only errors
|
|
9716
|
+
// as soon as the user changes the input.
|
|
9098
9717
|
const subs = [];
|
|
9099
9718
|
for (const v of list) {
|
|
9100
9719
|
const ctrl = form.get(v.controlKey);
|
|
@@ -9104,29 +9723,45 @@ class MetaSubmitValidatorService {
|
|
|
9104
9723
|
subs.push(ctrl.valueChanges
|
|
9105
9724
|
.pipe(takeUntilDestroyed(this.dr))
|
|
9106
9725
|
.subscribe(() => {
|
|
9107
|
-
// ✅
|
|
9726
|
+
// ✅ Only remove submit-only error.
|
|
9727
|
+
// ❌ Do NOT trigger updateValueAndValidity here to avoid noisy re-validation.
|
|
9108
9728
|
this.removeError(ctrl, errorKey);
|
|
9109
9729
|
}));
|
|
9110
9730
|
}
|
|
9111
9731
|
form[SUBMIT_CLEAR_SUBS] = subs;
|
|
9112
9732
|
}
|
|
9733
|
+
/**
|
|
9734
|
+
* Executes submit-only validators.
|
|
9735
|
+
*
|
|
9736
|
+
* Flow:
|
|
9737
|
+
* 1) Mark all controls as touched/dirty so sync validation messages become visible
|
|
9738
|
+
* 2) If form is sync-invalid, skip async validation
|
|
9739
|
+
* 3) Clear previous submit-only errors
|
|
9740
|
+
* 4) Execute async validators sequentially
|
|
9741
|
+
* 5) Apply mapped errors back to controls
|
|
9742
|
+
*
|
|
9743
|
+
* Returns:
|
|
9744
|
+
* - true → form is valid (sync + submit-only async)
|
|
9745
|
+
* - false → at least one submit-only validator failed
|
|
9746
|
+
*/
|
|
9113
9747
|
async run(form) {
|
|
9114
9748
|
const validators = form[SUBMIT_VALIDATORS] ?? [];
|
|
9115
|
-
//
|
|
9749
|
+
// Make sure sync validation errors are visible on submit
|
|
9116
9750
|
this.markAllTouchedOnSubmit(form);
|
|
9117
|
-
//
|
|
9751
|
+
// Do not emit valueChanges/statusChanges events here
|
|
9118
9752
|
form.updateValueAndValidity({ emitEvent: false });
|
|
9119
9753
|
if (!validators.length)
|
|
9120
9754
|
return form.valid;
|
|
9121
|
-
//
|
|
9755
|
+
// If no async executor is provided, do not block submit
|
|
9122
9756
|
if (!this.executor)
|
|
9123
9757
|
return form.valid;
|
|
9124
|
-
//
|
|
9758
|
+
// Clear previous submit-only errors before re-running validation
|
|
9125
9759
|
this.clearSubmitErrors(form, validators);
|
|
9126
|
-
//
|
|
9760
|
+
// If sync validation fails, skip async calls
|
|
9127
9761
|
form.updateValueAndValidity({ emitEvent: false });
|
|
9128
9762
|
if (form.invalid)
|
|
9129
9763
|
return false;
|
|
9764
|
+
// Execute submit-only validators sequentially
|
|
9130
9765
|
for (const v of validators) {
|
|
9131
9766
|
const req = v.buildRequest(form);
|
|
9132
9767
|
if (!req)
|
|
@@ -9141,7 +9776,9 @@ class MetaSubmitValidatorService {
|
|
|
9141
9776
|
const errorKey = v.errorKey ?? 'unique';
|
|
9142
9777
|
const msg = mapped?.message ??
|
|
9143
9778
|
'VALIDATION_MESSAGE.VALUE_IS_ALREADY_IN_USE';
|
|
9779
|
+
// Attach submit-only error to control
|
|
9144
9780
|
ctrl.setErrors({ ...(ctrl.errors ?? {}), [errorKey]: msg });
|
|
9781
|
+
// Force visibility of the error in UI
|
|
9145
9782
|
ctrl.markAsTouched();
|
|
9146
9783
|
ctrl.markAsDirty();
|
|
9147
9784
|
return false;
|
|
@@ -9149,6 +9786,10 @@ class MetaSubmitValidatorService {
|
|
|
9149
9786
|
}
|
|
9150
9787
|
return form.valid;
|
|
9151
9788
|
}
|
|
9789
|
+
/**
|
|
9790
|
+
* Removes a specific submit-only error from the control without
|
|
9791
|
+
* touching other validation errors.
|
|
9792
|
+
*/
|
|
9152
9793
|
removeError(ctrl, errorKey) {
|
|
9153
9794
|
if (!ctrl.errors?.[errorKey])
|
|
9154
9795
|
return;
|
|
@@ -9156,6 +9797,10 @@ class MetaSubmitValidatorService {
|
|
|
9156
9797
|
delete next[errorKey];
|
|
9157
9798
|
ctrl.setErrors(Object.keys(next).length ? next : null);
|
|
9158
9799
|
}
|
|
9800
|
+
/**
|
|
9801
|
+
* Clears submit-only errors for all controls involved in submit validators.
|
|
9802
|
+
* This does NOT trigger updateValueAndValidity to avoid unnecessary re-validation.
|
|
9803
|
+
*/
|
|
9159
9804
|
clearSubmitErrors(form, validators) {
|
|
9160
9805
|
const keys = new Set();
|
|
9161
9806
|
validators.forEach((v) => keys.add(v.controlKey));
|
|
@@ -9166,10 +9811,14 @@ class MetaSubmitValidatorService {
|
|
|
9166
9811
|
return;
|
|
9167
9812
|
const nextErrors = { ...(ctrl.errors ?? {}) };
|
|
9168
9813
|
errorKeys.forEach((ek) => delete nextErrors[ek]);
|
|
9169
|
-
// ✅
|
|
9814
|
+
// ✅ Only update errors object, no re-validation side effects
|
|
9170
9815
|
ctrl.setErrors(Object.keys(nextErrors).length ? nextErrors : null);
|
|
9171
9816
|
});
|
|
9172
9817
|
}
|
|
9818
|
+
/**
|
|
9819
|
+
* Marks all form controls as touched and dirty.
|
|
9820
|
+
* This is used on submit to make validation errors visible to the user.
|
|
9821
|
+
*/
|
|
9173
9822
|
markAllTouchedOnSubmit(form) {
|
|
9174
9823
|
Object.values(form.controls).forEach((c) => {
|
|
9175
9824
|
c.markAsTouched();
|
|
@@ -9189,71 +9838,129 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
9189
9838
|
args: [META_FORM_ASYNC_EXECUTOR]
|
|
9190
9839
|
}] }] });
|
|
9191
9840
|
|
|
9841
|
+
/**
|
|
9842
|
+
* Validator for START_DUE_DATE composite field.
|
|
9843
|
+
*
|
|
9844
|
+
* Responsibilities:
|
|
9845
|
+
* - Optionally enforce that BOTH startDate and endDate are provided (requireBoth)
|
|
9846
|
+
* - Validate that provided dates are valid Date values
|
|
9847
|
+
* - Ensure startDate is not after endDate
|
|
9848
|
+
*
|
|
9849
|
+
* This is a synchronous validator and should be attached to the single FormControl
|
|
9850
|
+
* that represents the composite START_DUE_DATE value.
|
|
9851
|
+
*/
|
|
9192
9852
|
function startDueDateV2Validator(opts) {
|
|
9193
9853
|
const requireBoth = !!opts?.requireBoth;
|
|
9194
9854
|
return (control) => {
|
|
9195
9855
|
const v = control.value;
|
|
9196
9856
|
const sdRaw = v?.startDate ?? null;
|
|
9197
9857
|
const edRaw = v?.endDate ?? null;
|
|
9198
|
-
//
|
|
9858
|
+
// Case 1: both dates are empty
|
|
9859
|
+
// - If both are required -> invalid
|
|
9860
|
+
// - Otherwise -> valid (optional date range)
|
|
9199
9861
|
if (!sdRaw && !edRaw) {
|
|
9200
9862
|
return requireBoth ? { bothDates: true } : null;
|
|
9201
9863
|
}
|
|
9202
|
-
//
|
|
9864
|
+
// Case 2: only one date is provided but both are required
|
|
9203
9865
|
if (requireBoth && (!sdRaw || !edRaw)) {
|
|
9204
9866
|
return { bothDates: true };
|
|
9205
9867
|
}
|
|
9206
|
-
//
|
|
9868
|
+
// Case 3: both are optional and one is missing -> valid
|
|
9207
9869
|
if (!sdRaw || !edRaw)
|
|
9208
9870
|
return null;
|
|
9871
|
+
// Parse raw values into Date instances
|
|
9209
9872
|
const sd = new Date(sdRaw);
|
|
9210
9873
|
const ed = new Date(edRaw);
|
|
9874
|
+
// Invalid date values (e.g. unparsable strings)
|
|
9211
9875
|
if (isNaN(sd.getTime()) || isNaN(ed.getTime()))
|
|
9212
9876
|
return { dueDate: true };
|
|
9877
|
+
// Normalize both dates to midnight for date-only comparison (ignore time)
|
|
9213
9878
|
const s = new Date(sd);
|
|
9214
9879
|
s.setHours(0, 0, 0, 0);
|
|
9215
9880
|
const e = new Date(ed);
|
|
9216
9881
|
e.setHours(0, 0, 0, 0);
|
|
9882
|
+
// Start date must not be after end date
|
|
9217
9883
|
return s.getTime() > e.getTime() ? { dueDate: true } : null;
|
|
9218
9884
|
};
|
|
9219
9885
|
}
|
|
9220
9886
|
|
|
9887
|
+
/**
|
|
9888
|
+
* Builds synchronous Angular validators for a single meta-field.
|
|
9889
|
+
*
|
|
9890
|
+
* This function only returns *sync* validators:
|
|
9891
|
+
* - Required, min/max, length
|
|
9892
|
+
* - Email
|
|
9893
|
+
* - Regex / phone
|
|
9894
|
+
* - Timeperiod
|
|
9895
|
+
* - Start/Due date cross-field-ish validator (but still sync)
|
|
9896
|
+
* - Whitespace rules
|
|
9897
|
+
* - Dangerous characters
|
|
9898
|
+
*
|
|
9899
|
+
* Submit-only async validators (e.g. uniqueness) are handled elsewhere (MetaSubmitValidatorService).
|
|
9900
|
+
*/
|
|
9221
9901
|
function buildSyncValidators(field, ctx) {
|
|
9222
9902
|
const out = [];
|
|
9903
|
+
// `field.validators` is a metadata-driven bag of validation rules
|
|
9223
9904
|
const v = field.validators ?? {};
|
|
9905
|
+
// Field type drives some special-case validators
|
|
9224
9906
|
const type = field.configuration.type;
|
|
9907
|
+
// ---- numeric constraints ----
|
|
9225
9908
|
if (v.min != null)
|
|
9226
9909
|
out.push(Validators.min(v.min));
|
|
9227
9910
|
if (v.max != null)
|
|
9228
9911
|
out.push(Validators.max(v.max));
|
|
9912
|
+
// ---- length constraints ----
|
|
9229
9913
|
if (v.minLength != null)
|
|
9230
9914
|
out.push(Validators.minLength(v.minLength));
|
|
9231
9915
|
if (v.maxLength != null)
|
|
9232
9916
|
out.push(Validators.maxLength(v.maxLength));
|
|
9917
|
+
// ---- email ----
|
|
9233
9918
|
if (v.email)
|
|
9234
9919
|
out.push(Validators.email);
|
|
9235
|
-
|
|
9920
|
+
// ---- regex / pattern ----
|
|
9921
|
+
// Supports either:
|
|
9922
|
+
// - plain pattern string
|
|
9923
|
+
// - "/.../flags" format which we try to compile into RegExp
|
|
9924
|
+
if (v.regex?.regexType) {
|
|
9236
9925
|
out.push(Validators.pattern(resolvePattern(v.regex.regexType) ?? v.regex.regexType));
|
|
9926
|
+
}
|
|
9927
|
+
// ---- phone ----
|
|
9928
|
+
// if (v.phone) out.push(Validators.pattern(/^\+?[1-9]\d{7,14}$/));
|
|
9237
9929
|
if (v.phone)
|
|
9238
|
-
out.push(
|
|
9930
|
+
out.push(phoneHumanValidator({ minDigits: 8, maxDigits: 15 }));
|
|
9931
|
+
// ---- time period ----
|
|
9932
|
+
// Localized validator; tpMin can enforce minimal value/length depending on your implementation.
|
|
9239
9933
|
if (v.timeperiod)
|
|
9240
9934
|
out.push(timePeriod(ctx?.lang ?? 'en', v.tpMin));
|
|
9935
|
+
// ---- start/due date ----
|
|
9936
|
+
// Apply the validator if field is of START_DUE_DATE type or if metadata explicitly enables dueDate.
|
|
9937
|
+
// requireBoth is driven by field.mandatory
|
|
9241
9938
|
if (type === 'START_DUE_DATE' || v.dueDate) {
|
|
9242
9939
|
out.push(startDueDateV2Validator({ requireBoth: !!field.mandatory }));
|
|
9243
9940
|
}
|
|
9941
|
+
// ---- required ----
|
|
9244
9942
|
if (field.mandatory)
|
|
9245
9943
|
out.push(Validators.required);
|
|
9944
|
+
// ---- whitespace guard for required text fields ----
|
|
9945
|
+
// Prevent " " from passing required validation.
|
|
9246
9946
|
if (field.mandatory && (type === 'TEXT' || type === 'TEXT_AREA')) {
|
|
9247
9947
|
out.push(WhiteSpaceValidator.noWhiteSpaceValidator);
|
|
9248
9948
|
}
|
|
9949
|
+
// ---- dangerous characters ----
|
|
9950
|
+
// Applied to text and textarea only.
|
|
9249
9951
|
if (type === 'TEXT' || type === 'TEXT_AREA') {
|
|
9250
9952
|
out.push(noDangerousCharsValidator());
|
|
9251
9953
|
}
|
|
9252
9954
|
return out;
|
|
9253
9955
|
}
|
|
9956
|
+
/**
|
|
9957
|
+
* Tries to interpret a regex string in "/pattern/flags" format.
|
|
9958
|
+
* If parsing fails, returns null and the caller falls back to using the raw string.
|
|
9959
|
+
*/
|
|
9254
9960
|
function resolvePattern(regexType) {
|
|
9255
9961
|
if (!regexType)
|
|
9256
9962
|
return null;
|
|
9963
|
+
// Support format like: "/^[0-9]+$/g"
|
|
9257
9964
|
const m = regexType.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
9258
9965
|
if (m) {
|
|
9259
9966
|
try {
|
|
@@ -9263,26 +9970,94 @@ function resolvePattern(regexType) {
|
|
|
9263
9970
|
return null;
|
|
9264
9971
|
}
|
|
9265
9972
|
}
|
|
9973
|
+
// Otherwise treat it as a plain string pattern
|
|
9266
9974
|
return regexType;
|
|
9267
9975
|
}
|
|
9976
|
+
/**
|
|
9977
|
+
* Phone validator that accepts common human formats:
|
|
9978
|
+
* - spaces, hyphens, parentheses
|
|
9979
|
+
* - local numbers starting with 0 (e.g. 060...)
|
|
9980
|
+
* - international numbers with +
|
|
9981
|
+
*
|
|
9982
|
+
* Strategy:
|
|
9983
|
+
* 1) Strip formatting chars
|
|
9984
|
+
* 2) Validate the normalized digits
|
|
9985
|
+
*/
|
|
9986
|
+
function phoneHumanValidator(opts) {
|
|
9987
|
+
const min = opts?.minDigits ?? 8;
|
|
9988
|
+
const max = opts?.maxDigits ?? 15;
|
|
9989
|
+
return (control) => {
|
|
9990
|
+
const raw = (control.value ?? '').toString().trim();
|
|
9991
|
+
if (!raw)
|
|
9992
|
+
return null; // let required handle empties
|
|
9993
|
+
// Keep leading "+" if present, remove common separators
|
|
9994
|
+
const normalized = raw
|
|
9995
|
+
.replace(/[\s\-().]/g, '') // remove spaces, dashes, parentheses, dots
|
|
9996
|
+
.replace(/(?!^\+)\+/g, ''); // remove any "+" not at the start
|
|
9997
|
+
// Allow optional leading "+"
|
|
9998
|
+
const hasPlus = normalized.startsWith('+');
|
|
9999
|
+
const digits = hasPlus ? normalized.slice(1) : normalized;
|
|
10000
|
+
// Digits only after normalization
|
|
10001
|
+
if (!/^\d+$/.test(digits))
|
|
10002
|
+
return { phone: true };
|
|
10003
|
+
// Length constraints (digits only)
|
|
10004
|
+
if (digits.length < min || digits.length > max)
|
|
10005
|
+
return { phone: true };
|
|
10006
|
+
// If it has "+", require country code style (first digit not 0)
|
|
10007
|
+
if (hasPlus && digits.startsWith('0'))
|
|
10008
|
+
return { phone: true };
|
|
10009
|
+
// If local, allow leading 0 (e.g. 060...)
|
|
10010
|
+
return null;
|
|
10011
|
+
};
|
|
10012
|
+
}
|
|
9268
10013
|
|
|
10014
|
+
/**
|
|
10015
|
+
* Ensures that the FormGroup structure matches the provided metadata (flat field list).
|
|
10016
|
+
*
|
|
10017
|
+
* Responsibilities:
|
|
10018
|
+
* - Create missing FormControls based on MetaFieldConfig
|
|
10019
|
+
* - Apply (and re-apply) synchronous validators derived from metadata
|
|
10020
|
+
* - Sync disabled/enabled state with metadata
|
|
10021
|
+
* - Remove obsolete controls that are no longer present in metadata
|
|
10022
|
+
*
|
|
10023
|
+
* This function is intentionally idempotent:
|
|
10024
|
+
* Calling it multiple times with the same metadata should not break form state.
|
|
10025
|
+
*/
|
|
9269
10026
|
function ensureControlsV2(fb, form, flat, initialValues, ctx) {
|
|
10027
|
+
// Tracks which control keys are allowed by current metadata
|
|
9270
10028
|
const allowed = new Set();
|
|
9271
10029
|
for (const field of flat) {
|
|
9272
10030
|
const key = field?.configuration?.key;
|
|
9273
10031
|
if (!key)
|
|
9274
10032
|
continue;
|
|
9275
10033
|
allowed.add(key);
|
|
10034
|
+
/**
|
|
10035
|
+
* Create control if it does not exist yet.
|
|
10036
|
+
* Initial value is taken from initialValues map if provided,
|
|
10037
|
+
* otherwise defaults to null.
|
|
10038
|
+
*/
|
|
9276
10039
|
if (!form.contains(key)) {
|
|
9277
10040
|
form.addControl(key, new FormControl(initialValues?.[key] ?? null));
|
|
9278
10041
|
}
|
|
9279
10042
|
const ctrl = form.get(key);
|
|
9280
10043
|
if (!ctrl)
|
|
9281
10044
|
continue;
|
|
9282
|
-
|
|
10045
|
+
/**
|
|
10046
|
+
* (1) Sync validators:
|
|
10047
|
+
* Rebuild and apply synchronous validators based on current field metadata.
|
|
10048
|
+
* This ensures that changes in metadata (e.g. required, min/max, patterns)
|
|
10049
|
+
* are reflected on the form control.
|
|
10050
|
+
*/
|
|
9283
10051
|
ctrl.setValidators(buildSyncValidators(field, ctx));
|
|
10052
|
+
// Recalculate validity silently (do not emit events to avoid UI side-effects)
|
|
9284
10053
|
ctrl.updateValueAndValidity({ emitEvent: false });
|
|
9285
|
-
|
|
10054
|
+
/**
|
|
10055
|
+
* (2) Sync disabled/enabled state:
|
|
10056
|
+
* The control's enabled state must reflect the field metadata.
|
|
10057
|
+
* This is symmetric:
|
|
10058
|
+
* - If metadata says "disable" → disable control
|
|
10059
|
+
* - If metadata says "enable" → enable control
|
|
10060
|
+
*/
|
|
9286
10061
|
const shouldBeDisabled = !!field.disable;
|
|
9287
10062
|
if (shouldBeDisabled && ctrl.enabled) {
|
|
9288
10063
|
ctrl.disable({ emitEvent: false });
|
|
@@ -9291,6 +10066,13 @@ function ensureControlsV2(fb, form, flat, initialValues, ctx) {
|
|
|
9291
10066
|
ctrl.enable({ emitEvent: false });
|
|
9292
10067
|
}
|
|
9293
10068
|
}
|
|
10069
|
+
/**
|
|
10070
|
+
* Remove any controls that are present in the FormGroup
|
|
10071
|
+
* but no longer exist in the metadata.
|
|
10072
|
+
*
|
|
10073
|
+
* This keeps the form structure in sync when metadata changes dynamically
|
|
10074
|
+
* (e.g. conditional fields, stage-based forms, etc.).
|
|
10075
|
+
*/
|
|
9294
10076
|
Object.keys(form.controls).forEach((k) => {
|
|
9295
10077
|
if (!allowed.has(k))
|
|
9296
10078
|
form.removeControl(k);
|
|
@@ -9311,48 +10093,101 @@ function flattenControls(input) {
|
|
|
9311
10093
|
}
|
|
9312
10094
|
|
|
9313
10095
|
class MetaFormV2Component {
|
|
10096
|
+
/** Form instance created/owned by the parent (dialog/page) */
|
|
9314
10097
|
form;
|
|
10098
|
+
/**
|
|
10099
|
+
* V2 metadata/config:
|
|
10100
|
+
* - controls (grouped or flat)
|
|
10101
|
+
* - initialValues
|
|
10102
|
+
* - submitValidators (run on submit only)
|
|
10103
|
+
* - setupDependencies (optional runtime bindings)
|
|
10104
|
+
*/
|
|
9315
10105
|
config;
|
|
9316
|
-
/**
|
|
10106
|
+
/**
|
|
10107
|
+
* Page-level readOnly state controlled by parent.
|
|
10108
|
+
* Used both for rendering and for "enter edit mode" behavior.
|
|
10109
|
+
*/
|
|
9317
10110
|
readOnly = false;
|
|
9318
|
-
/** Optional layout
|
|
10111
|
+
/** Optional layout customization for inner content wrapper */
|
|
9319
10112
|
contentStyle = null;
|
|
10113
|
+
/** Optional class name(s) applied to inner content wrapper */
|
|
9320
10114
|
contentClass = null;
|
|
10115
|
+
/** Builds/ensures controls exist (ensures validators & default values are wired) */
|
|
9321
10116
|
fb = inject(FormBuilder);
|
|
10117
|
+
/** Registers and executes submit-only validators (async validation on submit) */
|
|
9322
10118
|
submitValidator = inject(MetaSubmitValidatorService);
|
|
10119
|
+
/** Used for validator localization (lang-dependent validators / messages) */
|
|
9323
10120
|
translate = inject(TranslateService);
|
|
10121
|
+
/**
|
|
10122
|
+
* A lightweight signature of the current metadata structure.
|
|
10123
|
+
* Used to detect when the form schema changes (and avoid unnecessary resets).
|
|
10124
|
+
*/
|
|
9324
10125
|
lastSignature = '';
|
|
10126
|
+
/**
|
|
10127
|
+
* Cleanup function returned by setupDependencies (if any).
|
|
10128
|
+
* Called only when metadata structure changes or on destroy.
|
|
10129
|
+
*/
|
|
9325
10130
|
depCleanup;
|
|
9326
|
-
/**
|
|
10131
|
+
/**
|
|
10132
|
+
* PrimeNG Accordion "value" for opened panels.
|
|
10133
|
+
* For multiple panels, PrimeNG expects an array of ids.
|
|
10134
|
+
*/
|
|
9327
10135
|
expandedGroupIds = [];
|
|
9328
10136
|
ngOnChanges(changes) {
|
|
9329
10137
|
if (!this.form || !this.config)
|
|
9330
10138
|
return;
|
|
10139
|
+
/**
|
|
10140
|
+
* Flatten metadata controls to a single list for:
|
|
10141
|
+
* - building/enforcing FormControls
|
|
10142
|
+
* - dependency binding
|
|
10143
|
+
* - signature calculation
|
|
10144
|
+
*/
|
|
9331
10145
|
const flat = flattenControls(this.config.controls);
|
|
10146
|
+
/**
|
|
10147
|
+
* Signature is used to detect real schema changes.
|
|
10148
|
+
* We intentionally ignore labels/props and track key+type only.
|
|
10149
|
+
*/
|
|
9332
10150
|
const signature = flat.map((f) => `${f.configuration.key}:${f.configuration.type}`).join('|');
|
|
10151
|
+
/** "config changed" includes initialValues, validators, dependencies, etc. */
|
|
9333
10152
|
const configChanged = !!changes['config'];
|
|
10153
|
+
/** "form changed" means parent passed a different FormGroup instance */
|
|
9334
10154
|
const formChanged = !!changes['form'];
|
|
10155
|
+
/** schema changed if the signature differs from the last one */
|
|
9335
10156
|
const metaChanged = signature !== this.lastSignature;
|
|
10157
|
+
/**
|
|
10158
|
+
* Special case: entering edit mode (readOnly true -> false).
|
|
10159
|
+
* Used to selectively show validation for already-filled values.
|
|
10160
|
+
*/
|
|
9336
10161
|
const enteringEdit = !!changes['readOnly'] &&
|
|
9337
10162
|
changes['readOnly'].previousValue === true &&
|
|
9338
10163
|
changes['readOnly'].currentValue === false;
|
|
9339
|
-
// If nothing relevant changed, exit
|
|
10164
|
+
// If nothing relevant changed, exit early to avoid unnecessary work.
|
|
9340
10165
|
if (!configChanged && !formChanged && !metaChanged && !enteringEdit)
|
|
9341
10166
|
return;
|
|
9342
|
-
|
|
10167
|
+
/**
|
|
10168
|
+
* Dependencies are tied to the metadata structure.
|
|
10169
|
+
* If schema changed, cleanup old subscriptions/bindings first.
|
|
10170
|
+
*/
|
|
9343
10171
|
if (configChanged || metaChanged) {
|
|
9344
10172
|
this.depCleanup?.();
|
|
9345
10173
|
this.depCleanup = undefined;
|
|
9346
10174
|
}
|
|
9347
|
-
|
|
10175
|
+
/**
|
|
10176
|
+
* Ensure controls exist on the passed FormGroup and sync validators.
|
|
10177
|
+
* This is where missing controls are added and validator wiring is applied.
|
|
10178
|
+
*/
|
|
9348
10179
|
const initial = this.config.initialValues ?? {};
|
|
9349
10180
|
ensureControlsV2(this.fb, this.form, flat, initial, { lang: this.translate.currentLang });
|
|
9350
|
-
|
|
10181
|
+
/**
|
|
10182
|
+
* Patch initial values without emitting changes:
|
|
10183
|
+
* - prevents loops
|
|
10184
|
+
* - keeps create/edit initialization silent
|
|
10185
|
+
*/
|
|
9351
10186
|
this.form.patchValue(initial, { emitEvent: false });
|
|
9352
10187
|
this.form.updateValueAndValidity({ emitEvent: false });
|
|
9353
10188
|
/**
|
|
9354
|
-
* Initialize accordion
|
|
9355
|
-
*
|
|
10189
|
+
* Initialize accordion open panels ONLY when schema changes.
|
|
10190
|
+
* IMPORTANT: do NOT do this on readOnly toggle, otherwise user-collapsed state resets.
|
|
9356
10191
|
*/
|
|
9357
10192
|
if (metaChanged) {
|
|
9358
10193
|
if (this.isGrouped) {
|
|
@@ -9366,9 +10201,16 @@ class MetaFormV2Component {
|
|
|
9366
10201
|
this.expandedGroupIds = [];
|
|
9367
10202
|
}
|
|
9368
10203
|
}
|
|
9369
|
-
|
|
10204
|
+
/**
|
|
10205
|
+
* Register submit-only validators.
|
|
10206
|
+
* Safe to call even if empty; service will attach necessary structures internally.
|
|
10207
|
+
*/
|
|
9370
10208
|
this.submitValidator.register(this.form, this.config.submitValidators);
|
|
9371
|
-
|
|
10209
|
+
/**
|
|
10210
|
+
* Bind dependencies ONLY when schema changes.
|
|
10211
|
+
* setupDependencies can subscribe to valueChanges, set options, reset fields, etc.
|
|
10212
|
+
* If it returns a function, we store it for cleanup.
|
|
10213
|
+
*/
|
|
9372
10214
|
if ((configChanged || metaChanged) && this.config.setupDependencies) {
|
|
9373
10215
|
const maybeCleanup = this.config.setupDependencies({
|
|
9374
10216
|
form: this.form,
|
|
@@ -9381,10 +10223,10 @@ class MetaFormV2Component {
|
|
|
9381
10223
|
this.depCleanup = maybeCleanup;
|
|
9382
10224
|
}
|
|
9383
10225
|
/**
|
|
9384
|
-
* Entering edit:
|
|
9385
|
-
*
|
|
9386
|
-
*
|
|
9387
|
-
*
|
|
10226
|
+
* Entering edit mode:
|
|
10227
|
+
* - mark controls as touched ONLY if they already have a meaningful value
|
|
10228
|
+
* - expand groups that contain "visible invalid" controls (invalid + touched/dirty)
|
|
10229
|
+
* This prevents CREATE dialogs from showing "required" errors immediately.
|
|
9388
10230
|
*/
|
|
9389
10231
|
if (enteringEdit) {
|
|
9390
10232
|
queueMicrotask(() => {
|
|
@@ -9392,12 +10234,19 @@ class MetaFormV2Component {
|
|
|
9392
10234
|
this.expandVisibleInvalidGroupsUnion();
|
|
9393
10235
|
});
|
|
9394
10236
|
}
|
|
10237
|
+
// Store signature for the next change detection pass
|
|
9395
10238
|
this.lastSignature = signature;
|
|
9396
10239
|
}
|
|
9397
|
-
|
|
10240
|
+
/**
|
|
10241
|
+
* PrimeNG Accordion emits value as:
|
|
10242
|
+
* - single id (string/number)
|
|
10243
|
+
* - array of ids
|
|
10244
|
+
* We normalize everything into string[] for stable internal state.
|
|
10245
|
+
*/
|
|
9398
10246
|
onAccordionValueChange(v) {
|
|
9399
10247
|
this.expandedGroupIds = this.normalizeAccordionValue(v);
|
|
9400
10248
|
}
|
|
10249
|
+
/** Normalizes Accordion value into a stable string[] representation */
|
|
9401
10250
|
normalizeAccordionValue(v) {
|
|
9402
10251
|
if (Array.isArray(v))
|
|
9403
10252
|
return v.map((x) => `${x}`);
|
|
@@ -9406,31 +10255,44 @@ class MetaFormV2Component {
|
|
|
9406
10255
|
return [`${v}`];
|
|
9407
10256
|
}
|
|
9408
10257
|
// ---------------- template helpers ----------------
|
|
10258
|
+
/** True when metadata contains at least one control definition */
|
|
9409
10259
|
get hasControls() {
|
|
9410
10260
|
return Array.isArray(this.config?.controls) && this.config.controls.length > 0;
|
|
9411
10261
|
}
|
|
10262
|
+
/**
|
|
10263
|
+
* Heuristic: grouped config has "ctrl" on first element.
|
|
10264
|
+
* (Keeps template simple and avoids extra schema fields.)
|
|
10265
|
+
*/
|
|
9412
10266
|
get isGrouped() {
|
|
9413
10267
|
const c = this.config?.controls ?? [];
|
|
9414
10268
|
return !!c[0]?.ctrl;
|
|
9415
10269
|
}
|
|
10270
|
+
/** Returns grouped schema structure (accordion groups) */
|
|
9416
10271
|
get groupedControls() {
|
|
9417
10272
|
return this.config?.controls ?? [];
|
|
9418
10273
|
}
|
|
10274
|
+
/** Returns flat schema structure (grid mode) */
|
|
9419
10275
|
get flatControls() {
|
|
9420
10276
|
return this.config?.controls ?? [];
|
|
9421
10277
|
}
|
|
10278
|
+
/** TrackBy for group rendering */
|
|
9422
10279
|
groupTrack(g, idx) {
|
|
9423
10280
|
return g?.id ?? idx;
|
|
9424
10281
|
}
|
|
9425
|
-
/**
|
|
10282
|
+
/**
|
|
10283
|
+
* PrimeNG accordion panel `value` must match accordion `value` type.
|
|
10284
|
+
* We always convert group id to string for consistent behavior.
|
|
10285
|
+
*/
|
|
9426
10286
|
panelValue(g) {
|
|
9427
10287
|
const id = g?.id;
|
|
9428
10288
|
return id === null || id === undefined ? '' : `${id}`;
|
|
9429
10289
|
}
|
|
9430
10290
|
// ---------------- core behavior ----------------
|
|
9431
10291
|
/**
|
|
9432
|
-
*
|
|
9433
|
-
* This
|
|
10292
|
+
* Marks & validates ONLY controls that already have meaningful values.
|
|
10293
|
+
* This is used when switching from readOnly -> edit mode to avoid:
|
|
10294
|
+
* - triggering "required" errors for empty fields
|
|
10295
|
+
* - expanding groups based on empty mandatory fields on CREATE dialogs
|
|
9434
10296
|
*/
|
|
9435
10297
|
touchAndValidateOnlyFilledControls() {
|
|
9436
10298
|
const controls = this.form?.controls ?? {};
|
|
@@ -9438,21 +10300,21 @@ class MetaFormV2Component {
|
|
|
9438
10300
|
if (!ctrl)
|
|
9439
10301
|
continue;
|
|
9440
10302
|
const value = ctrl.value;
|
|
9441
|
-
// Touch only if
|
|
10303
|
+
// Touch only if this field is already populated (edit case) or prefilled.
|
|
9442
10304
|
if (this.hasMeaningfulValue(value)) {
|
|
9443
10305
|
ctrl.markAsTouched();
|
|
9444
10306
|
ctrl.updateValueAndValidity({ emitEvent: true });
|
|
9445
10307
|
}
|
|
9446
10308
|
}
|
|
9447
|
-
//
|
|
10309
|
+
// Optional: keep form status consistent after selective updates
|
|
9448
10310
|
this.form.updateValueAndValidity({ emitEvent: true });
|
|
9449
10311
|
}
|
|
9450
10312
|
/**
|
|
9451
|
-
*
|
|
10313
|
+
* Defines what counts as a "meaningful" value:
|
|
9452
10314
|
* - non-empty strings
|
|
9453
10315
|
* - numbers / booleans
|
|
9454
10316
|
* - non-empty arrays
|
|
9455
|
-
* - objects with
|
|
10317
|
+
* - objects with common identifiers (key/uuid/id) or any own keys
|
|
9456
10318
|
*/
|
|
9457
10319
|
hasMeaningfulValue(v) {
|
|
9458
10320
|
if (v === null || v === undefined)
|
|
@@ -9466,34 +10328,43 @@ class MetaFormV2Component {
|
|
|
9466
10328
|
if (Array.isArray(v))
|
|
9467
10329
|
return v.length > 0;
|
|
9468
10330
|
if (typeof v === 'object') {
|
|
9469
|
-
//
|
|
10331
|
+
// Common selection shapes: { key }, { uuid }, { id }, etc.
|
|
9470
10332
|
if ('key' in v && v.key != null && `${v.key}`.trim() !== '')
|
|
9471
10333
|
return true;
|
|
9472
10334
|
if ('uuid' in v && v.uuid != null && `${v.uuid}`.trim() !== '')
|
|
9473
10335
|
return true;
|
|
9474
10336
|
if ('id' in v && v.id != null && `${v.id}`.trim() !== '')
|
|
9475
10337
|
return true;
|
|
9476
|
-
//
|
|
10338
|
+
// Fallback: any own keys
|
|
9477
10339
|
return Object.keys(v).length > 0;
|
|
9478
10340
|
}
|
|
9479
10341
|
return false;
|
|
9480
10342
|
}
|
|
9481
10343
|
/**
|
|
9482
|
-
* Visible invalid
|
|
9483
|
-
*
|
|
10344
|
+
* "Visible invalid" means:
|
|
10345
|
+
* - invalid
|
|
10346
|
+
* - AND user has interacted with it (touched or dirty)
|
|
10347
|
+
* This matches typical UI behavior: show errors only after interaction.
|
|
9484
10348
|
*/
|
|
9485
10349
|
isVisibleInvalid(ctrl) {
|
|
9486
10350
|
if (!ctrl)
|
|
9487
10351
|
return false;
|
|
9488
10352
|
return ctrl.invalid && (ctrl.touched || ctrl.dirty);
|
|
9489
10353
|
}
|
|
10354
|
+
/**
|
|
10355
|
+
* Checks if a group contains at least one visible invalid control.
|
|
10356
|
+
* Used to auto-expand groups when entering edit mode.
|
|
10357
|
+
*/
|
|
9490
10358
|
groupHasVisibleInvalid(g) {
|
|
9491
10359
|
const keys = (g?.ctrl ?? [])
|
|
9492
10360
|
.map((f) => f?.configuration?.key)
|
|
9493
10361
|
.filter(Boolean);
|
|
9494
10362
|
return keys.some((k) => this.isVisibleInvalid(this.form.get(k)));
|
|
9495
10363
|
}
|
|
9496
|
-
/**
|
|
10364
|
+
/**
|
|
10365
|
+
* Expands all groups that contain visible invalid controls,
|
|
10366
|
+
* while preserving any groups already expanded by the user.
|
|
10367
|
+
*/
|
|
9497
10368
|
expandVisibleInvalidGroupsUnion() {
|
|
9498
10369
|
if (!this.isGrouped)
|
|
9499
10370
|
return;
|
|
@@ -9506,6 +10377,7 @@ class MetaFormV2Component {
|
|
|
9506
10377
|
return;
|
|
9507
10378
|
this.expandedGroupIds = Array.from(new Set([...this.expandedGroupIds, ...invalidIds]));
|
|
9508
10379
|
}
|
|
10380
|
+
/** Cleanup dependency subscriptions when component is destroyed */
|
|
9509
10381
|
ngOnDestroy() {
|
|
9510
10382
|
this.depCleanup?.();
|
|
9511
10383
|
}
|