ng-form-foundry 0.3.3 → 0.3.4

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
  };
@@ -414,6 +422,10 @@ interface DetailSection {
414
422
  schema: NodeGroup | null;
415
423
  /** The group `schema` binds to (the node's group; for a choice, the choice group). */
416
424
  group: FormGroup | null;
425
+ /** A trailing add-control section of a list / complex map: renders after the last item, with no heading. */
426
+ footer?: boolean;
427
+ /** The section continues a run of same-list items / same-map entries: its heading draws no divider line. */
428
+ continuation?: boolean;
417
429
  }
418
430
  /**
419
431
  * A navigable node in the config tree. Its `id` is the node's stable path from
@@ -486,8 +498,12 @@ declare class ConfigEditorComponent implements OnDestroy {
486
498
  readonly schema: _angular_core.InputSignal<NodeGroup>;
487
499
  /** The schema-built reactive group the editor binds to and mutates. */
488
500
  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>;
501
+ /**
502
+ * Whether fields accept input and structural controls (add/remove/menus)
503
+ * show. Two-way bindable: the root tree row carries a toggle, so the editor
504
+ * can flip it itself and the host observes the change.
505
+ */
506
+ readonly editable: _angular_core.ModelSignal<boolean>;
491
507
  root: TreeNode;
492
508
  selected: TreeNode | null;
493
509
  /** The selected subtree as a flat, breadcrumb-separated section list. */
@@ -522,15 +538,20 @@ declare class ConfigEditorComponent implements OnDestroy {
522
538
  * descendants, so an error anywhere below lights every ancestor row.
523
539
  */
524
540
  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;
541
+ /**
542
+ * Append a new item to a list node's FormArray (up to `maxItems`). A tree-row
543
+ * add selects the new item; a detail-pane add passes `keepSelection` so the
544
+ * current (possibly ancestor) view stays put and the item appears as a new
545
+ * section in it.
546
+ */
547
+ addItem(listNode: TreeNode, keepSelection?: boolean): void;
527
548
  /**
528
549
  * Remove a list item from its FormArray (down to `minItems`). List-item
529
550
  * identity is positional, so expansion state under the list is cleared —
530
551
  * otherwise it would silently migrate to the items that shift into the
531
552
  * removed indexes.
532
553
  */
533
- removeItem(listNode: TreeNode, item: TreeNode): void;
554
+ removeItem(listNode: TreeNode, item: TreeNode, keepSelection?: boolean): void;
534
555
  /** The just-added optional leaf (section path + key) whose field grabs focus when rendered. */
535
556
  protected focusSectionId: string | null;
536
557
  protected focusLeafKey: string | null;
@@ -544,23 +565,40 @@ declare class ConfigEditorComponent implements OnDestroy {
544
565
  activeCaseLabel(node: TreeNode): string | null;
545
566
  /** The display label for a case: the schema's `caseLabels` entry, else the case name. */
546
567
  protected caseLabel(choice: NodeChoice, caseName: string): string;
547
- /** Switch a choice node's active case; the structural sync rebuilds the tree and sections. */
568
+ /**
569
+ * Switch a choice node's active case from its detail selector; the
570
+ * structural sync rebuilds the tree and sections. The tree selection stays
571
+ * where it is: detail edits must never steal it, or a flattened ancestor
572
+ * view would collapse to the choice node mid-edit. Selection moves only
573
+ * through the tree itself.
574
+ */
548
575
  switchTreeCase(node: TreeNode, caseName: string): void;
549
576
  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;
577
+ /**
578
+ * Append a new entry to a complex map node under a generated unique key. A
579
+ * tree-row add selects the new entry; a detail-pane add passes
580
+ * `keepSelection` so the current view stays put (see {@link addItem}).
581
+ */
582
+ addTreeMapEntry(mapNode: TreeNode, keepSelection?: boolean): void;
552
583
  /** Remove a complex map entry (down to `minEntries`). */
553
- removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode): void;
584
+ removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode, keepSelection?: boolean): void;
554
585
  /**
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).
586
+ * Commit a rename-on-blur of a map entry's key; on success the selection is
587
+ * remapped to the entry's new identity when it pointed at (or inside) the
588
+ * entry otherwise it stays put and the fresh key field regains focus
589
+ * (the rename re-renders the section under a new id, destroying the input
590
+ * that held focus).
559
591
  */
560
592
  renameTreeMapEntry(entryNode: TreeNode, rawKey: string): void;
561
593
  /** Move keyboard focus to the selected tree row once the action's re-render settles. */
562
594
  private focusSelectedRow;
563
595
  /** A muted hint for a section whose node currently renders no content of its own. */
596
+ /**
597
+ * Explains a group's `minPresent`/`maxPresent` violation next to the red
598
+ * tree marker: the fix is enabling or removing optional fields, which is not
599
+ * evident from any single field's own error.
600
+ */
601
+ protected presentRangeHint(s: DetailSection): string | null;
564
602
  protected emptySectionHint(s: DetailSection): string | null;
565
603
  /** Whether a list node is at `maxItems` (the add control is hidden). */
566
604
  protected listAtMax(node: TreeNode | undefined): boolean;
@@ -596,6 +634,31 @@ declare class ConfigEditorComponent implements OnDestroy {
596
634
  * chrome — the headings are the boundaries between children.
597
635
  */
598
636
  private buildSections;
637
+ /**
638
+ * Whether a section renders anything beyond its heading: a leaf slice, the
639
+ * chrome of its node kind (case selector, key field, add controls), or a
640
+ * present-children error that needs explaining. Heading-only sections are
641
+ * dropped from the flat list — a divider with nothing under it is clutter.
642
+ */
643
+ private sectionHasContent;
644
+ /** Every expandable node currently expanded — drives the root row's expand/collapse-all toggle. */
645
+ protected allExpanded(): boolean;
646
+ /**
647
+ * Expand every expandable node, or collapse back when all are open. The
648
+ * collapse keeps the root open — a tree reduced to one row reads as empty.
649
+ */
650
+ protected toggleExpandAll(): void;
651
+ /** The selected member's container (list / map), for the breadcrumb's remove control. */
652
+ protected selectedMemberParent(): TreeNode | null;
653
+ /** The section node's container (list / map), for member controls in its heading. */
654
+ protected memberParent(s: DetailSection): TreeNode | null;
655
+ /**
656
+ * Flag the sections that continue a run of siblings — a list item or map
657
+ * entry whose preceding section is inside the same container. Their headings
658
+ * draw no divider line, so a run of same-kind members reads as one list
659
+ * rather than a stack of separated blocks.
660
+ */
661
+ private markContinuations;
599
662
  /** A section's own renderable fields: a leaf-only schema slice and the group it binds to. */
600
663
  private sectionContent;
601
664
  /**
@@ -620,7 +683,7 @@ declare class ConfigEditorComponent implements OnDestroy {
620
683
  */
621
684
  private escapeSeg;
622
685
  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>;
686
+ 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
687
  }
625
688
 
626
689
  declare class LeafRendererComponent implements AfterViewInit {
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.4",
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",