ng-form-foundry 0.3.3 → 0.3.5

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
@@ -114,6 +114,14 @@ type NodeGroup = {
114
114
  */
115
115
  presence?: boolean;
116
116
  description?: string;
117
+ /**
118
+ * Minimum / maximum number of keys present in the group's value (JSON Schema
119
+ * `minProperties`/`maxProperties` on a closed object). Meaningful when
120
+ * children are presence-optional: the group carries a `minPresent` /
121
+ * `maxPresent` error while the count of enabled children is out of range.
122
+ */
123
+ minPresent?: number;
124
+ maxPresent?: number;
117
125
  children: Record<string, NodeType>;
118
126
  appearance?: Appearance;
119
127
  };
@@ -145,6 +153,8 @@ type NodeChoice = {
145
153
  /**
146
154
  * Display labels for cases, keyed by case name — for anonymous/auto-named
147
155
  * branches whose keys are not human-friendly. Falls back to the case name.
156
+ * Selectors render these through `caseDisplayLabels`, which disambiguates
157
+ * colliding entries by each case's distinguishing fields.
148
158
  */
149
159
  caseLabels?: Record<string, string>;
150
160
  default?: string;
@@ -204,6 +214,19 @@ type DFormGroup<T extends NodeGroup> = FormGroup<FormGroupType<T>>;
204
214
  * clobber the active-case control.
205
215
  */
206
216
  declare function caseFields(body: ChoiceCase): Record<string, NodeType>;
217
+ /**
218
+ * Display labels for a choice's cases, keyed by case name, with colliding
219
+ * labels made unique. Schema-supplied `caseLabels` can repeat (e.g. two
220
+ * O-RAN A1 scope branches labeled from the same discriminating field), which
221
+ * makes the case selector ambiguous. Unique labels pass through untouched;
222
+ * each colliding case first gains the fields that set it apart from its
223
+ * same-labeled peers — "UE ID (Group ID)" vs "UE ID (Slice ID)" — and any
224
+ * cases the field suffix cannot separate (no distinguishing fields, peers
225
+ * with identical field sets among a larger clash group, or distinguishing
226
+ * fields that share one display label) fall back to their case name, which
227
+ * is unique by construction.
228
+ */
229
+ declare function caseDisplayLabels(choice: NodeChoice): Record<string, string>;
207
230
  /**
208
231
  * The active case of a choice: an explicit `__case` in the initial value, else
209
232
  * the case {@link inferChoiceCase} ranks best against the initial data (inline
@@ -368,7 +391,11 @@ declare class DynamicRecursiveFormComponent implements OnInit {
368
391
  protected activeCase(key: string): string | null;
369
392
  /** A synthetic flattened NodeGroup used to render the active case's fields against the choice's FormGroup. */
370
393
  protected caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
371
- /** The display label for a case: the schema's `caseLabels` entry, else the case name. */
394
+ /**
395
+ * The display label for a case: the schema's `caseLabels` entry (colliding
396
+ * labels disambiguated by their distinguishing fields — see
397
+ * {@link caseDisplayLabels}), else the case name.
398
+ */
372
399
  caseLabel(choice: NodeChoice, caseName: string): string;
373
400
  /**
374
401
  * A copy of a group flagged to render its fields inline (no inner panel). Used
@@ -414,6 +441,10 @@ interface DetailSection {
414
441
  schema: NodeGroup | null;
415
442
  /** The group `schema` binds to (the node's group; for a choice, the choice group). */
416
443
  group: FormGroup | null;
444
+ /** A trailing add-control section of a list / complex map: renders after the last item, with no heading. */
445
+ footer?: boolean;
446
+ /** The section continues a run of same-list items / same-map entries: its heading draws no divider line. */
447
+ continuation?: boolean;
417
448
  }
418
449
  /**
419
450
  * A navigable node in the config tree. Its `id` is the node's stable path from
@@ -486,8 +517,12 @@ declare class ConfigEditorComponent implements OnDestroy {
486
517
  readonly schema: _angular_core.InputSignal<NodeGroup>;
487
518
  /** The schema-built reactive group the editor binds to and mutates. */
