ng-form-foundry 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -152,6 +152,14 @@ type LeafList<TKind extends Leaf['type'] = Leaf['type']> = {
152
152
  maxItems?: number;
153
153
  /** Present every item in this base — see {@link LeafNumber.radix}. */
154
154
  radix?: 2 | 8 | 16;
155
+ /**
156
+ * Optional scalar list whose *presence itself* is data — an absent key,
157
+ * distinct from a present-but-empty list. Off means no control and no key in
158
+ * the value; on means the (possibly empty) list is materialized. Mirrors
159
+ * {@link NodeGroup.presence}; lets a round-trip distinguish `x = ( )` (present,
160
+ * empty) from an absent `x`.
161
+ */
162
+ presence?: boolean;
155
163
  };
156
164
  type NodeGroupList = {
157
165
  kind: 'nodeGroupList';
@@ -161,6 +169,8 @@ type NodeGroupList = {
161
169
  type: NodeGroup;
162
170
  minItems?: number;
163
171
  maxItems?: number;
172
+ /** Optional list whose presence is itself data — see {@link LeafList.presence}. */
173
+ presence?: boolean;
164
174
  };
165
175
  type NodeGroup = {
166
176
  kind: 'nodeGroup';
@@ -327,6 +337,24 @@ declare function addMapEntry(group: FormGroup, map: NodeMap, key?: string): stri
327
337
  declare function renameMapEntry(group: FormGroup, map: NodeMap, oldKey: string, newKey: string): boolean;
328
338
  /** Remove entry `key` unless the map is at `minEntries`. Returns whether it was removed. */
329
339
  declare function removeMapEntry(group: FormGroup, map: NodeMap, key: string): boolean;
340
+ /**
341
+ * Materialize or de-materialize an optional (presence) child of `group`,
342
+ * mirroring the presence toggle the form UI offers — but callable by any host
343
+ * holding the {@link FormGroup}, for a key at any depth. `present: true` builds
344
+ * the child fresh from `schema` (seeded with `initial` if given; nested
345
+ * presence descendants start absent, as {@link buildControl}); `present: false`
346
+ * removes it, dropping the key from the form value.
347
+ *
348
+ * The materialize direction is why the coupling exists: a materialized
349
+ * non-nullable presence leaf carries `Validators.required`, because an
350
+ * enabled-but-empty key would serialize as `null` and fail a typed schema. A
351
+ * host that materializes fields for editing therefore drops the ones left
352
+ * empty on cancel by de-materializing them — this API is that primitive.
353
+ *
354
+ * `schema` must be the presence node for `key`; a non-presence `schema` is a
355
+ * no-op. Returns whether the form changed.
356
+ */
357
+ declare function setNodePresence(group: FormGroup, schema: NodeType, key: string, present: boolean, initial?: unknown): boolean;
330
358
  /**
331
359
  * Build the `AbstractControl` for a single schema node.
332
360
  *
@@ -525,7 +553,8 @@ declare class DynamicRecursiveFormComponent implements OnInit {
525
553
  * Add or remove a presence node's control on this form — any presence kind:
526
554
  * group, leaf, map, or choice. Removing it drops the key from `form.value`;
527
555
  * adding it builds the control fresh from its schema (nested presence
528
- * children start absent).
556
+ * children start absent). Delegates to {@link setNodePresence}, the
557
+ * host-callable primitive.
529
558
  */
530
559
  toggleNodePresence(key: string, schema: NodeType, present: boolean): void;
531
560
  /**
@@ -549,6 +578,14 @@ declare class DynamicRecursiveFormComponent implements OnInit {
549
578
  * rendered field when the toggle just created it.
550
579
  */
551
580
  toggleLeafPresence(key: string, schema: Leaf, present: boolean): void;
581
+ /**
582
+ * Materialize an absent presence (optional / `advisoryRequired`) list *with
583
+ * its first entry* — clicking "Add <list>" makes the first entry appear at
584
+ * once. An optional list has no present-empty state: removing the last entry
585
+ * de-materializes it (→ absent). A required list is always present instead and
586
+ * uses the renderer's own "Add item" affordance, so it never routes here.
587
+ */
588
+ addPresenceList(key: string, schema: NodeType): void;
552
589
  protected objectKeys(obj: Record<string, unknown>): string[];
553
590
  /** The active case name of a choice control, or null if none is selected. */
554
591
  protected activeCase(key: string): string | null;
@@ -638,6 +675,7 @@ interface TreeNode {
638
675
  /** Present on a present optional child node: drives the row's remove control. */
639
676
  presenceRemovable?: {
640
677
  key: string;
678
+ schema: NodeType;
641
679
  };
642
680
  /** Field-layout appearance inherited from ancestor groups, for the node's detail-section form. */
643
681
  inherited?: Appearance | null;
@@ -724,6 +762,7 @@ declare class ConfigEditorComponent implements OnDestroy {
724
762
  private shape;
725
763
  private changes?;
726
764
  private readonly host;
765
+ private readonly cdr;
727
766
  /** Stable per-instance ids for the shape signature, so replacing a control (setControl) reads as a structural change. */
728
767
  private readonly controlIds;
729
768
  private controlIdSeq;
@@ -731,6 +770,21 @@ declare class ConfigEditorComponent implements OnDestroy {
731
770
  ngOnDestroy(): void;
732
771
  /** Bind the editor to a schema/form pair: fresh tree, root selection, and shape-sync subscription. */
733
772
  private attach;
773
+ /**
774
+ * Force the editor to re-read the bound form. A leaf field normally reflects
775
+ * an external `setValue` on its own — the reactive value accessor writes the
776
+ * value synchronously, no change detection needed — so this is only for the
777
+ * corners that bypass that: a structural change made with
778
+ * `{ emitEvent: false }` (the tree wouldn't otherwise rebuild), a non-reactive
779
+ * display (the case selector, a map key) that needs re-reading, or a host
780
+ * whose change detector was detached during the mutation.
781
+ *
782
+ * It re-syncs the tree structure and **tears down and recreates** the detail
783
+ * sections, so every rendered control rebinds to the live form and re-pulls
784
+ * its current value — even a field whose bound input somehow missed the
785
+ * external write.
786
+ */
787
+ refresh(): void;
734
788
  select(node: TreeNode, reveal?: boolean): void;
735
789
  /**
736
790
  * Root-to-`target` path for the detail-pane breadcrumb (inclusive of both ends),
@@ -940,6 +994,7 @@ declare class LeafListRendererComponent implements OnInit {
940
994
  formArray: any;
941
995
  editable: boolean;
942
996
  minItems: number;
997
+ maxItems: number;
943
998
  /**
944
999
  * The parent group's grid layout (its `fieldsLayout` styles). A stacked list
945
1000
  * spans the parent's full row, so its entries repeat the same tracks and
@@ -948,6 +1003,12 @@ declare class LeafListRendererComponent implements OnInit {
948
1003
  */
949
1004
  layout: LayoutStyles | null;
950
1005
  message: EventEmitter<any>;
1006
+ /**
1007
+ * Emitted from the empty-list remove button when the list is a presence list —
1008
+ * the host removes the whole list (de-materializes it, → absent). Only wired
1009
+ * for a `presence` `leafList`; the parent maps it to `setNodePresence`.
1010
+ */
1011
+ removeList: EventEmitter<void>;
951
1012
  matDialog: MatDialog;
952
1013
  /**
953
1014
  * Take a full-width row of its own once the array holds more than one entry.
@@ -961,10 +1022,14 @@ declare class LeafListRendererComponent implements OnInit {
961
1022
  /** Applies {@link layout} to the host (the entries' container) while stacked. */
962
1023
  get hostLayout(): LayoutStyles | null;
963
1024
  ngOnInit(): void;
1025
+ get effectiveMin(): number;
1026
+ get effectiveMax(): number;
1027
+ /** Whether another item may be appended (below the effective maximum). */
1028
+ get canAdd(): boolean;
964
1029
  removeItem($index: number): void;
965
1030
  addItem(): void;
966
1031
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafListRendererComponent, never>;
967
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<LeafListRendererComponent, "nff-leaf-list-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
1032
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<LeafListRendererComponent, "nff-leaf-list-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; }, { "message": "message"; "removeList": "removeList"; }, never, never, true, never>;
968
1033
  }
969
1034
 
970
1035
  declare class LeafEnumRendererComponent {
@@ -1053,17 +1118,27 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
1053
1118
  /** Field-layout appearance from the enclosing group, forwarded to every item form. */
1054
1119
  inheritedAppearance: Appearance | null;
1055
1120
  message: EventEmitter<any>;
1121
+ /**
1122
+ * Emitted when the last entry of a presence (optional / advisoryRequired) list
1123
+ * is removed — the host de-materializes the whole list (→ absent). An optional
1124
+ * list has no present-empty state; a required list never routes here.
1125
+ */
1126
+ removeList: EventEmitter<void>;
1056
1127
  items: QueryList<DynamicRecursiveFormComponent>;
1057
1128
  cdr: ChangeDetectorRef;
1058
1129
  ngOnInit(): void;
1130
+ get effectiveMin(): number;
1131
+ get effectiveMax(): number;
1132
+ /** Whether another item may be appended (below the effective maximum). */
1133
+ get canAdd(): boolean;
1059
1134
  ngAfterViewInit(): void;
1060
1135
  removeItem($index: number): void;
1061
- addItem: (index: number) => void;
1136
+ addItem: (index?: number) => void;
1062
1137
  setLastEditable(): void;
1063
1138
  asFormGroup(group: any): FormGroup;
1064
1139
  getTitle($index: number): string;
1065
1140
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeGroupListRendererComponent, never>;
1066
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<NodeGroupListRendererComponent, "nff-node-group-list-renderer", never, { "nodeGroupList": { "alias": "nodeGroupList"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
1141
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NodeGroupListRendererComponent, "nff-node-group-list-renderer", never, { "nodeGroupList": { "alias": "nodeGroupList"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, { "message": "message"; "removeList": "removeList"; }, never, never, true, never>;
1067
1142
  }
1068
1143
 
1069
1144
  /**
@@ -1094,6 +1169,10 @@ declare class NodeMapRendererComponent implements OnChanges {
1094
1169
  get valueLeaf(): Leaf;
1095
1170
  /** The value schema as a group (used when `value.kind === 'nodeGroup'`). */
1096
1171
  get valueGroup(): NodeGroup;
1172
+ /** Narrowing casts for the complex value kinds the template renders. */
1173
+ protected asLeafList(v: unknown): LeafList;
1174
+ protected asNodeGroupList(v: unknown): NodeGroupList;
1175
+ protected asNodeMap(v: unknown): NodeMap;
1097
1176
  get atMax(): boolean;
1098
1177
  get atMin(): boolean;
1099
1178
  /** Append a new entry under a unique placeholder key. Delegates to {@link addMapEntry}. */
@@ -1108,9 +1187,10 @@ declare class NodeMapRendererComponent implements OnChanges {
1108
1187
  renameEntry(oldKey: string, rawKey: string): void;
1109
1188
  protected readonly asFormControl: typeof asFormControl;
1110
1189
  protected readonly asFormGroup: typeof asFormGroup;
1190
+ protected readonly asFormArray: typeof asFormArray;
1111
1191
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
1112
1192
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<NodeMapRendererComponent, "nff-node-map-renderer", never, { "nodeMap": { "alias": "nodeMap"; "required": false; }; "formGroup": { "alias": "formGroup"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, {}, never, never, true, never>;
1113
1193
  }
1114
1194
 
1115
- export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, RadixInputDirective, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, formatRadix, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
1195
+ export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, RadixInputDirective, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, formatRadix, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, setNodePresence, switchChoiceCase, toWireValue };
1116
1196
  export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType, Radix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "Build fully-typed Angular Reactive Forms and Angular Material UI from a declarative, recursive form-description schema.",
5
5
  "keywords": [
6
6
  "angular",