@iapps/ng-dhis2-ui 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { NgModule, ContentChild, Input, Directive, inject, ElementRef, ChangeDetectionStrategy, Component, EventEmitter, Output, NgZone, input, Injectable, model, computed, signal, effect, ViewChildren } from '@angular/core';
3
+ import { NgModule, ContentChild, Input, Directive, inject, ElementRef, ChangeDetectionStrategy, Component, EventEmitter, Output, NgZone, input, Injectable, model, computed, signal, effect, DestroyRef, ViewChildren } from '@angular/core';
4
4
  import { useDataQuery, useConfig, useAlert, clearSensitiveCaches, DataProvider, useDataEngine, Provider } from '@dhis2/app-runtime';
5
5
  import * as React from 'react';
6
6
  import React__default, { useState, useCallback, useRef, useEffect, createContext, useContext, useMemo, memo } from 'react';
7
7
  import * as ReactDOM from 'react-dom/client';
8
- import { from, map, catchError, of, zip, firstValueFrom, filter, take, Subject, takeUntil } from 'rxjs';
8
+ import { from, map, catchError, of, zip, firstValueFrom, filter, take, Subject, takeUntil, startWith, distinctUntilChanged } from 'rxjs';
9
9
  import parse from 'html-react-parser';
10
10
  import i18n from '@dhis2/d2-i18n';
11
11
  import { colors, theme, spacers } from '@dhis2/ui-constants';
@@ -32,7 +32,7 @@ import { isValid, format, addDays } from 'date-fns';
32
32
  import * as XLSX from 'xlsx';
33
33
  import * as i1 from '@angular/forms';
34
34
  import { FormControl, Validators, UntypedFormControl, UntypedFormGroup, FormGroup, ReactiveFormsModule } from '@angular/forms';
35
- import { toObservable } from '@angular/core/rxjs-interop';
35
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
36
36
  import { useMapEvents, useMap, MapContainer, TileLayer, GeoJSON, Marker } from 'react-leaflet';
37
37
  import moment from 'moment';
38
38
 
@@ -12326,7 +12326,7 @@ class FormUtil {
12326
12326
  });
