@ng-forge/dynamic-forms 0.9.0-next.1 → 0.9.0-next.10

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.
Files changed (27) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CWxCWVdz.mjs → ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs} +3 -3
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CWxCWVdz.mjs.map → ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs.map} +1 -1
  3. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs → ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs} +3 -3
  4. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs.map → ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs.map} +1 -1
  5. package/fesm2022/{ng-forge-dynamic-forms-container-utils-DW_P7w-b.mjs → ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs} +2 -2
  6. package/fesm2022/{ng-forge-dynamic-forms-container-utils-DW_P7w-b.mjs.map → ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs.map} +1 -1
  7. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-DrtHSaVV.mjs → ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs} +2 -2
  8. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs.map +1 -0
  9. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-CSx0gwFc.mjs → ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs} +2 -2
  10. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-CSx0gwFc.mjs.map → ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs.map} +1 -1
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-2yRmHAUp.mjs → ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs} +3 -3
  12. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-2yRmHAUp.mjs.map → ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs.map} +1 -1
  13. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +629 -54
  14. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  15. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Ll4W2Z_n.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs} +662 -110
  16. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs.map +1 -0
  17. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs → ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs} +3 -3
  18. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs.map → ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs.map} +1 -1
  19. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs.map +1 -1
  20. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-rJMtKX2X.mjs → ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs} +2 -2
  21. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-rJMtKX2X.mjs.map → ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs.map} +1 -1
  22. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  23. package/package.json +1 -1
  24. package/types/ng-forge-dynamic-forms-integration.d.ts +380 -8
  25. package/types/ng-forge-dynamic-forms.d.ts +106 -8
  26. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-DrtHSaVV.mjs.map +0 -1
  27. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Ll4W2Z_n.mjs.map +0 -1
@@ -1,9 +1,12 @@
1
- import { inject, computed, signal, Injector, afterRenderEffect } from '@angular/core';
2
- import { FIELD_SIGNAL_CONTEXT, omit, buildBaseInputs, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, resolveNonFieldHidden, resolveNonFieldDisabled, RootFormRegistryService, ARRAY_CONTEXT, DynamicFormLogger, AppendArrayItemEvent, PrependArrayItemEvent, InsertArrayItemEvent, RemoveAtIndexEvent, PopArrayItemEvent, ShiftArrayItemEvent, FORM_OPTIONS, resolveSubmitButtonDisabled, resolveNextButtonDisabled, NextPageEvent, PreviousPageEvent, dynamicTextToObservable, interpolateParams, isEqual, applyMetaToElement } from '@ng-forge/dynamic-forms';
3
- export { isEqual, resolveNonFieldDisabled, resolveNonFieldHidden } from '@ng-forge/dynamic-forms';
1
+ import * as i0 from '@angular/core';
2
+ import { inject, computed, signal, Injector, isDevMode, afterRenderEffect, input, Directive, untracked, ElementRef, EnvironmentInjector, runInInjectionContext } from '@angular/core';
3
+ import { FIELD_SIGNAL_CONTEXT, omit, buildBaseInputs, DEFAULT_PROPS, resolveNonFieldHidden, resolveNonFieldDisabled, RootFormRegistryService, ARRAY_CONTEXT, DynamicFormLogger, AppendArrayItemEvent, PrependArrayItemEvent, InsertArrayItemEvent, RemoveAtIndexEvent, PopArrayItemEvent, ShiftArrayItemEvent, FORM_OPTIONS, resolveSubmitButtonDisabled, resolveNextButtonDisabled, NextPageEvent, PreviousPageEvent, dynamicTextToObservable, interpolateParams, isEqual, applyMetaToElement, DEFAULT_VALIDATION_MESSAGES, EventBus, resolveTokens } from '@ng-forge/dynamic-forms';
4
+ export { createWrappers, isEqual, isWrappersBundle, resolveNonFieldDisabled, resolveNonFieldHidden, wrapperProps } from '@ng-forge/dynamic-forms';
4
5
  import { toObservable, toSignal } from '@angular/core/rxjs-interop';
5
6
  import { combineLatest, of } from 'rxjs';
6
7
  import { switchMap, map } from 'rxjs/operators';
8
+ import { TestBed } from '@angular/core/testing';
9
+ import { form, schema } from '@angular/forms/signals';
7
10
 
8
11
  /**
9
12
  * Creates a value field context from the injected FIELD_SIGNAL_CONTEXT.
@@ -44,10 +47,9 @@ function resolveValueFieldContext() {
44
47
  * @param fieldDef The value field definition
45
48
  * @param ctx The resolved value field context
46
49
  * @param defaultProps Default props from the form configuration (may be undefined if not configured)
47
- * @param defaultValidationMessages Default validation messages from the form configuration (may be undefined if not configured)
48
50
  * @returns Record of input names to values
49
51
  */