488
519
  readonly formGroup: _angular_core.InputSignal<FormGroup<any>>;
489
- /** Whether fields accept input and structural controls (add/remove/menus) show. */
490
- readonly editable: _angular_core.InputSignal<boolean>;
520
+ /**
521
+ * Whether fields accept input and structural controls (add/remove/menus)
522
+ * show. Two-way bindable: the root tree row carries a toggle, so the editor
523
+ * can flip it itself and the host observes the change.
524
+ */
525
+ readonly editable: _angular_core.ModelSignal<boolean>;
491
526
  root: TreeNode;
492
527
  selected: TreeNode | null;
493
528
  /** The selected subtree as a flat, breadcrumb-separated section list. */
@@ -522,15 +557,20 @@ declare class ConfigEditorComponent implements OnDestroy {
522
557
  * descendants, so an error anywhere below lights every ancestor row.
523
558
  */
524
559
  protected hasError(node: TreeNode): boolean;
525
- /** Append a new item to a list node's FormArray (up to `maxItems`), then select it. */
526
- addItem(listNode: TreeNode): void;
560
+ /**
561
+ * Append a new item to a list node's FormArray (up to `maxItems`). A tree-row
562
+ * add selects the new item; a detail-pane add passes `keepSelection` so the
563
+ * current (possibly ancestor) view stays put and the item appears as a new
564
+ * section in it.
565
+ */
566
+ addItem(listNode: TreeNode, keepSelection?: boolean): void;
527
567
  /**
528
568
  * Remove a list item from its FormArray (down to `minItems`). List-item
529
569
  * identity is positional, so expansion state under the list is cleared —
530
570
  * otherwise it would silently migrate to the items that shift into the
531
571
  * removed indexes.
532
572
  */
533
- removeItem(listNode: TreeNode, item: TreeNode): void;
573
+ removeItem(listNode: TreeNode, item: TreeNode, keepSelection?: boolean): void;
534
574
  /** The just-added optional leaf (section path + key) whose field grabs focus when rendered. */
535
575
  protected focusSectionId: string | null;
536
576
  protected focusLeafKey: string | null;
@@ -542,25 +582,46 @@ declare class ConfigEditorComponent implements OnDestroy {
542
582
  activeCase(node: TreeNode): string | null;
543
583
  /** The display label of a choice node's active case, or null when no case is selected. */
544
584
  activeCaseLabel(node: TreeNode): string | null;
545
- /** The display label for a case: the schema's `caseLabels` entry, else the case name. */
585
+ /**
586
+ * The display label for a case: the schema's `caseLabels` entry (colliding
587
+ * labels disambiguated by their distinguishing fields — see
588
+ * {@link caseDisplayLabels}), else the case name.
589
+ */
546
590
  protected caseLabel(choice: NodeChoice, caseName: string): string;
547
- /** Switch a choice node's active case; the structural sync rebuilds the tree and sections. */
591
+ /**
592
+ * Switch a choice node's active case from its detail selector; the
593
+ * structural sync rebuilds the tree and sections. The tree selection stays
594
+ * where it is: detail edits must never steal it, or a flattened ancestor
595
+ * view would collapse to the choice node mid-edit. Selection moves only
596
+ * through the tree itself.
597
+ */
548
598
  switchTreeCase(node: TreeNode, caseName: string): void;
549
599
  protected objectKeys(obj: Record<string, unknown>): string[];
550
- /** Append a new entry to a complex map node under a generated unique key, then select it. */
551
- addTreeMapEntry(mapNode: TreeNode): void;
600
+ /**
601
+ * Append a new entry to a complex map node under a generated unique key. A
602
+ * tree-row add selects the new entry; a detail-pane add passes
603
+ * `keepSelection` so the current view stays put (see {@link addItem}).
604
+ */
605
+ addTreeMapEntry(mapNode: TreeNode, keepSelection?: boolean): void;
552
606
  /** Remove a complex map entry (down to `minEntries`). */
553
- removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode): void;
607
+ removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode, keepSelection?: boolean): void;
554
608
  /**
555
- * Commit a rename-on-blur of a map entry's key; on success the entry is
556
- * selected under its new path and its fresh key field regains focus (the
557
- * rename re-renders the section under a new id, destroying the input that
558
- * held focus).
609
+ * Commit a rename-on-blur of a map entry's key; on success the selection is
610
+ * remapped to the entry's new identity when it pointed at (or inside) the
611
+ * entry otherwise it stays put and the fresh key field regains focus
612
+ * (the rename re-renders the section under a new id, destroying the input
613
+ * that held focus).
559
614
  */