12327
12327
  if (field.referenceKey) {
12328
12328
  const value = dataEntities
12329
- ? dataEntities[field.referenceKey] || ''
12329
+ ? dataEntities[field.referenceKey] ?? ''
12330
12330
  : '';
12331
12331
  group[field.referenceKey] = field.required
12332
12332
  ? new UntypedFormControl({ value, disabled: field.disabled }, {
@@ -12354,7 +12354,7 @@ class FormUtil {
12354
12354
  }
12355
12355
  static getFieldValue(field, dataEntities) {
12356
12356
  const fieldValue = dataEntities
12357
- ? dataEntities[field.id] || dataEntities[field.key] || ''
12357
+ ? dataEntities[field.id] ?? dataEntities[field.key] ?? ''
12358
12358
  : '';
12359
12359
  return isArray(fieldValue) ? JSON.stringify(fieldValue) : fieldValue;
12360
12360
  }
@@ -12998,6 +12998,31 @@ class FormMetaData {
12998
12998
  // Use of this source code is governed by a BSD-style
12999
12999
  // license that can be found in the LICENSE file.
13000
13000
 
13001
+ class FormValidationScopeUtil {
13002
+ static resolve(field, config, sections, actions) {
13003
+ const keys = [field.id, field.key, field.code].filter((key) => typeof key === 'string' && key.length > 0);
13004
+ const isMatch = (key) => typeof key === 'string' && keys.includes(key);
13005
+ const hiddenByMetadata = !!field.hidden;
13006
+ const hiddenByRule = (actions ?? []).some((action) => action.actionType === 'HIDEFIELD' && isMatch(action.field));
13007
+ const hiddenByAssignment = !!config?.hideCustomAssignedFields &&
13008
+ (config.autoAssignedValues ?? []).some((assignment) => isMatch(assignment.field));
13009
+ // Section membership is relevant only if the TRACKER form actually uses
13010
+ // configured sections. The fallback template renders all sanitized fields.
13011
+ const hasTrackerSections = config?.formType === 'TRACKER' &&
13012
+ Array.isArray(sections) &&
13013
+ sections.length > 0;
13014
+ const absentFromTrackerSections = !!hasTrackerSections &&
13015
+ !sections.some((section) => (section?.formFields ?? []).some((sectionField) => [sectionField?.id, sectionField?.key, sectionField?.code].some(isMatch)));
13016
+ return {
13017
+ excluded: hiddenByMetadata || hiddenByRule || hiddenByAssignment || absentFromTrackerSections,
13018
+ hiddenByMetadata,
13019
+ hiddenByRule,
13020
+ hiddenByAssignment,
13021
+ absentFromTrackerSections,
13022
+ };
13023
+ }
13024
+ }
13025
+
13001
13026
  class FormComponent {
13002
13027
  get layoutCssClass() {
13003
13028
  if (!this.isFormHorizontal()) {
@@ -13078,6 +13103,7 @@ class FormComponent {
13078
13103
  ...(ngDevMode ? [{ debugName: "configuration" }] : /* istanbul ignore next */ []));
13079
13104
  this.formUpdate = new EventEmitter();
13080
13105
  this.formValidityUpdate = new EventEmitter();
13106
+ this.destroyRef = inject(DestroyRef);
13081
13107
  // get sanitizedFields(): IFormField<string>[] {
13082
13108
  // return (this.fields() || []).filter((field: any) => {
13083
13109
  // if (field.hidden) {
@@ -13116,40 +13142,11 @@ class FormComponent {
13116
13142
  return map;
13117
13143
  }, /* @ts-ignore */
13118
13144
  ...(ngDevMode ? [{ debugName: "fieldControlKeyById" }] : /* istanbul ignore next */ []));
13119
- // get sanitizedFields(): IFormField<string>[] {
13120
- // return (this.fields() || []).filter(
13121
- // (field) => !this.isFieldHiddenByRules(field)
13122
- // );
13123
- // }
13124
- // getVisibleSectionFields(section: any): IFormField<string>[] {
13125
- // const rawFields: IFormField<string>[] = section?.formFields ?? [];
13126
- // return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13127
- // }
13128
- // private isFieldHiddenByRules(field: IFormField<string>): boolean {
13129
- // if (field.hidden) {
13130
- // return true;
13131
- // }
13132
- // const type = (field as any).isDataElement
13133
- // ? 'dataElement'
13134
- // : 'trackedEntityAttribute';
13135
- // const elementItem = (field as any)[type] || field;
13136
- // if (!elementItem) return false;
13137
- // const rule = (this.programRuleActions() || []).find(
13138
- // (ruleAction) =>
13139
- // ruleAction.field === elementItem.id &&
13140
- // ruleAction.actionType === 'HIDEFIELD'
13141
- // );
13142
- // return !!rule;
13143
- // }
13144
- // 1) Precompute the hidden field ids from programRuleActions()
13145
- this.hiddenFieldIds = computed(() => {
13146
- const actions = this.programRuleActions() || [];
13147
- return new Set(actions
13148
- .filter((ruleAction) => ruleAction.actionType === 'HIDEFIELD')
13149
- .map((ruleAction) => ruleAction.field)
13150
- .filter((id) => !!id));
13151
- }, /* @ts-ignore */
13152
- ...(ngDevMode ? [{ debugName: "hiddenFieldIds" }] : /* istanbul ignore next */ []));
13145
+ // Track only controls disabled by this component's visibility handling.
13146
+ // Do not re-enable controls disabled by metadata or other components.
13147
+ this.visibilityDisabledKeys = new Set();
13148
+ this.unrenderedViewDisabledKeys = new Set();
13149
+ this.previousShowErrorIds = new Set();
13153
13150
  this.ruleProcessor = effect(() => {
13154
13151
  this.#processRules(this.programRuleActions());
13155
13152
  }, /* @ts-ignore */
@@ -13250,6 +13247,9 @@ class FormComponent {
13250
13247
  return hasShowSectionAction || !hasHideSectionAction;
13251
13248
  }
13252
13249
  ngOnInit() {
13250
+ // A field can be hidden directly by metadata before program rules run.
13251
+ // Exclude such fields from validation without discarding their values.
13252
+ this.#processRules(this.programRuleActions(), true);
13253
13253
  this.values = this.form().getRawValue();
13254
13254
  // TODO: This is uneccesary emit at this stage of execution, since nothing has changes at initiation and potentially break some of implementation that depends formUpdate, which also is triggered when there is actual change in the form apart form initial values that came with it
13255
13255
  this.formUpdate.emit(new FormValue(this.form(), this.fields()));
@@ -13258,26 +13258,152 @@ class FormComponent {
13258
13258
  ngOnChanges(changes) {
13259
13259
  this.values = this.form().getRawValue();
13260
13260
  }
13261
- #processRules(ruleActions) {
13262
- ruleActions.forEach((ruleAction) => {
13263
- switch (ruleAction.actionType) {
13264
- case 'HIDEFIELD': {
13265
- const fieldToRemove = this.fields().find((field) => field.id === ruleAction.field);
13266
- if (fieldToRemove) {
13267
- const form = this.form();
13268
- const currentValue = form.get(fieldToRemove.key)?.value;
13269
- const shouldPreserveValue = fieldToRemove.disabled || fieldToRemove.generated;
13270
- if (currentValue != null && !shouldPreserveValue) {
13271
- form.get(fieldToRemove.key)?.setValue(null);
13272
- this.onFieldUpdate(form, fieldToRemove);
13273
- }
13261
+ ngAfterViewInit() {
13262
+ // For expanded, non-collapsible TRACKER forms, the instantiated field
13263
+ // components are the final authority on what the user can actually edit.
13264
+ // Avoid this policy for collapsible sections: collapsing must never waive
13265
+ // mandatory fields that the user can reveal by expanding a section.
13266
+ if (this.programEntryFormConfig()?.formType !== 'TRACKER' ||
13267
+ this.collapsibleSections())
13268
+ return;
13269
+ queueMicrotask(() => this.syncActuallyRenderedFields());
13270
+ this.fieldComponents.changes
13271
+ .pipe(takeUntilDestroyed(this.destroyRef))
13272
+ .subscribe(() => queueMicrotask(() => this.syncActuallyRenderedFields()));
13273
+ }
13274
+ syncActuallyRenderedFields() {
13275
+ if (this.destroyRef.destroyed)
13276
+ return;
13277
+ if (this.programEntryFormConfig()?.formType !== 'TRACKER' ||
13278
+ this.collapsibleSections())
13279
+ return;
13280
+ const renderedIds = new Set(this.fieldComponents.toArray()
13281
+ .map((component) => component.field()?.id)
13282
+ .filter((id) => typeof id === 'string'));
13283
+ // An empty view may be a loading/teardown transition, not an intentionally
13284
+ // empty form. Never disable the entire form based on this transient state.
13285
+ if (renderedIds.size === 0)
13286
+ return;
13287
+ const form = this.form();
13288
+ let changed = false;
13289
+ for (const field of this.fields() ?? []) {
13290
+ if (!field?.id)
13291
+ continue;
13292
+ const key = field.key || field.id;
13293
+ const controlKeys = [key, field.referenceKey].filter((item) => typeof item === 'string' && item.length > 0);
13294
+ const scope = FormValidationScopeUtil.resolve(field, this.programEntryFormConfig(), this.programEntryFormMetaData()?.sections, this.programRuleActions());
13295
+ const actuallyUnrendered = !renderedIds.has(field.id);
13296
+ for (const controlKey of controlKeys) {
13297
+ const control = form.get(controlKey);
13298
+ if (!control)
13299
+ continue;
13300
+ if (actuallyUnrendered) {
13301
+ if (control.enabled) {
13302
+ control.disable({ emitEvent: false });
13303
+ this.unrenderedViewDisabledKeys.add(controlKey);
13304
+ changed = true;
13274
13305
  }
13275
- break;
13276
13306
  }
13277
- default:
13278
- break;
13307
+ else if (this.unrenderedViewDisabledKeys.delete(controlKey) &&
13308
+ !scope.excluded && !field.disabled && !field.generated &&
13309
+ control.disabled) {
13310
+ control.enable({ emitEvent: false });
13311
+ changed = true;
13312
+ }
13279
13313
  }
13280
- });
13314
+ }
13315
+ if (changed)
13316
+ form.updateValueAndValidity({ emitEvent: true });
13317
+ }
13318
+ #processRules(ruleActions, force = false) {
13319
+ // Share the same visibility/validation policy as FlatFormComponent.
13320
+ const config = this.programEntryFormConfig();
13321
+ const sections = this.programEntryFormMetaData()?.sections;
13322
+ const hasTrackerSections = config?.formType === 'TRACKER' && Array.isArray(sections) && sections.length > 0;
13323
+ // This effect can run before required inputs are set. An empty action set
13324
+ // needs no work until ngOnInit synchronizes metadata-hidden controls.
13325
+ if (!ruleActions.length &&
13326
+ !this.visibilityDisabledKeys.size &&
13327
+ !this.previousShowErrorIds.size &&
13328
+ !hasTrackerSections &&
13329
+ !config?.hideCustomAssignedFields &&
13330
+ !force) {
13331
+ return;
13332
+ }
13333
+ const form = this.form();
13334
+ const showErrorIds = new Set(ruleActions
13335
+ .filter((action) => action.actionType === 'SHOWERROR')
13336
+ .map((action) => action.field)
13337
+ .filter((id) => !!id));
13338
+ let statusChanged = false;
13339
+ let clearedField;
13340
+ for (const field of this.fields() ?? []) {
13341
+ if (!field)
13342
+ continue;
13343
+ const controlKey = field.key || field.id;
13344
+ const control = form.get(controlKey);
13345
+ if (!control)
13346
+ continue;
13347
+ const scope = FormValidationScopeUtil.resolve(field, config, sections, ruleActions);
13348
+ const hiddenByRule = scope.hiddenByRule;
13349
+ const omittedFromSections = scope.absentFromTrackerSections;
13350
+ const hidden = scope.excluded;
13351
+ const originallyDisabled = !!field.disabled || !!field.generated;
13352
+ if (hidden) {
13353
+ // Retain prior HIDEFIELD clearing semantics for editable fields.
13354
+ // Metadata-hidden values (e.g. auto-assigned attributes) are preserved.
13355
+ if (hiddenByRule &&
13356
+ !omittedFromSections &&
13357
+ !originallyDisabled &&
13358
+ control.value != null) {
13359
+ control.setValue(null, { emitEvent: false });
13360
+ clearedField = field;
13361
+ statusChanged = true;
13362
+ }
13363
+ }
13364
+ // Keep both the field's control and any generated reference control in
13365
+ // the same validation scope; preserve their raw values when hidden.
13366
+ const keys = [controlKey, field.referenceKey].filter((key) => typeof key === 'string' && key.length > 0);
13367
+ for (const key of keys) {
13368
+ const target = form.get(key);
13369
+ if (!target)
13370
+ continue;
13371
+ if (hidden) {
13372
+ if (target.enabled) {
13373
+ target.disable({ emitEvent: false });
13374
+ statusChanged = true;
13375
+ }
13376
+ // Also track initial exclusions already disabled by FlatFormComponent,
13377
+ // so visibility changes can restore them without touching metadata locks.
13378
+ this.visibilityDisabledKeys.add(key);
13379
+ }
13380
+ else if (this.visibilityDisabledKeys.delete(key)) {
13381
+ if (!originallyDisabled && target.disabled) {
13382
+ target.enable({ emitEvent: false });
13383
+ statusChanged = true;
13384
+ }
13385
+ }
13386
+ }
13387
+ // A SHOWERROR action may cease to apply without any control value change.
13388
+ // Remove only its custom error; keep required/pattern/min/max errors.
13389
+ if (this.previousShowErrorIds.has(field.id) &&
13390
+ !showErrorIds.has(field.id) &&
13391
+ control.hasError('customError')) {
13392
+ const remainingErrors = { ...(control.errors ?? {}) };
13393
+ delete remainingErrors['customError'];
13394
+ control.setErrors(Object.keys(remainingErrors).length ? remainingErrors : null, { emitEvent: false });
13395
+ statusChanged = true;
13396
+ }
13397
+ }
13398
+ this.previousShowErrorIds.clear();
13399
+ showErrorIds.forEach((id) => this.previousShowErrorIds.add(id));
13400
+ if (statusChanged) {
13401
+ // Emit a single aggregate validity change for the parent flat form.
13402
+ form.updateValueAndValidity({ emitEvent: true });
13403
+ }
13404
+ if (clearedField) {
13405
+ this.onFieldUpdate(form, clearedField);
13406
+ }
13281
13407
  }
13282
13408
  onSubmit() {
13283
13409
  this.formUpdate.emit(this.form().getRawValue());
@@ -13302,7 +13428,7 @@ class FormComponent {
13302
13428
  }
13303
13429
  onRunTimeOptionUpdate(result) {
13304
13430
  if (this.fieldComponents) {
13305
- const fieldToUpdate = this.fieldComponents.find((fieldComponent) => fieldComponent?.field()?.dependentField?.id === result?.field?.id);
13431
+ const fieldToUpdate = this.fieldComponents.toArray().find((fieldComponent) => fieldComponent?.field()?.dependentField?.id === result?.field?.id);
13306
13432
  const selectedValue = String(this.form()?.value[result?.field?.id] ??
13307
13433
  this.form()?.value[result?.field?.key] ??
13308
13434
  '');
@@ -13313,6 +13439,31 @@ class FormComponent {
13313
13439
  ngOnDestroy() {
13314
13440
  this.ruleProcessor.destroy();
13315
13441
  }
13442
+ // get sanitizedFields(): IFormField<string>[] {
13443
+ // return (this.fields() || []).filter(
13444
+ // (field) => !this.isFieldHiddenByRules(field)
13445
+ // );
13446
+ // }
13447
+ // getVisibleSectionFields(section: any): IFormField<string>[] {
13448
+ // const rawFields: IFormField<string>[] = section?.formFields ?? [];
13449
+ // return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13450
+ // }
13451
+ // private isFieldHiddenByRules(field: IFormField<string>): boolean {
13452
+ // if (field.hidden) {
13453
+ // return true;
13454
+ // }
13455
+ // const type = (field as any).isDataElement
13456
+ // ? 'dataElement'
13457
+ // : 'trackedEntityAttribute';
13458
+ // const elementItem = (field as any)[type] || field;
13459
+ // if (!elementItem) return false;
13460
+ // const rule = (this.programRuleActions() || []).find(
13461
+ // (ruleAction) =>
13462
+ // ruleAction.field === elementItem.id &&
13463
+ // ruleAction.actionType === 'HIDEFIELD'
13464
+ // );
13465
+ // return !!rule;
13466
+ // }
13316
13467
  get sanitizedFields() {
13317
13468
  return (this.fields() || []).filter((field) => !this.isFieldHiddenByRules(field));
13318
13469
  }
@@ -13321,17 +13472,7 @@ class FormComponent {
13321
13472
  return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13322
13473
  }
13323
13474
  isFieldHiddenByRules(field) {
13324
- if (field.hidden) {
13325
- return true;
13326
- }
13327
- const type = field.isDataElement
13328
- ? 'dataElement'
13329
- : 'trackedEntityAttribute';
13330
- const elementItem = field || field;
13331
- if (!elementItem || !elementItem.id) {
13332
- return false;
13333
- }
13334
- return this.hiddenFieldIds().has(elementItem.id);
13475
+ return FormValidationScopeUtil.resolve(field, this.programEntryFormConfig(), this.programEntryFormMetaData()?.sections, this.programRuleActions()).excluded;
13335
13476
  }
13336
13477
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13337
13478
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FormComponent, isStandalone: false, selector: "ng-dhis2-ui-form", inputs: { programEntryFormConfig: { classPropertyName: "programEntryFormConfig", publicName: "programEntryFormConfig", isSignal: true, isRequired: false, transformFunction: null }, formConfig: { classPropertyName: "formConfig", publicName: "formConfig", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, isFormHorizontal: { classPropertyName: "isFormHorizontal", publicName: "isFormHorizontal", isSignal: true, isRequired: false, transformFunction: null }, showSaveButton: { classPropertyName: "showSaveButton", publicName: "showSaveButton", isSignal: true, isRequired: false, transformFunction: null }, fieldsData: { classPropertyName: "fieldsData", publicName: "fieldsData", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, programEntryFormMetaData: { classPropertyName: "programEntryFormMetaData", publicName: "programEntryFormMetaData", isSignal: true, isRequired: false, transformFunction: null }, fieldClass: { classPropertyName: "fieldClass", publicName: "fieldClass", isSignal: true, isRequired: false, transformFunction: null }, shouldRenderAsCheckBoxesButton: { classPropertyName: "shouldRenderAsCheckBoxesButton", publicName: "shouldRenderAsCheckBoxesButton", isSignal: true, isRequired: false, transformFunction: null }, programRuleActions: { classPropertyName: "programRuleActions", publicName: "programRuleActions", isSignal: true, isRequired: false, transformFunction: null }, dataEntities: { classPropertyName: "dataEntities", publicName: "dataEntities", isSignal: true, isRequired: false, transformFunction: null }, dataId: { classPropertyName: "dataId", publicName: "dataId", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, optionCascadeConfigs: { classPropertyName: "optionCascadeConfigs", publicName: "optionCascadeConfigs", isSignal: true, isRequired: false, transformFunction: null }, coordinatePickerGeoConfig: { classPropertyName: "coordinatePickerGeoConfig", publicName: "coordinatePickerGeoConfig", isSignal: true, isRequired: false, transformFunction: null }, program: { classPropertyName: "program", publicName: "program", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formUpdate: "formUpdate", formValidityUpdate: "formValidityUpdate" }, viewQueries: [{ propertyName: "fieldComponents", predicate: FormFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- <form [formGroup]=\"form()\" class=\"{{ configuration().directionClassName }}\">\n <div class=\"form-name\">\n <h2>{{ configuration().formName }}</h2>\n </div>\n\n <div>\n {{ formMetaData().sections }}\n </div>\n <div\n *ngFor=\"let field of sanitizedFields\"\n class=\"form-field-{{ configuration().direction }}\"\n >\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [program]=\"program()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n</form> -->\n\n<!-- <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n\n @if (meta.programStageSections?.length) {\n @for (section of meta.programStageSections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n\n <div class=\"form__section-content\">\n @if (section.formFields?.length) {\n @for (field of section.formFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n</form> -->\n\n<!-- @if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'EVENT') {\n <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n @if (meta.programStageSections?.length) {\n @for (section of meta.programStageSections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n }\n\n @if (!collapsibleSections() || isSectionOpen(section, $index)) {\n <div class=\"form__section-content\">\n @if (section.formFields?.length) {\n @for (field of section.formFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n} @else if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'TRACKER') {\n <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n @if (meta.sections?.length) {\n @for (section of meta.sections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n }\n \n @if (!collapsibleSections() || isSectionOpen(section, $index)) {\n <div class=\"form__section-content\">\n \n @if (section.formFields?.length) {\n \n @for (field of section.formFields; track field?.id ?? $index) {\n <div>\n </div>\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n} @else {\n <form [formGroup]=\"form()\" class=\"{{ configuration().directionClassName }}\">\n <div class=\"form-name\">\n <h2>{{ configuration().formName }}</h2>\n </div>\n\n <div>\n {{ formMetaData().sections }}\n </div>\n <div\n *ngFor=\"let field of sanitizedFields\"\n class=\"form-field-{{ configuration().direction }}\"\n >\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n </form>\n} -->\n<!-- \n@if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'EVENT')\n{\n<form\n [formGroup]=\"form()\"\n class=\"form {{ configuration().directionClassName }}\"\n>\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) { @if\n (meta?.programStageSections?.length) { @for (section of\n meta?.programStageSections; track section?.id ?? $index) { @let sectionId =\n 'section-' + (section?.id ?? $index); @let descId = sectionId + '-desc'; @let\n desc = section?.description; @let hasDesc = !!desc;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionId\">\n @if (section?.name) { @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n [attr.aria-controls]=\"sectionId\"\n [attr.aria-describedby]=\"\n hasDesc && isSectionOpen(section, $index) ? descId : null\n \"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ section.name }} - {{ descId }}</div>\n @if (hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n } } } @if (!collapsibleSections() || isSectionOpen(section, $index)) { @if\n (collapsibleSections() && hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionId\">\n @if (section.formFields?.length) { @for (field of\n getVisibleSectionFields(section); track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n \n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [optionCascadeConfigs]=\"optionCascadeConfigs()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n } } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } }\n</form>\n} @else if (programEntryFormConfig() && programEntryFormConfig()!.formType ===\n'TRACKER') {\n<form\n [formGroup]=\"form()\"\n class=\"form {{ configuration().directionClassName }}\"\n>\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) { @if (meta?.sections?.length) {\n @for (section of meta?.sections; track section?.id ?? $index) { @let sectionId\n = 'section-' + (section?.id ?? $index); @let descId = sectionId + '-desc';\n @let desc = section?.description; @let hasDesc = !!desc;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionId\">\n @if (section?.name) { @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n [attr.aria-controls]=\"sectionId\"\n [attr.aria-describedby]=\"\n hasDesc && isSectionOpen(section, $index) ? descId : null\n \"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n @if (hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n } } } @if (!collapsibleSections() || isSectionOpen(section, $index)) { @if\n (collapsibleSections() && hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n }\n <div class=\"form__section-content\" [id]=\"sectionId\">\n @if (section.formFields?.length) { @for (field of\n getVisibleSectionFields(section); track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n } } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } }\n</form>\n} @else {\n<form [formGroup]=\"form()\" [class]=\"configuration().directionClassName\">\n @if (configuration()?.formName) {\n <div class=\"form-name\">\n <h2>{{ configuration()?.formName }}</h2>\n </div>\n } @if (sanitizedFields?.length) { @for (field of sanitizedFields; track\n field?.id ?? $index) {\n <div class=\"form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } } @else {\n <div class=\"text-sm text-gray-500\">No fields to display.</div>\n }\n</form>\n} -->\n\n@let entryFormConfig = programEntryFormConfig();\n@let entryFormMeta = programEntryFormMetaData();\n@let uiConfig = configuration();\n@let formGroupValue = form();\n\n@let isSectionsCollapsible = collapsibleSections();\n\n@let dataEntitiesValue = dataEntities();\n@let dataEntityId = dataId();\n@let orgUnitRootIds = customOrgUnitRoots();\n\n@let cascadeConfigMap = optionCascadeConfigMap();\n@let cascadeConfigs = optionCascadeConfigs();\n\n@let ruleActionList = programRuleActions();\n@let renderAsCheckboxButtons = shouldRenderAsCheckBoxesButton();\n\n@let entryFormCoordinatePickerGeoConfig = coordinatePickerGeoConfig();\n@let entryFormFieldControlKeyById = fieldControlKeyById();\n\n@if (entryFormConfig?.formType === 'EVENT') {\n <form [formGroup]=\"formGroupValue\" class=\"form {{ uiConfig.directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ uiConfig.formName }}</h2>\n </div>\n\n @if (entryFormMeta?.programStageSections?.length) {\n @for (stageSection of entryFormMeta.programStageSections; track stageSection?.id ?? $index) {\n @if (isSectionVisible(stageSection, 'PROGRAM_STAGE_SECTION')) {\n @let sectionKey = stageSection?.id ?? $index;\n @let sectionDomId = 'section-' + sectionKey;\n @let descriptionDomId = sectionDomId + '-desc';\n <!-- @let sectionDescription = $safeNavigationMigration(stageSection?.description); -->\n @let sectionDescription = stageSection?.description;\n @let hasDescription = !!sectionDescription;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionDomId\">\n @if (stageSection?.name) {\n @if (isSectionsCollapsible) {\n @let isOpen = isSectionOpen(stageSection, $index);\n\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(stageSection, $index)\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"sectionDomId\"\n [attr.aria-describedby]=\"hasDescription && isOpen ? descriptionDomId : null\"\n >\n <span class=\"form__section-title\">{{ stageSection.name }}</span>\n <span class=\"form__chevron\" [class.form__chevron--open]=\"isOpen\"></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ stageSection.name }}</div>\n @if (hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n }\n }\n\n @let isOpen = !isSectionsCollapsible || isSectionOpen(stageSection, $index);\n @if (isOpen) {\n @if (isSectionsCollapsible && hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionDomId\">\n @if (stageSection?.formFields?.length) {\n @for (visibleField of getVisibleSectionFields(stageSection); track visibleField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"visibleField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [optionCascadeConfigs]=\"cascadeConfigs\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, visibleField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n }\n } @empty {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n\n} @else if (entryFormConfig?.formType === 'TRACKER') {\n\n <form [formGroup]=\"formGroupValue\" class=\"form {{ uiConfig.directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ uiConfig.formName }}</h2>\n </div>\n\n @if (entryFormMeta?.sections?.length) {\n @for (teiSection of entryFormMeta.sections; track teiSection?.id ?? $index) {\n @if (isSectionVisible(teiSection, 'PROGRAM_SECTION')) {\n @let sectionKey = teiSection?.id ?? $index;\n @let sectionDomId = 'section-' + sectionKey;\n @let descriptionDomId = sectionDomId + '-desc';\n <!-- @let sectionDescription = $safeNavigationMigration(teiSection?.description); -->\n @let sectionDescription = teiSection?.description;\n @let hasDescription = !!sectionDescription;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionDomId\">\n @if (teiSection?.name) {\n @if (isSectionsCollapsible) {\n @let isOpen = isSectionOpen(teiSection, $index);\n\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(teiSection, $index)\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"sectionDomId\"\n [attr.aria-describedby]=\"hasDescription && isOpen ? descriptionDomId : null\"\n >\n <span class=\"form__section-title\">{{ teiSection.name }}</span>\n <span class=\"form__chevron\" [class.form__chevron--open]=\"isOpen\"></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ teiSection.name }}</div>\n @if (hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n }\n }\n\n @let isOpen = !isSectionsCollapsible || isSectionOpen(teiSection, $index);\n @if (isOpen) {\n @if (isSectionsCollapsible && hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionDomId\">\n @if (teiSection?.formFields?.length) {\n @for (visibleField of getVisibleSectionFields(teiSection); track visibleField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"visibleField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, visibleField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n }\n } @empty {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n\n} @else {\n\n <form [formGroup]=\"formGroupValue\" [class]=\"uiConfig.directionClassName\">\n @if (uiConfig?.formName) {\n <div class=\"form-name\">\n <h2>{{ uiConfig.formName }}</h2>\n </div>\n }\n\n @if (sanitizedFields?.length) {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n }\n } @else {\n <div class=\"text-sm text-gray-500\">No fields to display.</div>\n }\n </form>\n\n}\n", styles: ["@charset \"UTF-8\";.form__section-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;font-size:15px;font-weight:600;padding:14px 16px 16px;background-color:#e8edf2;color:#374151;border:0;cursor:pointer;text-align:left}.form__section-toggle:focus{outline:2px solid #93c5fd;outline-offset:2px}.form__section-title{pointer-events:none}.form__chevron{width:10px;height:10px;display:inline-block;margin-left:12px;-webkit-mask-image:linear-gradient(#000,#000);mask-image:linear-gradient(#000,#000);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .12s ease-in-out}.form__chevron--open{transform:rotate(45deg)}.form__section{border:1px solid #eee;margin-bottom:16px;background-color:#fff}.form__field-group-name{display:inline-flex;font-size:14px;font-weight:600;padding:8px 16px;background-color:#e8edf2;color:#374151}.form__section-content{padding:16px}.form__section-description{margin:5px 5px 15px;font-size:15px;line-height:1.5;color:#4b5563;white-space:pre-line}.form__section-description:before{content:\"\";display:inline-block;width:4px;height:.9em;margin-right:8px;vertical-align:-2px;background-color:#cbd5e1;border-radius:2px}\n"], dependencies: [{ kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormFieldComponent, selector: "ng-dhis2-ui-form-field", inputs: ["field", "fieldConfig", "form", "isCheckBoxButton", "fieldClass", "programRuleActions", "dataEntities", "minDate", "maxDate", "dataId", "customOrgUnitRoots", "program", "optionCascadeConfigs", "optionCascadeConfigMap", "coordinatePickerGeoConfig", "fieldControlKeyById"], outputs: ["fieldUpdate", "immediateFieldUpdate", "runtimeOptionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -13634,6 +13775,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
13634
13775
  class FlatFormComponent extends BaseFormWrapperComponent {
13635
13776
  constructor() {
13636
13777
  super(...arguments);
13778
+ this.destroyRef = inject(DestroyRef);
13637
13779
  this.collapsibleSections = input(false, /* @ts-ignore */
13638
13780
  ...(ngDevMode ? [{ debugName: "collapsibleSections" }] : /* istanbul ignore next */ []));
13639
13781
  this.programEntryFormConfig = input.required(/* @ts-ignore */
@@ -13643,10 +13785,59 @@ class FlatFormComponent extends BaseFormWrapperComponent {
13643
13785
  }
13644
13786
  ngOnInit() {
13645
13787
  this.formGroup = FormUtil.getFormGroup(this.fields(), this.dataValues());
13788
+ // Build the interactive validation scope before the first validity event.
13789
+ // A TRACKER with sections renders only fields belonging to those sections,
13790
+ // while FormUtil creates controls for the complete metadata field list.
13791
+ this.excludeUnrenderedTrackerControls();
13792
+ // Form controls can change validity without going through a fieldUpdate
13793
+ // callback (e.g. conditional validators, program rules, or async changes).
13794
+ // Keep the form validity output in sync with the actual FormGroup status.
13795
+ this.formGroup.statusChanges
13796
+ .pipe(startWith(this.formGroup.status), map((status) => status === 'VALID'), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
13797
+ .subscribe((isValid) => this.updateFormValidity(isValid));
13646
13798
  this.onFormUpdate(this.dataValues());
13647
13799
  }
13800
+ /**
13801
+ * Exclude only controls that cannot be edited in this form: metadata-hidden,
13802
+ * HIDEFIELD, hidden auto-assigned, or absent from TRACKER sections.
13803
+ * Controls keep their raw values for the existing SDK save flow.
13804
+ */
13805
+ excludeUnrenderedTrackerControls() {
13806
+ const config = this.programEntryFormConfig();
13807
+ const sections = this.programEntryFormMetaData()?.sections;
13808
+ const actions = this.ruleActions() ?? [];
13809
+ let changed = false;
13810
+ for (const field of this.fields() ?? []) {
13811
+ if (!field)
13812
+ continue;
13813
+ const scope = FormValidationScopeUtil.resolve(field, config, sections, actions);
13814
+ const control = this.formGroup.get(field.key || field.id);
13815
+ if (scope.excluded && control?.enabled) {
13816
+ control.disable({ emitEvent: false });
13817
+ changed = true;
13818
+ }
13819
+ // A referenceKey is a separate generated control and must share its
13820
+ // source field's visibility; it must not remain an invisible requirement.
13821
+ if (scope.excluded && field.referenceKey) {
13822
+ const reference = this.formGroup.get(field.referenceKey);
13823
+ if (reference?.enabled) {
13824
+ reference.disable({ emitEvent: false });
13825
+ changed = true;
13826
+ }
13827
+ }
13828
+ }
13829
+ if (changed)
13830
+ this.formGroup.updateValueAndValidity({ emitEvent: false });
13831
+ }
13648
13832
  onFormUpdate(dataValues) {
13649
- this.updateDataValues(dataValues ?? this.formGroup.getRawValue());
13833
+ // Controls can be omitted from a FLAT form when their fields are hidden.
13834
+ // Keep their initial values while letting rendered controls override them,
13835
+ // including intentional clears (null or empty string).
13836
+ const updatedValues = dataValues ?? this.formGroup.getRawValue();
13837
+ this.updateDataValues({
13838
+ ...this.dataValues(),
13839
+ ...updatedValues,
13840
+ });
13650
13841
  this.updateFormValidity(this.formGroup.valid);
13651
13842
  }
13652
13843
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FlatFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
@@ -15348,7 +15539,201 @@ class ProgramEntryFormMetaData {
15348
15539
  // Use of this source code is governed by a BSD-style
15349
15540
  // license that can be found in the LICENSE file.
15350
15541
 
15351
- // eslint-disable-next-line @angular-eslint/component-class-suffix
15542
+ function idOf(value) {
15543
+ if (typeof value === 'string')
15544
+ return value;
15545
+ if (value && typeof value === 'object' && 'id' in value) {
15546
+ const id = value.id;
15547
+ if (typeof id === 'string')
15548
+ return id;
15549
+ }
15550
+ return undefined;
15551
+ }
15552
+ function isEventContextField(field, config) {
15553
+ const name = field.id || field.key;
15554
+ const names = [name, field.key];
15555
+ if (names.some((n) => n === 'occurredAt' || n === 'eventDate')) {
15556
+ return config.hideEventDate !== true;
15557
+ }
15558
+ if (field.isOrgUnit || names.includes('orgUnit')) {
15559
+ return config.hideRegistrationUnit !== true;
15560
+ }
15561
+ return false;
15562
+ }
15563
+ function fieldIds(field) {
15564
+ return [field.id, idOf(field.dataElement), field.dataElementId]
15565
+ .filter((id) => typeof id === 'string' && id.length > 0);
15566
+ }
15567
+ function isTrackedEntityAttribute(field) {
15568
+ // Form metadata may use typed flags, or SDK-style attribute references.
15569
+ return field.isAttribute === true ||
15570
+ !!idOf(field.trackedEntityAttribute) ||
15571
+ !!idOf(field.attribute);
15572
+ }
15573
+ function belongsTo(field, stage) {
15574
+ return (idOf(field.programStage) ?? field.stageId ?? field.programStageId) === stage;
15575
+ }
15576
+ function matchingSectionFields(sections, stage) {
15577
+ return sections.filter((section) => {
15578
+ const owner = idOf(section.programStage) ?? section.stageId ?? section.programStageId;
15579
+ return !owner || owner === stage;
15580
+ });
15581
+ }
15582
+ class ProgramEntryFormScopeUtil {
15583
+ static resolve(metadata, config) {
15584
+ // Preserve the existing TRACKER-with-stage implementation. It can have
15585
+ // separate registration/stage workflows and must not be silently changed.
15586
+ if (config.formType !== 'EVENT' && config.formType !== 'TRACKER') {
15587
+ return { metadata, error: null };
15588
+ }
15589
+ if (config.formType === 'TRACKER') {
15590
+ if (config.programStage) {
15591
+ return { metadata, error: null };
15592
+ }
15593
+ // Registration without a stage has attributes and its existing special
15594
+ // registration fields; data elements are never required in this mode.
15595
+ const fields = metadata.formFields.filter((field) => field.isDataElement !== true &&
15596
+ !['occurredAt', 'eventDate'].includes(field.id) &&
15597
+ !['occurredAt', 'eventDate'].includes(field.key ?? ''));
15598
+ const allowed = new Set(fields.map((field) => field.id));
15599
+ const sections = metadata.sections?.map((section) => ({
15600
+ ...section,
15601
+ formFields: (section.formFields ?? []).filter((field) => allowed.has(field.id)),
15602
+ })).filter((section) => section.formFields.length > 0);
15603
+ return {
15604
+ metadata: { ...metadata, formFields: fields, sections },
15605
+ error: null,
15606
+ };
15607
+ }
15608
+ const stageId = config.programStage?.trim();
15609
+ if (!stageId) {
15610
+ return { metadata: null, error: 'EVENT form requires config.programStage.' };
15611
+ }
15612
+ const stageCandidates = [
15613
+ ...(Array.isArray(metadata.program?.programStages) ? metadata.program.programStages : []),
15614
+ ...(Array.isArray(metadata.programStages) ? metadata.programStages : []),
15615
+ ];
15616
+ if (metadata.programStage && typeof metadata.programStage !== 'string') {
15617
+ stageCandidates.push(metadata.programStage);
15618
+ }
15619
+ const stage = stageCandidates.find((item) => item.id === stageId);
15620
+ if (stageCandidates.length > 0 && !stage) {
15621
+ return {
15622
+ metadata: null,
15623
+ error: `Program stage ${stageId} is not part of the supplied program metadata.`,
15624
+ };
15625
+ }
15626
+ const stageElementIds = new Set((stage?.programStageDataElements ?? [])
15627
+ .map((item) => idOf(item.dataElement) ?? item.dataElementId)
15628
+ .filter((id) => !!id));
15629
+ // Sections supplied by the selected ProgramStage take precedence. The
15630
+ // top-level programStageSections is used when metadata is already scoped
15631
+ // to a stage, as is customary in entry-form metadata builders.
15632
+ const stageSectionsWithFields = stage?.programStageSections?.some((section) => (section.formFields ?? []).length > 0);
15633
+ const sectionSource = stageSectionsWithFields
15634
+ ? stage.programStageSections
15635
+ : metadata.programStageSections?.length
15636
+ ? metadata.programStageSections
15637
+ : stage?.programStageSections ?? [];
15638
+ const candidateSections = matchingSectionFields(sectionSource, stageId);
15639
+ const sectionFieldIds = new Set(candidateSections.flatMap((section) => (section.formFields ?? []).flatMap((field) => fieldIds(field))));
15640
+ // Stage sections sometimes contain complete field definitions even when
15641
+ // ProgramEntryFormMetaData.formFields contains registration attributes only.
15642
+ // Prefer the richer top-level definition where both are available.
15643
+ const candidates = [...metadata.formFields];
15644
+ const knownFieldIds = new Set(candidates.flatMap((field) => fieldIds(field)));
15645
+ for (const section of candidateSections) {
15646
+ for (const sectionField of section.formFields ?? []) {
15647
+ if (fieldIds(sectionField).some((id) => knownFieldIds.has(id)))
15648
+ continue;
15649
+ candidates.push(sectionField);
15650
+ fieldIds(sectionField).forEach((id) => knownFieldIds.add(id));
15651
+ }
15652
+ }
15653
+ // The metadata builder may not attach isDataElement=true. A stage's
15654
+ // programStageDataElements, its stage sections, or an explicit stage ID
15655
+ // is authoritative evidence of EVENT field membership. Do not allow
15656
+ // tracked-entity attributes through even when IDs overlap unexpectedly.
15657
+ const hasStageMembership = stageElementIds.size > 0 ||
15658
+ candidateSections.length > 0 ||
15659
+ candidates.some((field) => belongsTo(field, stageId));
15660
+ if (!hasStageMembership) {
15661
+ return {
15662
+ metadata: null,
15663
+ error: `No form metadata could be resolved for program stage ${stageId}.`,
15664
+ };
15665
+ }
15666
+ const fields = candidates.filter((field) => {
15667
+ if (isEventContextField(field, config))
15668
+ return true;
15669
+ if (isTrackedEntityAttribute(field))
15670
+ return false;
15671
+ const fieldStage = idOf(field.programStage) ?? field.stageId ?? field.programStageId;
15672
+ if (fieldStage && fieldStage !== stageId)
15673
+ return false;
15674
+ const refs = fieldIds(field);
15675
+ // An explicit stage DE list is the strongest source of truth.
15676
+ if (stageElementIds.size > 0) {
15677
+ return refs.some((id) => stageElementIds.has(id));
15678
+ }
15679
+ // Use selected-stage sections or explicit field ownership. Do not
15680
+ // require the optional isDataElement boolean from the metadata builder.
15681
+ return fieldStage === stageId || refs.some((id) => sectionFieldIds.has(id));
15682
+ });
15683
+ const allowed = new Set(fields.flatMap((field) => fieldIds(field)));
15684
+ const resolvedFieldById = new Map();
15685
+ for (const field of fields) {
15686
+ fieldIds(field).forEach((id) => resolvedFieldById.set(id, field));
15687
+ }
15688
+ const sections = candidateSections.map((section) => ({
15689
+ ...section,
15690
+ formFields: (section.formFields ?? [])
15691
+ .filter((field) => fieldIds(field).some((id) => allowed.has(id)))
15692
+ .map((field) => fieldIds(field)
15693
+ .map((id) => resolvedFieldById.get(id))
15694
+ .find((resolved) => !!resolved) ?? field),
15695
+ })).filter((section) => section.formFields.length > 0);
15696
+ // Fail closed only when there really are no stage fields. Do not reject
15697
+ // verified stage fields just because isDataElement was not supplied.
15698
+ const hasStageFields = fields.some((field) => !isEventContextField(field, config));
15699
+ const stageExplicitlyEmpty = !!stage &&
15700
+ Array.isArray(stage.programStageDataElements) &&
15701
+ stage.programStageDataElements.length === 0 &&
15702
+ sectionFieldIds.size === 0;
15703
+ if (!stageExplicitlyEmpty && !hasStageFields) {
15704
+ return {
15705
+ metadata: null,
15706
+ error: `Stage ${stageId} has no resolvable data-element field definitions. Check ProgramEntryFormMetaData stage sections and field IDs.`,
15707
+ };
15708
+ }
15709
+ // In section-based EVENT forms, ensure selected-stage fields not assigned
15710
+ // to a named section still render and are not invisible required controls.
15711
+ // This also keeps occurredAt editable when hideEventDate is false.
15712
+ if (sections.length > 0) {
15713
+ const sectioned = new Set(sections.flatMap((section) => (section.formFields ?? []).map((field) => field.id)));
15714
+ const extraFields = fields.filter((field) => !sectioned.has(field.id));
15715
+ if (extraFields.length > 0) {
15716
+ sections.push({
15717
+ id: `__event_additional_${stageId}`,
15718
+ name: 'Additional event fields',
15719
+ formFields: extraFields,
15720
+ });
15721
+ }
15722
+ }
15723
+ // Keep stage sections only; never pass registration sections to the EVENT
15724
+ // renderer. The renderer's fallback displays 'fields' when none exist.
15725
+ return {
15726
+ metadata: {
15727
+ ...metadata,
15728
+ formFields: fields,
15729
+ sections: [],
15730
+ programStageSections: sections,
15731
+ },
15732
+ error: null,
15733
+ };
15734
+ }
15735
+ }
15736
+
15352
15737
  class ProgramEntryFormModule {
15353
15738
  constructor() {
15354
15739
  this.config = input.required(/* @ts-ignore */
@@ -15375,13 +15760,34 @@ class ProgramEntryFormModule {
15375
15760
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
15376
15761
  this.metaData = signal(null, /* @ts-ignore */
15377
15762
  ...(ngDevMode ? [{ debugName: "metaData" }] : /* istanbul ignore next */ []));
15378
- this.isFormValid = signal(true, /* @ts-ignore */
15763
+ // Select exactly the configured EVENT program stage (or TRACKER registration)
15764
+ // before the FLAT form builds its FormGroup. Keep raw metadata for the SDK.
15765
+ this.activeFormScope = computed(() => {
15766
+ const metadata = this.metaData();
15767
+ if (!metadata)
15768
+ return null;
15769
+ return ProgramEntryFormScopeUtil.resolve(metadata, this.config());
15770
+ }, /* @ts-ignore */
15771
+ ...(ngDevMode ? [{ debugName: "activeFormScope" }] : /* istanbul ignore next */ []));
15772
+ // Do not enable submit before the child form has reported its validity.
15773
+ this.isFormValid = signal(false, /* @ts-ignore */
15379
15774
  ...(ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []));
15775
+ // Keep submission state in Angular so React component remounts cannot reset it.
15776
+ this.isSubmitting = signal(false, /* @ts-ignore */
15777
+ ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
15380
15778
  this.isFormValid$ = toObservable(this.isFormValid);
15381
15779
  this.instance = signal(null, /* @ts-ignore */
15382
15780
  ...(ngDevMode ? [{ debugName: "instance" }] : /* istanbul ignore next */ []));
15383
15781
  this.trackedEntityInstance = signal(null, /* @ts-ignore */
15384
15782
  ...(ngDevMode ? [{ debugName: "trackedEntityInstance" }] : /* istanbul ignore next */ []));
15783
+ // SDK instances are mutable class objects. A revision signal ensures that
15784
+ // dependent computed values refresh after updateDataValues() mutates one.
15785
+ this.instanceRevision = signal(0, /* @ts-ignore */
15786
+ ...(ngDevMode ? [{ debugName: "instanceRevision" }] : /* istanbul ignore next */ []));
15787
+ // Maintain configured defaults independently of the rendered field list.
15788
+ // hideCustomAssignedFields can remove their controls from the FormGroup.
15789
+ this.initialAutoAssignedValues = signal({}, /* @ts-ignore */
15790
+ ...(ngDevMode ? [{ debugName: "initialAutoAssignedValues" }] : /* istanbul ignore next */ []));
15385
15791
  this.preprocess = input(/* @ts-ignore */
15386
15792
  ...(ngDevMode ? [undefined, { debugName: "preprocess" }] : /* istanbul ignore next */ []));
15387
15793
  this.instanceSave = new EventEmitter();
@@ -15403,6 +15809,7 @@ class ProgramEntryFormModule {
15403
15809
  }, /* @ts-ignore */
15404
15810
  ...(ngDevMode ? [{ debugName: "dataId" }] : /* istanbul ignore next */ []));
15405
15811
  this.dataEntities = computed(() => {
15812
+ this.instanceRevision();
15406
15813
  if (!this.instance()) {
15407
15814
  return {};
15408
15815
  }
@@ -15414,41 +15821,60 @@ class ProgramEntryFormModule {
15414
15821
  ? this.#getTrackedEntityDataValues(this.instance())
15415
15822
  : this.instance();
15416
15823
  return {
15824
+ // Fallback for assignments hidden from the rendered form metadata.
15825
+ // Values held by the SDK instance take precedence when present.
15826
+ ...this.initialAutoAssignedValues(),
15417
15827
  ...attributeDataValues,
15418
15828
  ...(currentInstanceDataValues || {}),
15419
15829
  };
15420
15830
  }, /* @ts-ignore */
15421
15831
  ...(ngDevMode ? [{ debugName: "dataEntities" }] : /* istanbul ignore next */ []));
15422
15832
  this.FormActionButtons = computed(() => {
15423
- if (this.config().hideActionButtons) {
15833
+ const config = this.config();
15834
+ if (config.hideActionButtons) {
15424
15835
  return React__default.createElement(React__default.Fragment, null);
15425
15836
  }
15426
15837
  return () => {
15427
- const [isValid, setIsValid] = useState();
15428
- const [submitting, setSubmitting] = useState(false);
15429
- useEffect(() => {
15430
- const isValidSubscription = this.isFormValid$.subscribe((isValid) => {
15431
- setIsValid(isValid);
15838
+ // ReactWrapper may mount the supplied function only once. Angular's
15839
+ // computed() changing its return value does not guarantee that the
15840
+ // already-mounted React component will be rendered again.
15841
+ const [isValid, setIsValid] = React__default.useState(() => this.isFormValid());
15842
+ const [submitting, setSubmitting] = React__default.useState(() => this.isSubmitting());
15843
+ React__default.useEffect(() => {
15844
+ // Subscribe to the same event that Angular consumers receive.
15845
+ // It is emitted synchronously by onValidityUpdate(), so the mounted
15846
+ // React button can respond without a ReactWrapper input refresh.
15847
+ const subscription = this.formValidityUpdate.subscribe((valid) => {
15848
+ setIsValid(valid);
15432
15849
  });
15433
- return () => {
15434
- isValidSubscription.unsubscribe();
15435
- };
15850
+ // Catch an update that arrived before React mounted its effect.
15851
+ setIsValid(this.isFormValid());
15852
+ return () => subscription.unsubscribe();
15436
15853
  }, []);
15437
15854
  const onSave = () => {
15855
+ // Do not trust a possibly stale React callback for validation.
15856
+ if (!this.isFormValid() || this.isSubmitting())
15857
+ return;
15438
15858
  setSubmitting(true);
15439
15859
  this.ngZone.run(async () => {
15440
- const saveResponse = await this.#saveInstance();
15441
- this.instanceSaveCompleted.emit(saveResponse);
15442
- setSubmitting(false);
15860
+ this.isSubmitting.set(true);
15861
+ try {
15862
+ const saveResponse = await this.#saveInstance();
15863
+ this.instanceSaveCompleted.emit(saveResponse);
15864
+ }
15865
+ finally {
15866
+ this.isSubmitting.set(false);
15867
+ setSubmitting(false);
15868
+ }
15443
15869
  });
15444
15870
  };
15445
15871
  return (React__default.createElement(ButtonStrip$1, null,
15446
- !(this.config().hideSubmitButton || this.config().disableForm) && (React__default.createElement(Button$1, { primary: true, disabled: !isValid, loading: submitting, onClick: onSave }, this.config().submitButtonLabel)),
15447
- !this.config().hideCancelButton && (React__default.createElement(Button$1, { onClick: () => {
15872
+ !(config.hideSubmitButton || config.disableForm) && (React__default.createElement(Button$1, { primary: true, disabled: !isValid || submitting, loading: submitting, onClick: onSave }, config.submitButtonLabel)),
15873
+ !config.hideCancelButton && (React__default.createElement(Button$1, { onClick: () => {
15448
15874
  this.ngZone.run(() => {
15449
15875
  this.onCancel();
15450
15876
  });
15451
- } }, this.config().cancelButtonLabel))));
15877
+ } }, config.cancelButtonLabel))));
15452
15878
  };
15453
15879
  }, /* @ts-ignore */
15454
15880
  ...(ngDevMode ? [{ debugName: "FormActionButtons" }] : /* istanbul ignore next */ []));
@@ -15480,11 +15906,13 @@ class ProgramEntryFormModule {
15480
15906
  console.error('Failed to create or load instance for program entry form');
15481
15907
  return;
15482
15908
  }
15483
- this.instance.set(instance);
15909
+ // Apply assignments BEFORE publishing the instance to the form. This
15910
+ // prevents a child form from initializing with pre-assignment values.
15484
15911
  const autoAssigned = config.autoAssignedValues;
15485
15912
  if (autoAssigned?.length) {
15486
- this.#updateInstanceWithAutoAssignedValues(autoAssigned);
15913
+ this.#updateInstanceWithAutoAssignedValues(instance, autoAssigned);
15487
15914
  }
15915
+ this.instance.set(instance);
15488
15916
  }
15489
15917
  catch (error) {
15490
15918
  console.error('Error during ProgramEntryForm initialization', error);
@@ -15494,7 +15922,35 @@ class ProgramEntryFormModule {
15494
15922
  }
15495
15923
  }
15496
15924
  onFormUpdate(dataValues) {
15497
- this.#updateInstance(dataValues);
15925
+ // Hidden auto-assigned fields may be absent from the FormGroup. Preserve
15926
+ // their CURRENT values (not repeatedly their initial defaults), so later
15927
+ // programmatic updates are not accidentally rolled back by another field.
15928
+ const preservedAssignedValues = {};
15929
+ if (this.config().hideCustomAssignedFields) {
15930
+ // Read the mutable SDK instance directly: a cached computed signal may
15931
+ // not yet reflect values modified by external SDK/preprocess code.
15932
+ const currentInstance = this.instance();
15933
+ const attributeValues = this.trackedEntityInstance()
15934
+ ? this.#getTrackedEntityDataValues(this.trackedEntityInstance())
15935
+ : {};
15936
+ const currentInstanceValues = this.config().formType === 'TRACKER' && currentInstance
15937
+ ? this.#getTrackedEntityDataValues(currentInstance)
15938
+ : currentInstance ?? {};
15939
+ const currentValues = {
15940
+ ...attributeValues,
15941
+ ...currentInstanceValues,
15942
+ };
15943
+ for (const [key, initialValue] of Object.entries(this.initialAutoAssignedValues())) {
15944
+ // orgUnit has its own registration control and precedence rules.
15945
+ if (key === 'orgUnit')
15946
+ continue;
15947
+ preservedAssignedValues[key] = Object.prototype.hasOwnProperty.call(currentValues, key)
15948
+ ? currentValues[key]
15949
+ : initialValue;
15950
+ }
15951
+ }
15952
+ // Explicit user edits and clears (including null/empty string) win.
15953
+ this.#updateInstance({ ...preservedAssignedValues, ...dataValues });
15498
15954
  if (this.instance()) {
15499
15955
  this.instanceUpdate.emit(this.instance());
15500
15956
  }
@@ -15526,10 +15982,11 @@ class ProgramEntryFormModule {
15526
15982
  }
15527
15983
  return configuredKey;
15528
15984
  }
15529
- #updateInstanceWithAutoAssignedValues(autoAssignedValues) {
15985
+ #updateInstanceWithAutoAssignedValues(instance, autoAssignedValues) {
15530
15986
  const trackedEntityId = this.trackedEntity?.();
15531
15987
  if (
15532
- // Event forms also receive a trackedEntity ID checking the ID alone prevented their auto-assigned fields from being populated since its passed even in creation of the events.
15988
+ // Existing TRACKER records must not have their stored attributes replaced.
15989
+ // EVENT forms may legitimately have an existing tracked entity ID.
15533
15990
  this.config().formType === 'TRACKER' &&
15534
15991
  typeof trackedEntityId === 'string' &&
15535
15992
  trackedEntityId.trim().length > 0) {
@@ -15543,6 +16000,14 @@ class ProgramEntryFormModule {
15543
16000
  const aggregatedValues = {};
15544
16001
  for (let index = 0; index < valueCount; index++) {
15545
16002
  const { field: configuredKey, value } = autoAssignedValues[index];
16003
+ if (!configuredKey)
16004
+ continue;
16005
+ // An explicit orgUnit input takes precedence over the optional
16006
+ // auto-assignment fallback used for new TRACKER registration.
16007
+ if (configuredKey === 'orgUnit' && this.orgUnit()?.trim()) {
16008
+ aggregatedValues['orgUnit'] = this.orgUnit().trim();
16009
+ continue;
16010
+ }
15546
16011
  const semanticKey = metaData
15547
16012
  ? this.#resolveFieldRuntimeId(configuredKey, metaData)
15548
16013
  : configuredKey;
@@ -15554,26 +16019,30 @@ class ProgramEntryFormModule {
15554
16019
  if (Object.keys(aggregatedValues).length === 0) {
15555
16020
  return;
15556
16021
  }
15557
- this.#updateInstance(aggregatedValues);
16022
+ this.initialAutoAssignedValues.set(aggregatedValues);
16023
+ this.#applyDataValues(instance, aggregatedValues);
16024
+ }
16025
+ #applyDataValues(instance, dataValues) {
16026
+ for (const key in dataValues) {
16027
+ if (Object.prototype.hasOwnProperty.call(dataValues, key)) {
16028
+ // Keep the existing SDK integration behavior for other consumers.
16029
+ instance[key] = dataValues[key];
16030
+ }
16031
+ }
16032
+ instance.updateDataValues(dataValues, this.config().updateTeiOrgUnit);
15558
16033
  }
15559
16034
  #updateInstance(dataValues) {
15560
16035
  if (!dataValues || Object.keys(dataValues).length === 0) {
15561
16036
  return;
15562
16037
  }
15563
- const shouldUpdateOrgUnit = this.config().updateTeiOrgUnit;
15564
- this.instance.update((instance) => {
15565
- if (!instance) {
15566
- return instance;
15567
- }
15568
- for (const key in dataValues) {
15569
- if (Object.prototype.hasOwnProperty.call(dataValues, key)) {
15570
- instance[key] =
15571
- dataValues[key];
15572
- }
15573
- }
15574
- instance.updateDataValues(dataValues, shouldUpdateOrgUnit);
15575
- return instance;
15576
- });
16038
+ const instance = this.instance();
16039
+ if (!instance) {
16040
+ return;
16041
+ }
16042
+ this.#applyDataValues(instance, dataValues);
16043
+ // The SDK model is a class, so do not spread/clone it just to change the
16044
+ // signal reference (that would discard its prototype methods).
16045
+ this.instanceRevision.update((revision) => revision + 1);
15577
16046
  }
15578
16047
  async #saveInstance() {
15579
16048
  if (this.preprocess() && this.instance()) {
@@ -15603,7 +16072,7 @@ class ProgramEntryFormModule {
15603
16072
  }
15604
16073
  }
15605
16074
  async #setReservedValuesForCodeGeneration(instance) {
15606
- const orgUnitForCodeGeneration = this.codeGeneratorOrgUnit() || this.orgUnit();
16075
+ const orgUnitForCodeGeneration = this.codeGeneratorOrgUnit() || this.#resolveTrackerOrgUnit();
15607
16076
  const reservedValueQuery = this.d2.trackerModule.trackedEntity.setProgram(this.config().program);
15608
16077
  if (orgUnitForCodeGeneration) {
15609
16078
  reservedValueQuery.setOrgUnit(orgUnitForCodeGeneration);
@@ -15614,13 +16083,28 @@ class ProgramEntryFormModule {
15614
16083
  });
15615
16084
  return instance;
15616
16085
  }
16086
+ #resolveTrackerOrgUnit() {
16087
+ const explicitOrgUnit = this.orgUnit()?.trim();
16088
+ if (explicitOrgUnit) {
16089
+ return explicitOrgUnit;
16090
+ }
16091
+ // For new TRACKER records, an orgUnit auto-assignment must also be used
16092
+ // when creating the instance/query, not only after it has been created.
16093
+ if (this.config().formType !== 'TRACKER' || this.trackedEntity()?.trim()) {
16094
+ return undefined;
16095
+ }
16096
+ const assignedOrgUnit = this.config().autoAssignedValues?.find((item) => item.field === 'orgUnit')?.value;
16097
+ return typeof assignedOrgUnit === 'string' && assignedOrgUnit.trim()
16098
+ ? assignedOrgUnit.trim()
16099
+ : undefined;
16100
+ }
15617
16101
  #getQuery(instanceType) {
15618
16102
  const d2 = window.d2Web;
15619
16103
  switch (instanceType) {
15620
16104
  case 'TRACKER':
15621
16105
  return d2.trackerModule.trackedEntity
15622
16106
  .setProgram(this.config().program)
15623
- .setOrgUnit(this.orgUnit());
16107
+ .setOrgUnit(this.#resolveTrackerOrgUnit());
15624
16108
  case 'EVENT':
15625
16109
  return d2.eventModule.event
15626
16110
  .setProgram(this.config().program)
@@ -15689,11 +16173,11 @@ class ProgramEntryFormModule {
15689
16173
  }
15690
16174
  }
15691
16175
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProgramEntryFormModule, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15692
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ProgramEntryFormModule, isStandalone: true, selector: "ng-dhis2-ui-program-entry-form", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, trackedEntity: { classPropertyName: "trackedEntity", publicName: "trackedEntity", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, enrollment: { classPropertyName: "enrollment", publicName: "enrollment", isSignal: true, isRequired: false, transformFunction: null }, orgUnit: { classPropertyName: "orgUnit", publicName: "orgUnit", isSignal: true, isRequired: false, transformFunction: null }, codeGeneratorOrgUnit: { classPropertyName: "codeGeneratorOrgUnit", publicName: "codeGeneratorOrgUnit", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null }, preprocess: { classPropertyName: "preprocess", publicName: "preprocess", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { instanceSave: "instanceSave", instanceSaveCompleted: "instanceSaveCompleted", instanceUpdate: "instanceUpdate", formValidityUpdate: "formValidityUpdate", formCancel: "formCancel" }, ngImport: i0, template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if(metaData()) {\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"metaData()!\"\n [name]=\"metaData()!.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"], dependencies: [{ kind: "ngmodule", type: D2FormModule }, { kind: "component", type: FlatFormComponent, selector: "ng-dhis2-ui-flat-form", inputs: ["collapsibleSections", "programEntryFormConfig", "optionCascadeConfigs"] }, { kind: "directive", type: ReactWrapperModule, selector: "ng-dhis2-ui-wrapper", inputs: ["props", "component"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16176
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ProgramEntryFormModule, isStandalone: true, selector: "ng-dhis2-ui-program-entry-form", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, trackedEntity: { classPropertyName: "trackedEntity", publicName: "trackedEntity", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, enrollment: { classPropertyName: "enrollment", publicName: "enrollment", isSignal: true, isRequired: false, transformFunction: null }, orgUnit: { classPropertyName: "orgUnit", publicName: "orgUnit", isSignal: true, isRequired: false, transformFunction: null }, codeGeneratorOrgUnit: { classPropertyName: "codeGeneratorOrgUnit", publicName: "codeGeneratorOrgUnit", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null }, preprocess: { classPropertyName: "preprocess", publicName: "preprocess", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { instanceSave: "instanceSave", instanceSaveCompleted: "instanceSaveCompleted", instanceUpdate: "instanceUpdate", formValidityUpdate: "formValidityUpdate", formCancel: "formCancel" }, ngImport: i0, template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if (activeFormScope()?.error) {\n<div role=\"alert\" class=\"program-entry__form-error\">\n {{ activeFormScope()?.error }}\n</div>\n} @else if (activeFormScope()?.metadata) {\n@let activeMetaData = activeFormScope()!.metadata!;\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"activeMetaData\"\n [name]=\"activeMetaData.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"activeMetaData.rules\"\n [fields]=\"activeMetaData.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"], dependencies: [{ kind: "ngmodule", type: D2FormModule }, { kind: "component", type: FlatFormComponent, selector: "ng-dhis2-ui-flat-form", inputs: ["collapsibleSections", "programEntryFormConfig", "optionCascadeConfigs"] }, { kind: "directive", type: ReactWrapperModule, selector: "ng-dhis2-ui-wrapper", inputs: ["props", "component"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15693
16177
  }
15694
16178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProgramEntryFormModule, decorators: [{
15695
16179
  type: Component,
15696
- args: [{ selector: 'ng-dhis2-ui-program-entry-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [D2FormModule, ReactWrapperModule], template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if(metaData()) {\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"metaData()!\"\n [name]=\"metaData()!.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"] }]
16180
+ args: [{ selector: 'ng-dhis2-ui-program-entry-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [D2FormModule, ReactWrapperModule], template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if (activeFormScope()?.error) {\n<div role=\"alert\" class=\"program-entry__form-error\">\n {{ activeFormScope()?.error }}\n</div>\n} @else if (activeFormScope()?.metadata) {\n@let activeMetaData = activeFormScope()!.metadata!;\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"activeMetaData\"\n [name]=\"activeMetaData.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"activeMetaData.rules\"\n [fields]=\"activeMetaData.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"] }]
15697
16181
  }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], trackedEntity: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackedEntity", required: false }] }], event: [{ type: i0.Input, args: [{ isSignal: true, alias: "event", required: false }] }], enrollment: [{ type: i0.Input, args: [{ isSignal: true, alias: "enrollment", required: false }] }], orgUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "orgUnit", required: false }] }], codeGeneratorOrgUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "codeGeneratorOrgUnit", required: false }] }], customOrgUnitRoots: [{ type: i0.Input, args: [{ isSignal: true, alias: "customOrgUnitRoots", required: false }] }], formMetaData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formMetaData", required: false }] }], collapsibleSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsibleSections", required: false }] }], preprocess: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocess", required: false }] }], instanceSave: [{
15698
16182
  type: Output
15699
16183
  }], instanceSaveCompleted: [{