50
- function buildValueFieldInputs(fieldDef, ctx, defaultProps, defaultValidationMessages) {
52
+ function buildValueFieldInputs(fieldDef, ctx, defaultProps) {
51
53
  const omittedFields = omit(fieldDef, ['value']);
52
54
  const baseInputs = buildBaseInputs(omittedFields, defaultProps);
53
55
  const inputs = {
@@ -57,9 +59,6 @@ function buildValueFieldInputs(fieldDef, ctx, defaultProps, defaultValidationMes
57
59
  if (fieldDef.placeholder !== undefined) {
58
60
  inputs['placeholder'] = fieldDef.placeholder;
59
61
  }
60
- if (defaultValidationMessages !== undefined) {
61
- inputs['defaultValidationMessages'] = defaultValidationMessages;
62
- }
63
62
  // Resolve field tree reactively - this call is inside a computed,
64
63
  // so it establishes the reactive dependency correctly
65
64
  const fieldTree = ctx.getFieldTree(fieldDef.key);
@@ -92,13 +91,12 @@ function valueFieldMapper(fieldDef) {
92
91
  // Get the context once at injection time (captures the FIELD_SIGNAL_CONTEXT reference)
93
92
  const ctx = resolveValueFieldContext();
94
93
  const defaultProps = inject(DEFAULT_PROPS);
95
- const defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES);
96
94
  // The computed calls ctx.getFieldTree(fieldDef.key) inside, which:
97
95
  // 1. Accesses context.form (triggering the getter for array items)
98
96
  // 2. Returns the field tree if available, or undefined if not yet
99
97
  // 3. Re-runs when the form structure updates (reactive dependency)
100
98
  return computed(() => {
101
- return buildValueFieldInputs(fieldDef, ctx, defaultProps(), defaultValidationMessages());
99
+ return buildValueFieldInputs(fieldDef, ctx, defaultProps());
102
100
  });
103
101
  }
104
102
 
@@ -114,11 +112,9 @@ function valueFieldMapper(fieldDef) {
114
112
  function checkboxFieldMapper(fieldDef) {
115
113
  const context = inject(FIELD_SIGNAL_CONTEXT);
116
114
  const defaultProps = inject(DEFAULT_PROPS);
117
- const defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES);
118
115
  return computed(() => {
119
116
  const omittedFields = omit(fieldDef, ['value']);
120
117
  const baseInputs = buildBaseInputs(omittedFields, defaultProps());
121
- const validationMessages = defaultValidationMessages();
122
118
  const inputs = {
123
119
  ...baseInputs,
124
120
  validationMessages: fieldDef.validationMessages ?? {},
@@ -126,9 +122,6 @@ function checkboxFieldMapper(fieldDef) {
126
122
  if (fieldDef.placeholder !== undefined) {
127
123
  inputs['placeholder'] = fieldDef.placeholder;
128
124
  }
129
- if (validationMessages !== undefined) {
130
- inputs['defaultValidationMessages'] = validationMessages;
131
- }
132
125
  // Access form inside computed for reactivity and to handle cases where
133
126
  // form may not be immediately available (e.g., during array item initialization)
134
127
  const formRoot = context.form;
@@ -172,9 +165,8 @@ function toDate(value) {
172
165
  function datepickerFieldMapper(fieldDef) {
173
166
  const ctx = resolveValueFieldContext();
174
167
  const defaultProps = inject(DEFAULT_PROPS);
175
- const defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES);
176
168
  return computed(() => {
177
- const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps(), defaultValidationMessages());
169
+ const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps());
178
170
  // Add datepicker-specific properties, converting strings to Date objects
179
171
  if (fieldDef.minDate !== undefined) {
180
172
  inputs['minDate'] = toDate(fieldDef.minDate);
@@ -200,10 +192,9 @@ function datepickerFieldMapper(fieldDef) {
200
192
  function optionsFieldMapper(fieldDef) {
201
193
  const ctx = resolveValueFieldContext();
202
194
  const defaultProps = inject(DEFAULT_PROPS);
203
- const defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES);
204
195
  return computed(() => {
205
196
  // Cast to BaseValueField - safe because all FieldWithOptions types extend BaseValueField
206
- const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps(), defaultValidationMessages());
197
+ const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps());
207
198
  // Add options property
208
199
  inputs['options'] = fieldDef.options;
209
200
  return inputs;
@@ -224,9 +215,8 @@ function optionsFieldMapper(fieldDef) {
224
215
  function sliderFieldMapper(fieldDef) {
225
216
  const ctx = resolveValueFieldContext();
226
217
  const defaultProps = inject(DEFAULT_PROPS);
227
- const defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES);
228
218
  return computed(() => {
229
- const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps(), defaultValidationMessages());
219
+ const inputs = buildValueFieldInputs(fieldDef, ctx, defaultProps());
230
220
  // Add slider-specific properties (these are at field level, not in props)
231
221
  if (fieldDef.minValue !== undefined) {
232
222
  inputs['minValue'] = fieldDef.minValue;
@@ -730,6 +720,59 @@ function shouldShowErrors(field) {
730
720
  });
731
721
  }
732
722
 
723
+ /**
724
+ * Resolve target set, compute combined attrs, apply via applyMetaToElement.
725
+ * Target resolution is cached by selector identity — re-resolves only on
726
+ * selector change or `dependents` change. afterRenderEffect is signal-tracked,
727
+ * so the callback re-runs only when computeAttrs / dependents differ.
728
+ * @internal
729
+ */
730
+ function applyTrackedAttrsToTargets(elementRef, computeAttrs, options = {}) {
731
+ const appliedByElement = new WeakMap();
732
+ let cachedSelector;
733
+ let cachedTargets = [];
734
+ let previousAttrs;
735
+ let previousDeps;
736
+ const warnedSelectors = isDevMode() ? new Set() : undefined;
737
+ afterRenderEffect({
738
+ write: () => {
739
+ // Read dependents first so afterRenderEffect tracks them.
740
+ const currentDeps = options.dependents?.map((d) => d());
741
+ const attrs = computeAttrs();
742
+ const selector = (typeof options.selector === 'function' ? options.selector() : options.selector)?.trim() || undefined;
743
+ const selectorChanged = selector !== cachedSelector;
744
+ const depsChanged = !!currentDeps &&
745
+ (!previousDeps || currentDeps.length !== previousDeps.length || currentDeps.some((d, i) => d !== previousDeps[i]));
746
+ if (selectorChanged || depsChanged || cachedTargets.length === 0) {
747
+ cachedSelector = selector;
748
+ cachedTargets = selector ? Array.from(elementRef.nativeElement.querySelectorAll(selector)) : [elementRef.nativeElement];
749
+ }
750
+ previousDeps = currentDeps;
751
+ // Dev-mode safety net: selector typo or wrong descendant query leaves
752
+ // meta + aria unapplied. Warn the first time we'd apply non-empty attrs
753
+ // to zero matches for a given selector. Re-warns only when the selector
754
+ // string changes (so authors fixing one typo see the next).
755
+ if (warnedSelectors && selector && cachedTargets.length === 0 && attrs && Object.keys(attrs).length > 0) {
756
+ if (!warnedSelectors.has(selector)) {
757
+ warnedSelectors.add(selector);
758
+ const tag = elementRef.nativeElement.tagName.toLowerCase();
759
+ console.warn(`[Dynamic Forms] ngForgeControl selector "${selector}" matched no descendants of <${tag}> — meta + aria attributes will not be applied. ` +
760
+ `Check the selector for typos, or supply 'dependents' if the target is rendered after a signal emits.`);
761
+ }
762
+ }
763
+ // Bail when attrs unchanged AND nothing structural moved — keeps repeated
764
+ // effect runs (driven by an unrelated tracked-signal flicker) cheap.
765
+ if (!selectorChanged && !depsChanged && isEqual(attrs, previousAttrs)) {
766
+ return;
767
+ }
768
+ previousAttrs = attrs;
769
+ for (const el of cachedTargets) {
770
+ const applied = appliedByElement.get(el) ?? new Set();
771
+ appliedByElement.set(el, applyMetaToElement(el, attrs, applied));
772
+ }
773
+ },
774
+ });
775
+ }
733
776
  /**
734
777
  * Sets up automatic meta attribute tracking and application for wrapped components.
735
778
  *
@@ -768,43 +811,575 @@ function shouldShowErrors(field) {
768
811
  * ```
769
812
  */
770
813
  function setupMetaTracking(elementRef, meta, options) {
771
- let appliedAttributes = new Set();
772
- let previousMeta;
773
- let previousDeps;
774
- afterRenderEffect({
775
- write: () => {
776
- // Read dependents to establish signal tracking and collect their values
777
- const currentDeps = options?.dependents?.map((dep) => dep());
778
- const currentMeta = meta();
779
- const hostElement = elementRef.nativeElement;
780
- // Early exit if nothing changed (using deep equality for meta)
781
- const metaChanged = !isEqual(currentMeta, previousMeta);
782
- const depsChanged = !previousDeps || currentDeps?.length !== previousDeps.length || currentDeps?.some((d, i) => d !== previousDeps[i]);
783
- if (!metaChanged && !depsChanged) {
784
- return;
785
- }
786
- previousMeta = currentMeta;
787
- previousDeps = currentDeps;
788
- if (options?.selector) {
789
- // Apply to elements matching selector
790
- const elements = hostElement.querySelectorAll(options.selector);
791
- const allApplied = new Set();
792
- elements.forEach((el) => {
793
- const applied = applyMetaToElement(el, currentMeta, appliedAttributes);
794
- applied.forEach((attr) => allApplied.add(attr));
795
- });
796
- appliedAttributes = allApplied;
797
- }
798
- else {
799
- // Apply to host element (for Shadow DOM components)
800
- appliedAttributes = applyMetaToElement(hostElement, currentMeta, appliedAttributes);
801
- }
802
- },
814
+ applyTrackedAttrsToTargets(elementRef, () => meta(), { selector: options?.selector, dependents: options?.dependents });
815
+ }
816
+ /**
817
+ * Marker-directive variant: writes parent meta + aria to resolved targets.
818
+ * Parent aria wins on key collision. Internal to NgForgeControl/HostControl.
819
+ * @internal
820
+ */
821
+ function setupMarkerAttrTracking(elementRef, parent, selector) {
822
+ applyTrackedAttrsToTargets(elementRef, () => {
823
+ const ariaRequired = parent.ariaRequired();
824
+ const ariaDescribedBy = parent.ariaDescribedBy();
825
+ // Skip null aria-required/describedby so applyMetaToElement removes the
826
+ // attr (rather than serializing "null") on transitions.
827
+ return {
828
+ ...(parent.meta() ?? {}),
829
+ 'aria-invalid': parent.ariaInvalid(),
830
+ ...(ariaRequired === null ? {} : { 'aria-required': ariaRequired }),
831
+ ...(ariaDescribedBy === null ? {} : { 'aria-describedby': ariaDescribedBy }),
832
+ };
833
+ }, { selector });
834
+ }
835
+
836
+ /**
837
+ * Creates a signal that computes the aria-describedby value based on errors and hint state.
838
+ * Errors take precedence over hints - when errors are displayed, the hint is hidden.
839
+ * Only the first error is displayed (single error ID, not indexed).
840
+ *
841
+ * @param errorsToDisplay Signal containing the array of errors currently being displayed
842
+ * @param errorId Signal containing the ID for the error element (single error only)
843
+ * @param hintId Signal containing the ID for the hint element
844
+ * @param hasHint Function that returns true if a hint is configured
845
+ * @returns Signal containing the aria-describedby value or null
846
+ */
847
+ function createAriaDescribedBySignal(errorsToDisplay, errorId, hintId, hasHint) {
848
+ return computed(() => {
849
+ if (errorsToDisplay().length > 0) {
850
+ return errorId();
851
+ }
852
+ if (hasHint()) {
853
+ return hintId();
854
+ }
855
+ return null;
803
856
  });
804
857
  }
805
858
 
806
859
  // Error display utilities for field components
807
860
 
861
+ /**
862
+ * Base directive composed into every ng-forge component via `hostDirectives`.
863
+ * Owns the universal identity inputs (`key`, `className`) and the host
864
+ * bindings that every field shape — value-bearing, action, future container —
865
+ * shares: `[id]`, `[attr.data-testid]`, `[class]`.
866
+ *
867
+ * Shape-specific add-ons (`NgForgeField` for value fields, `NgForgeAction`
868
+ * for actions) inject this directive and read `key()` / `className()` from
869
+ * it. Source-of-truth concerns like `hidden` / `disabled` live on the add-on
870
+ * because they vary by shape (value fields derive from `field()()`; actions
871
+ * receive direct inputs).
872
+ *
873
+ * Selectorless — usage is exclusively via `hostDirectives`. Prefer the
874
+ * `NG_FORGE_FIELD` / `NG_FORGE_ACTION` presets rather than composing Shell
875
+ * directly.
876
+ */
877
+ class NgForgeFieldShell {
878
+ key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
879
+ className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
880
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeFieldShell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
881
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: NgForgeFieldShell, isStandalone: true, inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "key()", "attr.data-testid": "key()", "class": "className()" } }, ngImport: i0 });
882
+ }
883
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeFieldShell, decorators: [{
884
+ type: Directive,
885
+ args: [{
886
+ host: {
887
+ '[id]': 'key()',
888
+ '[attr.data-testid]': 'key()',
889
+ '[class]': 'className()',
890
+ },
891
+ }]
892
+ }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }] } });
893
+ /** Input names forwarded onto `NgForgeFieldShell` via `hostDirectives`. */
894
+ const NG_FORGE_FIELD_SHELL_INPUTS = ['key', 'className'];
895
+
896
+ /**
897
+ * Value-bearing add-on directive — composed alongside `NgForgeFieldShell` via
898
+ * the `NG_FORGE_FIELD` preset. Owns the value/validation/aria plumbing every
899
+ * value-bearing field needs.
900
+ *
901
+ * The Shell owns universal identity (`key`, `className`, `[id]`,
902
+ * `[attr.data-testid]`, `[class]`); this directive injects Shell and adds
903
+ * field-driven bindings (`[attr.hidden]`, `[attr.aria-disabled]`), the
904
+ * standard value-field inputs (`field`, `label`, `placeholder`, `tabIndex`,
905
+ * `props`, `meta`, `validationMessages`), the derived error/aria signals,
906
+ * and the meta-tracking claim contract.
907
+ *
908
+ * @example
909
+ * ```ts
910
+ * \@Component({
911
+ * hostDirectives: NG_FORGE_FIELD,
912
+ * imports: [NgForgeControl, FormField],
913
+ * template: `
914
+ * <label [for]="ngf.key() + '-input'">{{ ngf.label() | dynamicText | async }}</label>
915
+ * <input ngForgeControl [id]="ngf.key() + '-input'" [formField]="ngf.field()" />
916
+ * @if (ngf.errorsToDisplay()[0]; as e) {
917
+ * <span [id]="ngf.errorId()">{{ e.message }}</span>
918
+ * }
919
+ * `,
920
+ * })
921
+ * export class MyInputField {
922
+ * protected readonly ngf = injectNgForgeField<string>();
923
+ * }
924
+ * ```
925
+ *
926
+ * Selectorless — usage is exclusively via `hostDirectives`. Prefer the
927
+ * `NG_FORGE_FIELD` preset over composing this directive manually.
928
+ */
929
+ class NgForgeField {
930
+ // ───────────────────────────────────────────────────────────────────────────
931
+ // Shell-owned identity — re-exported so component templates keep using
932
+ // `ngf.key()` / `ngf.className()` without reaching for the Shell directly.
933
+ // ───────────────────────────────────────────────────────────────────────────
934
+ shell = inject(NgForgeFieldShell);
935
+ /** Shell-owned key, re-exported as plain `Signal<string>` so it doesn't get flagged by the host-directive input-lockstep assertion. */
936
+ key = this.shell.key;
937
+ /** Shell-owned className, re-exported as plain `Signal<string>` for the same reason. */
938
+ className = this.shell.className;
939
+ // ───────────────────────────────────────────────────────────────────────────
940
+ // Forwarded inputs — the value-field-specific subset of mapper output.
941
+ // ───────────────────────────────────────────────────────────────────────────
942
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
943
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
944
+ placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
945
+ tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
946
+ props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
947
+ meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
948
+ validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
949
+ // ───────────────────────────────────────────────────────────────────────────
950
+ // Wiring
951
+ // ───────────────────────────────────────────────────────────────────────────
952
+ defaultValidationMessages = inject(DEFAULT_VALIDATION_MESSAGES, { optional: true }) ?? signal(undefined);
953
+ // ───────────────────────────────────────────────────────────────────────────
954
+ // Error display
955
+ // ───────────────────────────────────────────────────────────────────────────
956
+ errors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
957
+ showErrors = shouldShowErrors(this.field);
958
+ errorsToDisplay = computed(() => (this.showErrors() ? this.errors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
959
+ // ───────────────────────────────────────────────────────────────────────────
960
+ // Accessibility helpers
961
+ // ───────────────────────────────────────────────────────────────────────────
962
+ errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
963
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
964
+ ariaInvalid = computed(() => {
965
+ const state = this.field()();
966
+ return state.invalid() && state.touched();
967
+ }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
968
+ ariaRequired = computed(() => {
969
+ return this.field()().required?.() === true ? true : null;
970
+ }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
971
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => Boolean(this.props()?.hint));
972
+ // ───────────────────────────────────────────────────────────────────────────
973
+ // Claim tracking — markers and ambient sub-components flip this to true so
974
+ // dev-mode can warn when meta() is non-empty but nothing consumed it.
975
+ // ───────────────────────────────────────────────────────────────────────────
976
+ _claimed = signal(false, ...(ngDevMode ? [{ debugName: "_claimed" }] : /* istanbul ignore next */ []));
977
+ /** @internal Marker directives + ambient sub-components call this on construction. */
978
+ markClaimed() {
979
+ this._claimed.set(true);
980
+ }
981
+ logger = inject(DynamicFormLogger, { optional: true });
982
+ constructor() {
983
+ // Dev-mode safety net: a mapper or template-author binds meta() but no
984
+ // NgForgeControl / NgForgeHostControl / ambient sub-component is in
985
+ // place to consume it. Fires at most once per directive instance.
986
+ if (isDevMode()) {
987
+ let warned = false;
988
+ afterRenderEffect({
989
+ write: () => {
990
+ if (warned)
991
+ return;
992
+ const meta = this.meta();
993
+ if (!meta || Object.keys(meta).length === 0)
994
+ return;
995
+ if (untracked(this._claimed))
996
+ return;
997
+ warned = true;
998
+ // `untracked` safe — afterRenderEffect.write runs after first CD, so Shell's required `key` is bound (no NG0950).
999
+ const key = untracked(this.key);
1000
+ const message = `NgForgeField - meta() provided for field "${key}" but no NgForgeControl / NgForgeHostControl / ` +
1001
+ `ambient sub-component claimed it. Meta will not be applied. ` +
1002
+ `See https://ng-forge.dev/building-an-adapter#meta-forwarding`;
1003
+ if (this.logger) {
1004
+ this.logger.warn(message);
1005
+ }
1006
+ else {
1007
+ console.warn(message);
1008
+ }
1009
+ },
1010
+ });
1011
+ }
1012
+ }
1013
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1014
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: NgForgeField, isStandalone: true, inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.hidden": "field()().hidden() || null", "attr.aria-disabled": "field()().disabled() || null" } }, ngImport: i0 });
1015
+ }
1016
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeField, decorators: [{
1017
+ type: Directive,
1018
+ args: [{
1019
+ host: {
1020
+ '[attr.hidden]': 'field()().hidden() || null',
1021
+ '[attr.aria-disabled]': 'field()().disabled() || null',
1022
+ },
1023
+ }]
1024
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }] } });
1025
+ /**
1026
+ * Inputs forwarded onto `NgForgeField` via `hostDirectives`. Excludes
1027
+ * `key` / `className` (forwarded to `NgForgeFieldShell` separately via
1028
+ * `NG_FORGE_FIELD_SHELL_INPUTS`). Most consumers shouldn't reference this
1029
+ * directly — use the `NG_FORGE_FIELD` preset.
1030
+ */
1031
+ const NG_FORGE_VALUE_FIELD_INPUTS = ['field', 'label', 'placeholder', 'tabIndex', 'props', 'meta', 'validationMessages'];
1032
+ // Compile-time lockstep: NG_FORGE_VALUE_FIELD_INPUTS must equal the declared
1033
+ // `input()` properties on NgForgeField. Drift in either direction fails the build.
1034
+ const _NG_FORGE_VALUE_FIELD_INPUTS_LOCKSTEP = true;
1035
+ void _NG_FORGE_VALUE_FIELD_INPUTS_LOCKSTEP;
1036
+ /**
1037
+ * Typed wrapper around `inject(NgForgeField)`. The generic narrows the
1038
+ * `field` signal to `Signal<FieldTree<T>>` for the calling component's value
1039
+ * type. The cast is unchecked — the runtime contract is that mappers only
1040
+ * forward a `field` whose value type matches the field-type definition.
1041
+ *
1042
+ * @example
1043
+ * ```ts
1044
+ * protected readonly ngf = injectNgForgeField<string>();
1045
+ * // ngf.field() is Signal<FieldTree<string>>; ngf.errors() etc. unchanged
1046
+ * ```
1047
+ */
1048
+ function injectNgForgeField() {
1049
+ return inject(NgForgeField);
1050
+ }
1051
+
1052
+ /**
1053
+ * Action add-on directive — composed alongside `NgForgeFieldShell` via the
1054
+ * `NG_FORGE_ACTION` preset. Owns the inputs + dispatch logic every button /
1055
+ * action component needs (label, disabled, event, eventArgs, …) plus the
1056
+ * `[attr.hidden]` / `[attr.aria-disabled]` host bindings driven by its own
1057
+ * inputs (no field tree).
1058
+ *
1059
+ * Components call `action.dispatch()` from their click handler; this
1060
+ * directive resolves any `eventArgs` tokens via the optional `ARRAY_CONTEXT`
1061
+ * and dispatches the configured `event` through the `EventBus`.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * \@Component({
1066
+ * hostDirectives: NG_FORGE_ACTION,
1067
+ * template: `<button (click)="action.dispatch()" [disabled]="action.disabled()">{{ action.label() }}</button>`,
1068
+ * })
1069
+ * export class MyButton {
1070
+ * protected readonly action = inject(NgForgeAction);
1071
+ * }
1072
+ * ```
1073
+ *
1074
+ * Selectorless — usage is exclusively via `hostDirectives`. Prefer the
1075
+ * `NG_FORGE_ACTION` preset over composing this directive manually.
1076
+ */
1077
+ class NgForgeAction {
1078
+ // ───────────────────────────────────────────────────────────────────────────
1079
+ // Shell-owned identity — re-exported as plain `Signal<T>` so they don't get
1080
+ // flagged by the lockstep assertion below.
1081
+ // ───────────────────────────────────────────────────────────────────────────
1082
+ shell = inject(NgForgeFieldShell);
1083
+ key = this.shell.key;
1084
+ className = this.shell.className;
1085
+ // ───────────────────────────────────────────────────────────────────────────
1086
+ // Forwarded inputs — the action-specific subset of mapper output.
1087
+ // ───────────────────────────────────────────────────────────────────────────
1088
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1089
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
1090
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
1091
+ tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1092
+ event = input(...(ngDevMode ? [undefined, { debugName: "event" }] : /* istanbul ignore next */ []));
1093
+ eventArgs = input(...(ngDevMode ? [undefined, { debugName: "eventArgs" }] : /* istanbul ignore next */ []));
1094
+ eventContext = input(...(ngDevMode ? [undefined, { debugName: "eventContext" }] : /* istanbul ignore next */ []));
1095
+ props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1096
+ // ───────────────────────────────────────────────────────────────────────────
1097
+ // Dispatch
1098
+ // ───────────────────────────────────────────────────────────────────────────
1099
+ eventBus = inject(EventBus);
1100
+ arrayContext = inject(ARRAY_CONTEXT, { optional: true });
1101
+ /**
1102
+ * Dispatch the configured event through the EventBus. Resolves `eventArgs`
1103
+ * tokens (`$key`, `$index`, `$arrayKey`, `formValue`, …) against the
1104
+ * ambient array context if present, falling back to a static `eventContext`
1105
+ * input or the field's own `key` otherwise. No-op when `event` is unset
1106
+ * (e.g. submit buttons handled by the native form lifecycle).
1107
+ */
1108
+ dispatch() {
1109
+ const event = this.event();
1110
+ if (!event)
1111
+ return;
1112
+ const args = this.eventArgs();
1113
+ if (!args || args.length === 0) {
1114
+ this.eventBus.dispatch(event);
1115
+ return;
1116
+ }
1117
+ const context = this.arrayContext
1118
+ ? {
1119
+ key: this.key(),
1120
+ index: this.arrayContext.index(),
1121
+ arrayKey: this.arrayContext.arrayKey,
1122
+ formValue: this.arrayContext.formValue,
1123
+ }
1124
+ : (this.eventContext() ?? { key: this.key() });
1125
+ this.eventBus.dispatch(event, ...resolveTokens(args, context));
1126
+ }
1127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1128
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: NgForgeAction, isStandalone: true, inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, eventArgs: { classPropertyName: "eventArgs", publicName: "eventArgs", isSignal: true, isRequired: false, transformFunction: null }, eventContext: { classPropertyName: "eventContext", publicName: "eventContext", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.hidden": "hidden() || null", "attr.aria-disabled": "disabled() || null" } }, ngImport: i0 });
1129
+ }
1130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeAction, decorators: [{
1131
+ type: Directive,
1132
+ args: [{
1133
+ host: {
1134
+ '[attr.hidden]': 'hidden() || null',
1135
+ '[attr.aria-disabled]': 'disabled() || null',
1136
+ },
1137
+ }]
1138
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], event: [{ type: i0.Input, args: [{ isSignal: true, alias: "event", required: false }] }], eventArgs: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventArgs", required: false }] }], eventContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventContext", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1139
+ /**
1140
+ * Inputs forwarded onto `NgForgeAction` via `hostDirectives`. Excludes
1141
+ * `key` / `className` (forwarded to `NgForgeFieldShell` separately via
1142
+ * `NG_FORGE_FIELD_SHELL_INPUTS`). Most consumers shouldn't reference this
1143
+ * directly — use the `NG_FORGE_ACTION` preset.
1144
+ */
1145
+ const NG_FORGE_ACTION_INPUTS = ['label', 'disabled', 'hidden', 'tabIndex', 'event', 'eventArgs', 'eventContext', 'props'];
1146
+ // Compile-time lockstep: NG_FORGE_ACTION_INPUTS must equal the declared
1147
+ // `input()` properties on NgForgeAction. Drift fails the build.
1148
+ const _NG_FORGE_ACTION_INPUTS_LOCKSTEP = true;
1149
+ void _NG_FORGE_ACTION_INPUTS_LOCKSTEP;
1150
+ /**
1151
+ * Typed wrapper around `inject(NgForgeAction)`. The generic narrows the
1152
+ * `event` input to a specific FormEvent subclass.
1153
+ *
1154
+ * @example
1155
+ * ```ts
1156
+ * protected readonly action = injectNgForgeAction<SubmitEvent>();
1157
+ * ```
1158
+ */
1159
+ function injectNgForgeAction() {
1160
+ return inject(NgForgeAction);
1161
+ }
1162
+
1163
+ /**
1164
+ * Marker directive applied to the canonical control element of an
1165
+ * `NgForgeField`-using component. Forwards the field's `meta` AND
1166
+ * parent-derived aria signals (`aria-invalid`, `aria-required`,
1167
+ * `aria-describedby`) onto a target element.
1168
+ *
1169
+ * By default attributes land on the directive's own host. For wrapped
1170
+ * controls (e.g. `<mat-checkbox>`, `<p-toggleSwitch>`) where the canonical
1171
+ * native input is rendered as a descendant inside the wrapper, pass a CSS
1172
+ * selector through the input alias — the directive caches the resolved
1173
+ * descendant set and re-resolves only when the selector changes.
1174
+ *
1175
+ * @example Bare native control
1176
+ * ```html
1177
+ * <input ngForgeControl [formField]="ngf.field()" />
1178
+ * ```
1179
+ *
1180
+ * @example Wrapped control with inner native input
1181
+ * ```html
1182
+ * <mat-checkbox ngForgeControl="input[type='checkbox']" [formField]="ngf.field()">
1183
+ * {{ ngf.label() }}
1184
+ * </mat-checkbox>
1185
+ * ```
1186
+ *
1187
+ * @example Dynamic option list (radios rendered in @for)
1188
+ * ```html
1189
+ * @for (option of options(); track option.value) {
1190
+ * <input type="radio" ngForgeControl [value]="option.value" />
1191
+ * }
1192
+ * ```
1193
+ *
1194
+ * Each `@for` iteration creates its own directive instance; Angular's
1195
+ * structural lifecycle handles add/remove naturally.
1196
+ */
1197
+ class NgForgeControl {
1198
+ target = input(undefined, { ...(ngDevMode ? { debugName: "target" } : /* istanbul ignore next */ {}), alias: 'ngForgeControl' });
1199
+ constructor() {
1200
+ const parent = inject(NgForgeField);
1201
+ setupMarkerAttrTracking(inject(ElementRef), parent, () => this.target());
1202
+ parent.markClaimed();
1203
+ }
1204
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1205
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: NgForgeControl, isStandalone: true, selector: "[ngForgeControl]", inputs: { target: { classPropertyName: "target", publicName: "ngForgeControl", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1206
+ }
1207
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeControl, decorators: [{
1208
+ type: Directive,
1209
+ args: [{ selector: '[ngForgeControl]' }]
1210
+ }], ctorParameters: () => [], propDecorators: { target: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngForgeControl", required: false }] }] } });
1211
+ /**
1212
+ * Selectorless companion directive for shadow-DOM-encapsulated components
1213
+ * (PrimeNG `p-select`, Ionic web components, etc.) where no light-DOM child
1214
+ * is the canonical control. Added to the component's `hostDirectives` so the
1215
+ * field's `meta` + parent aria land on the component's own host element.
1216
+ *
1217
+ * For split parent/control components (e.g. `df-prime-select` rendering
1218
+ * `df-prime-select-control` internally), place `NgForgeHostControl` on the
1219
+ * INNER control component's `hostDirectives` — the inner element is the
1220
+ * canonical control, and the marker's `inject(NgForgeField)` walks up the
1221
+ * element-injector tree to find the parent's directive instance.
1222
+ *
1223
+ * @example
1224
+ * ```ts
1225
+ * \@Component({
1226
+ * hostDirectives: [
1227
+ * { directive: NgForgeField, inputs: [...NG_FORGE_FIELD_INPUTS] },
1228
+ * NgForgeHostControl,
1229
+ * ],
1230
+ * template: `...`,
1231
+ * })
1232
+ * export class IonicInputField { ... }
1233
+ * ```
1234
+ */
1235
+ // Empty `@Directive({})` is intentional — selectorless; used only via `hostDirectives`.
1236
+ class NgForgeHostControl {
1237
+ constructor() {
1238
+ const parent = inject(NgForgeField);
1239
+ setupMarkerAttrTracking(inject(ElementRef), parent, () => undefined);
1240
+ parent.markClaimed();
1241
+ }
1242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeHostControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1243
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NgForgeHostControl, isStandalone: true, ngImport: i0 });
1244
+ }
1245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeHostControl, decorators: [{
1246
+ type: Directive,
1247
+ args: [{}]
1248
+ }], ctorParameters: () => [] });
1249
+
1250
+ /**
1251
+ * Value-bearing field host wrapper. Composes `NgForgeFieldShell` (universal
1252
+ * identity) with `NgForgeField` (value + validation + aria + meta-tracking)
1253
+ * in a single host-directive entry, so consumer components compose just one
1254
+ * directive instead of two.
1255
+ *
1256
+ * Why a directive instead of a `const` preset: cross-package partial
1257
+ * compilation can't resolve a const reference inside `hostDirectives:`, so
1258
+ * an exported const array doesn't work for library consumers. A wrapper
1259
+ * directive's own `hostDirectives` IS a literal at compile time (in this
1260
+ * file), so it works everywhere.
1261
+ *
1262
+ * Authoring shape for adapter components:
1263
+ * ```ts
1264
+ * \@Component({
1265
+ * hostDirectives: [NgForgeFieldHost],
1266
+ * ...
1267
+ * })
1268
+ * export class MyInputField {
1269
+ * protected readonly ngf = injectNgForgeField<string>();
1270
+ * }
1271
+ * ```
1272
+ *
1273
+ * Pattern reference: Taiga UI uses the same wrapper-directive technique
1274
+ * for the same reason. See https://www.angularspace.com/host-directives-decomposition-unleashed/
1275
+ */
1276
+ class NgForgeFieldHost {
1277
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeFieldHost, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1278
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NgForgeFieldHost, isStandalone: true, hostDirectives: [{ directive: NgForgeFieldShell, inputs: ["key", "key", "className", "className"] }, { directive: NgForgeField, inputs: ["field", "field", "label", "label", "placeholder", "placeholder", "tabIndex", "tabIndex", "props", "props", "meta", "meta", "validationMessages", "validationMessages"] }], ngImport: i0 });
1279
+ }
1280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeFieldHost, decorators: [{
1281
+ type: Directive,
1282
+ args: [{
1283
+ hostDirectives: [
1284
+ { directive: NgForgeFieldShell, inputs: [...NG_FORGE_FIELD_SHELL_INPUTS] },
1285
+ { directive: NgForgeField, inputs: [...NG_FORGE_VALUE_FIELD_INPUTS] },
1286
+ ],
1287
+ }]
1288
+ }] });
1289
+ /**
1290
+ * Action / button host wrapper. Composes `NgForgeFieldShell` with
1291
+ * `NgForgeAction` (event dispatch + aria-disabled) for button-shaped
1292
+ * components.
1293
+ *
1294
+ * Authoring shape:
1295
+ * ```ts
1296
+ * \@Component({
1297
+ * hostDirectives: [NgForgeActionHost],
1298
+ * ...
1299
+ * })
1300
+ * export class MyButton {
1301
+ * protected readonly action = injectNgForgeAction();
1302
+ * }
1303
+ * ```
1304
+ */
1305
+ class NgForgeActionHost {
1306
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeActionHost, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1307
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NgForgeActionHost, isStandalone: true, hostDirectives: [{ directive: NgForgeFieldShell, inputs: ["key", "key", "className", "className"] }, { directive: NgForgeAction, inputs: ["label", "label", "disabled", "disabled", "hidden", "hidden", "tabIndex", "tabIndex", "event", "event", "eventArgs", "eventArgs", "eventContext", "eventContext", "props", "props"] }], ngImport: i0 });
1308
+ }
1309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NgForgeActionHost, decorators: [{
1310
+ type: Directive,
1311
+ args: [{
1312
+ hostDirectives: [
1313
+ { directive: NgForgeFieldShell, inputs: [...NG_FORGE_FIELD_SHELL_INPUTS] },
1314
+ { directive: NgForgeAction, inputs: [...NG_FORGE_ACTION_INPUTS] },
1315
+ ],
1316
+ }]
1317
+ }] });
1318
+
1319
+ /**
1320
+ * Builds a `ComponentFixture` for an `NgForgeField`-composing component with
1321
+ * `field` + `key` bound BEFORE `detectChanges()` — sidesteps the NG0950 that
1322
+ * Shell's required `key` and Field's required `field` would otherwise throw.
1323
+ *
1324
+ * The caller drives `detectChanges()` themselves (so they can `setInput`
1325
+ * additional component-specific props in between).
1326
+ */
1327
+ function createNgForgeFieldFixture(component, options) {
1328
+ TestBed.configureTestingModule({ imports: [component], providers: [...(options.providers ?? [])] });
1329
+ const key = options.key;
1330
+ const rootValue = signal({ [key]: options.value }, ...(ngDevMode ? [{ debugName: "rootValue" }] : /* istanbul ignore next */ []));
1331
+ const injector = TestBed.inject(EnvironmentInjector);
1332
+ const schemaCallback = options.schema;
1333
+ const root = runInInjectionContext(injector, () => {
1334
+ if (!schemaCallback)
1335
+ return form(rootValue);
1336
+ return form(rootValue, schema((path) => {
1337
+ schemaCallback(path[key]);
1338
+ }));
1339
+ });
1340
+ const field = root[key];
1341
+ if (options.touched)
1342
+ field().markAsTouched();
1343
+ const fixture = TestBed.createComponent(component);
1344
+ fixture.componentRef.setInput('field', field);
1345
+ fixture.componentRef.setInput('key', key);
1346
+ if (options.inputs) {
1347
+ for (const [name, value] of Object.entries(options.inputs)) {
1348
+ fixture.componentRef.setInput(name, value);
1349
+ }
1350
+ }
1351
+ return { fixture, field, rootValue };
1352
+ }
1353
+ /**
1354
+ * Builds a `ComponentFixture` for an `NgForgeAction`-composing component with
1355
+ * `key` + `label` bound and `EventBus` provided. The caller calls
1356
+ * `detectChanges()`; the harness handles the NG0950-avoidance bindings.
1357
+ */
1358
+ function createNgForgeActionFixture(component, options) {
1359
+ TestBed.configureTestingModule({ imports: [component], providers: [EventBus, ...(options.providers ?? [])] });
1360
+ const fixture = TestBed.createComponent(component);
1361
+ fixture.componentRef.setInput('key', options.key);
1362
+ fixture.componentRef.setInput('label', options.label);
1363
+ if (options.event)
1364
+ fixture.componentRef.setInput('event', options.event);
1365
+ if (options.eventArgs)
1366
+ fixture.componentRef.setInput('eventArgs', options.eventArgs);
1367
+ if (options.disabled !== undefined)
1368
+ fixture.componentRef.setInput('disabled', options.disabled);
1369
+ if (options.hidden !== undefined)
1370
+ fixture.componentRef.setInput('hidden', options.hidden);
1371
+ if (options.inputs) {
1372
+ for (const [name, value] of Object.entries(options.inputs)) {
1373
+ fixture.componentRef.setInput(name, value);
1374
+ }
1375
+ }
1376
+ return { fixture, eventBus: TestBed.inject(EventBus) };
1377
+ }
1378
+ /** Provider for `DEFAULT_VALIDATION_MESSAGES` in field tests — supply the default-messages map and the directive resolves it from DI. */
1379
+ function provideTestValidationMessages(messages) {
1380
+ return { provide: DEFAULT_VALIDATION_MESSAGES, useValue: signal(messages) };
1381
+ }
1382
+
808
1383
  /**
809
1384
  * @ng-forge/dynamic-forms/integration
810
1385
  *
@@ -824,5 +1399,5 @@ function setupMetaTracking(elementRef, meta, options) {
824
1399
  * Generated bundle index. Do not edit.
825
1400
  */