560
615
  renameTreeMapEntry(entryNode: TreeNode, rawKey: string): void;
561
616
  /** Move keyboard focus to the selected tree row once the action's re-render settles. */
562
617
  private focusSelectedRow;
563
618
  /** A muted hint for a section whose node currently renders no content of its own. */
619
+ /**
620
+ * Explains a group's `minPresent`/`maxPresent` violation next to the red
621
+ * tree marker: the fix is enabling or removing optional fields, which is not
622
+ * evident from any single field's own error.
623
+ */
624
+ protected presentRangeHint(s: DetailSection): string | null;
564
625
  protected emptySectionHint(s: DetailSection): string | null;
565
626
  /** Whether a list node is at `maxItems` (the add control is hidden). */
566
627
  protected listAtMax(node: TreeNode | undefined): boolean;
@@ -596,6 +657,31 @@ declare class ConfigEditorComponent implements OnDestroy {
596
657
  * chrome — the headings are the boundaries between children.
597
658
  */
598
659
  private buildSections;
660
+ /**
661
+ * Whether a section renders anything beyond its heading: a leaf slice, the
662
+ * chrome of its node kind (case selector, key field, add controls), or a
663
+ * present-children error that needs explaining. Heading-only sections are
664
+ * dropped from the flat list — a divider with nothing under it is clutter.
665
+ */
666
+ private sectionHasContent;
667
+ /** Every expandable node currently expanded — drives the root row's expand/collapse-all toggle. */
668
+ protected allExpanded(): boolean;
669
+ /**
670
+ * Expand every expandable node, or collapse back when all are open. The
671
+ * collapse keeps the root open — a tree reduced to one row reads as empty.
672
+ */
673
+ protected toggleExpandAll(): void;
674
+ /** The selected member's container (list / map), for the breadcrumb's remove control. */
675
+ protected selectedMemberParent(): TreeNode | null;
676
+ /** The section node's container (list / map), for member controls in its heading. */
677
+ protected memberParent(s: DetailSection): TreeNode | null;
678
+ /**
679
+ * Flag the sections that continue a run of siblings — a list item or map
680
+ * entry whose preceding section is inside the same container. Their headings
681
+ * draw no divider line, so a run of same-kind members reads as one list
682
+ * rather than a stack of separated blocks.
683
+ */
684
+ private markContinuations;
599
685
  /** A section's own renderable fields: a leaf-only schema slice and the group it binds to. */
600
686
  private sectionContent;
601
687
  /**
@@ -620,7 +706,7 @@ declare class ConfigEditorComponent implements OnDestroy {
620
706
  */
621
707
  private escapeSeg;
622
708
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ConfigEditorComponent, never>;
623
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ConfigEditorComponent, "nff-config-editor", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": true; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
709
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ConfigEditorComponent, "nff-config-editor", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": true; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; }, { "editable": "editableChange"; }, never, never, true, never>;
624
710
  }
625
711
 
626
712
  declare class LeafRendererComponent implements AfterViewInit {
@@ -737,5 +823,5 @@ declare class NodeMapRendererComponent implements OnChanges {
737
823
  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; }; }, {}, never, never, true, never>;
738
824
  }
739
825
 
740
- export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
826
+ export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
741
827
  export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
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",