826
1401
 
827
- export { addArrayItemButtonMapper, buildArrayButtonEventContext, buildValueFieldInputs, buttonFieldMapper, checkboxFieldMapper, createResolvedErrorsSignal, datepickerFieldMapper, insertArrayItemButtonMapper, nextButtonFieldMapper, optionsFieldMapper, popArrayItemButtonMapper, prependArrayItemButtonMapper, previousButtonFieldMapper, removeArrayItemButtonMapper, resolveArrayButtonContext, resolveArrayButtonEventArgs, resolveValueFieldContext, setupMetaTracking, shiftArrayItemButtonMapper, shouldShowErrors, submitButtonFieldMapper, valueFieldMapper };
1402
+ export { NG_FORGE_ACTION_INPUTS, NG_FORGE_FIELD_SHELL_INPUTS, NG_FORGE_VALUE_FIELD_INPUTS, NgForgeAction, NgForgeActionHost, NgForgeControl, NgForgeField, NgForgeFieldHost, NgForgeFieldShell, NgForgeHostControl, addArrayItemButtonMapper, buildArrayButtonEventContext, buildValueFieldInputs, buttonFieldMapper, checkboxFieldMapper, createAriaDescribedBySignal, createNgForgeActionFixture, createNgForgeFieldFixture, createResolvedErrorsSignal, datepickerFieldMapper, injectNgForgeAction, injectNgForgeField, insertArrayItemButtonMapper, nextButtonFieldMapper, optionsFieldMapper, popArrayItemButtonMapper, prependArrayItemButtonMapper, previousButtonFieldMapper, provideTestValidationMessages, removeArrayItemButtonMapper, resolveArrayButtonContext, resolveArrayButtonEventArgs, resolveValueFieldContext, setupMetaTracking, shiftArrayItemButtonMapper, shouldShowErrors, submitButtonFieldMapper, valueFieldMapper };
828
1403
  //# sourceMappingURL=ng-forge-dynamic-forms-integration.mjs.map