ng-form-foundry 0.3.5 → 0.4.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
@@ -79,6 +79,51 @@ type Appearance = {
79
79
  noBorder?: boolean;
80
80
  /** Start this node's section panel collapsed. Ignored when `flatten` is set. */
81
81
  collapsed?: boolean;
82
+ /**
83
+ * Fixed grid for the node's scalar fields: `cols` fields per row, filling
84
+ * left-to-right; `rows` alone fills top-to-bottom into that many rows,
85
+ * adding columns as needed. Overrides {@link minFieldWidth}.
86
+ */
87
+ grid?: {
88
+ rows?: number;
89
+ cols?: number;
90
+ };
91
+ /**
92
+ * Narrowest a scalar field may get (a CSS length, e.g. `'12rem'`): each row
93
+ * fits as many equal-width fields as stay at least this wide and wraps the
94
+ * rest. Ignored when {@link grid} is set. With neither option the fields
95
+ * share one wrapping row, shrinking down to 10% of it.
96
+ */
97
+ minFieldWidth?: string;
98
+ /**
99
+ * Where boolean (checkbox-rendered) fields go. A checkbox doesn't need a
100
+ * field-sized slot — in a {@link grid} it would claim a whole track —
101
+ * so `'beginning'`/`'end'` gathers them into a compact wrapping row of
102
+ * natural-width items before/after the node's other fields. `'default'`
103
+ * (or unset) keeps them in declaration order within the field flow.
104
+ */
105
+ booleanFields?: 'beginning' | 'end' | 'default';
106
+ /**
107
+ * Narrowest a **text (string) field** may get in the flex flow (a CSS
108
+ * length): the row wraps rather than shrink such a field further. Enum
109
+ * fields (rendered as a select) are text-like and follow it too, as do a
110
+ * string/enum leaf-list's entries. No effect under {@link grid} /
111
+ * {@link minFieldWidth}, whose tracks size every field alike.
112
+ */
113
+ minTextFieldWidth?: string;
114
+ /**
115
+ * Narrowest a **number field** may get in the flex flow (a CSS length) —
116
+ * the numeric counterpart of {@link minTextFieldWidth}, typically smaller.
117
+ * Also bounds a number leaf-list's entries.
118
+ */
119
+ minNumberFieldWidth?: string;
120
+ /**
121
+ * Widest a **number field** may grow in the flex flow (a CSS length):
122
+ * numbers are short, so capping them keeps a lone number from stretching
123
+ * across space a text field could use. Also caps a number leaf-list's
124
+ * entries. No effect under {@link grid} / {@link minFieldWidth}.
125
+ */
126
+ maxNumberFieldWidth?: string;
82
127
  };
83
128
  type Leaf = LeafString | LeafNumber | LeafBoolean | LeafEnum;
84
129
  type LeafList<TKind extends Leaf['type'] = Leaf['type']> = {
@@ -335,6 +380,14 @@ declare function serializeForm(schema: NodeGroup, form: FormGroup): Record<strin
335
380
  */
336
381
  declare function defineSchema<const S extends NodeGroup>(schema: S): S;
337
382
 
383
+ /**
384
+ * Inline CSS declarations (property → value) for a node's `.fields` area,
385
+ * derived from its {@link Appearance} (see the form component's
386
+ * `fieldsLayout`) and bound via `[style]`. Also handed to a stacked
387
+ * leaf-list's `layout` input so its entries repeat the same grid tracks.
388
+ */
389
+ type LayoutStyles = Record<string, string>;
390
+
338
391
  declare function asFormControl(control: any): FormControl;
339
392
  declare function asFormArray(control: any): FormArray;
340
393
  declare function asFormGroup(control: any): FormGroup;
@@ -364,8 +417,67 @@ declare class DynamicRecursiveFormComponent implements OnInit {
364
417
  * menu) hand focus to the new field, like the form's own add button does.
365
418
  */
366
419
  readonly focusLeaf: _angular_core.InputSignal<string | null>;
420
+ /**
421
+ * Field-layout appearance cascading down from the parent group. The node's
422
+ * own `appearance` wins per property — see {@link mergeAppearance}.
423
+ */
424
+ readonly inheritedAppearance: _angular_core.InputSignal<Appearance | null>;
367
425
  /** True when the schema is a root group (rendered flat, without a wrapping card). */
368
426
  readonly root: _angular_core.Signal<boolean>;
427
+ /** The node's own `appearance` with inherited layout gaps filled in. */
428
+ protected readonly effectiveAppearance: _angular_core.Signal<Appearance | undefined>;
429
+ /** The layout subset this node's children inherit. */
430
+ protected readonly childAppearance: _angular_core.Signal<Appearance | null>;
431
+ /**
432
+ * The per-type width bounds are flex-flow-only, and their CSS custom
433
+ * properties inherit into leaf-list internals that the grid-mode resets
434
+ * cannot reach across the encapsulation boundary — so under a grid layout
435
+ * the variables are simply not set at all.
436
+ */
437
+ private flexOnly;
438
+ /** `minTextFieldWidth` for the `.fields` CSS custom property, or null. */
439
+ protected readonly textFieldMin: _angular_core.Signal<string | null>;
440
+ /** `minNumberFieldWidth` for the `.fields` CSS custom property, or null. */
441
+ protected readonly numberFieldMin: _angular_core.Signal<string | null>;
442
+ /** `maxNumberFieldWidth` for the `.fields` CSS custom property, or null. */
443
+ protected readonly numberFieldMax: _angular_core.Signal<string | null>;
444
+ /**
445
+ * True when the active grid defines explicit column tracks. Only then can a
446
+ * stacked leaf-list meaningfully span the row (`grid-column: 1 / -1`
447
+ * resolves `-1` to line 1 without an explicit column template) and repeat
448
+ * the tracks for its entries; in a rows-only grid the list stays a normal
449
+ * auto-flowed item.
450
+ */
451
+ protected readonly gridHasCols: _angular_core.Signal<boolean>;
452
+ /**
453
+ * Inline grid styles for the `.fields` area, from `appearance`: `grid`
454
+ * becomes explicit tracks (`cols` fields per row; `rows` alone fills
455
+ * top-to-bottom, adding columns as needed) and `minFieldWidth` becomes
456
+ * as-many-as-fit equal columns of at least that width. `grid` wins over
457
+ * `minFieldWidth`; with neither, `null` keeps the stylesheet's wrapping
458
+ * flex flow. Non-positive `rows`/`cols` are ignored.
459
+ */
460
+ protected readonly fieldsLayout: _angular_core.Signal<LayoutStyles | null>;
461
+ /**
462
+ * Whether the gathered boolean row would show anything: a plain boolean, an
463
+ * enabled presence boolean, or (while editable) an absent one's add button.
464
+ * Keeps a read-only form from rendering an empty `.boolean-fields` div,
465
+ * which would add a stray container gap.
466
+ */
467
+ protected booleanAreaVisible(): boolean;
468
+ /**
469
+ * Whether a leaf renders in the regular `.fields` flow: always, unless it is
470
+ * a boolean that {@link booleanPlacement} gathers into the `.boolean-fields`
471
+ * row instead.
472
+ */
473
+ protected inFieldFlow(child: Leaf): boolean;
474
+ /**
475
+ * Where `appearance.booleanFields` places the checkbox fields: `'beginning'`
476
+ * or `'end'` renders them grouped in the `.boolean-fields` row and excludes
477
+ * them from the regular field flow; `'default'` (also when the group has no
478
+ * boolean leaf to move) leaves the flow untouched.
479
+ */
480
+ protected readonly booleanPlacement: _angular_core.Signal<"beginning" | "end" | "default">;
369
481
  ngOnInit(): void;
370
482
  protected get nodeGroupChildrenList(): Array<{
371
483
  key: string;
@@ -389,7 +501,11 @@ declare class DynamicRecursiveFormComponent implements OnInit {
389
501
  protected objectKeys(obj: Record<string, unknown>): string[];
390
502
  /** The active case name of a choice control, or null if none is selected. */
391
503
  protected activeCase(key: string): string | null;
392
- /** A synthetic flattened NodeGroup used to render the active case's fields against the choice's FormGroup. */
504
+ /**
505
+ * A synthetic flattened NodeGroup used to render the active case's fields
506
+ * against the choice's FormGroup. Carries the choice's own `appearance`
507
+ * (grid / field-width layout) onto the case fields.
508
+ */
393
509
  protected caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
394
510
  /**
395
511
  * The display label for a case: the schema's `caseLabels` entry (colliding
@@ -409,7 +525,7 @@ declare class DynamicRecursiveFormComponent implements OnInit {
409
525
  protected readonly asFormArray: typeof asFormArray;
410
526
  protected readonly asFormControl: typeof asFormControl;
411
527
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicRecursiveFormComponent, never>;
412
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicRecursiveFormComponent, "nff-dynamic-recursive-form", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "initialValue": { "alias": "initialValue"; "required": false; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; "addButtonCallback": { "alias": "addButtonCallback"; "required": false; "isSignal": true; }; "focusLeaf": { "alias": "focusLeaf"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
528
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicRecursiveFormComponent, "nff-dynamic-recursive-form", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "initialValue": { "alias": "initialValue"; "required": false; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; "addButtonCallback": { "alias": "addButtonCallback"; "required": false; "isSignal": true; }; "focusLeaf": { "alias": "focusLeaf"; "required": false; "isSignal": true; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
413
529
  }
414
530
 
415
531
  /** Metadata on a list-container node that lets the tree add items to its FormArray. */
@@ -472,6 +588,8 @@ interface TreeNode {
472
588
  presenceRemovable?: {
473
589
  key: string;
474
590
  };
591
+ /** Field-layout appearance inherited from ancestor groups, for the node's detail-section form. */
592
+ inherited?: Appearance | null;
475
593
  /** Present on a choice node: the choice schema and its FormGroup (holding `__case` + the active case's fields). */
476
594
  choice?: {
477
595
  schema: NodeChoice;
@@ -523,6 +641,12 @@ declare class ConfigEditorComponent implements OnDestroy {
523
641
  * can flip it itself and the host observes the change.
524
642
  */
525
643
  readonly editable: _angular_core.ModelSignal<boolean>;
644
+ /**
645
+ * Title for the root tree row and breadcrumb origin, overriding the schema's
646
+ * own `label`/`name`. Read when the tree is (re)built, so a later change
647
+ * shows on the next rebind or structural rebuild, not immediately.
648
+ */
649
+ readonly rootTitle: _angular_core.InputSignal<string | undefined>;
526
650
  root: TreeNode;
527
651
  selected: TreeNode | null;
528
652
  /** The selected subtree as a flat, breadcrumb-separated section list. */
@@ -643,6 +767,8 @@ declare class ConfigEditorComponent implements OnDestroy {
643
767
  private shapeOf;
644
768
  /** The instance id a container contributes to the shape signature. */
645
769
  private uidOf;
770
+ /** Root row title: host override, else schema label, else its name — with the {@link ROOT_SENTINEL} swapped for a generic title. */
771
+ private rootLabelOf;
646
772
  /** Rebuild the whole tree from schema + form and refresh the shape signature. */
647
773
  private rebuild;
648
774
  /** Re-point `selected` at the rebuilt tree: same path, else the closest surviving ancestor. */
@@ -687,13 +813,19 @@ declare class ConfigEditorComponent implements OnDestroy {
687
813
  /**
688
814
  * The node's own fields as a flattened schema: leaf and leafList children
689
815
  * only. Complex children are rendered as sections of their own, so the
690
- * embedded form never draws nested section chrome. Null when there are none.
816
+ * embedded form never draws nested section chrome. The node's `appearance`
817
+ * (grid / field-width layout) carries into the slice. Null when there are
818
+ * no own fields.
691
819
  */
692
820
  private leafOnly;
693
821
  private buildTree;
694
822
  /** Build the tree node for a single non-leaf child, dispatching on its kind. Null when its control is missing. */
695
823
  private buildChildNode;
696
- /** Synthetic group over a case's normalized fields, so a case body builds like any group. */
824
+ /**
825
+ * Synthetic group over a case's normalized fields, so a case body builds
826
+ * like any group. Carries the choice's own `appearance` so its layout
827
+ * reaches the case fields.
828
+ */
697
829
  private caseAsGroup;
698
830
  /** A node's display label: its schema `label`, else its record key. */
699
831
  private labelOf;
@@ -706,7 +838,7 @@ declare class ConfigEditorComponent implements OnDestroy {
706
838
  */
707
839
  private escapeSeg;
708
840
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ConfigEditorComponent, 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>;
841
+ 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; }; "rootTitle": { "alias": "rootTitle"; "required": false; "isSignal": true; }; }, { "editable": "editableChange"; }, never, never, true, never>;
710
842
  }
711
843
 
712
844
  declare class LeafRendererComponent implements AfterViewInit {
@@ -739,6 +871,13 @@ declare class LeafListRendererComponent implements OnInit {
739
871
  formArray: any;
740
872
  editable: boolean;
741
873
  minItems: number;
874
+ /**
875
+ * The parent group's grid layout (its `fieldsLayout` styles). A stacked list
876
+ * spans the parent's full row, so its entries repeat the same tracks and
877
+ * stay aligned with the columns above; an inline (single-entry) list sits in
878
+ * one track and ignores it.
879
+ */
880
+ layout: LayoutStyles | null;
742
881
  message: EventEmitter<any>;
743
882
  matDialog: MatDialog;
744
883
  /**
@@ -748,11 +887,15 @@ declare class LeafListRendererComponent implements OnInit {
748
887
  * cannot. Consumed by the `:host(.stacked)` rule.
749
888
  */
750
889
  get stacked(): boolean;
890
+ /** The entries' value type as a host class, so type-scoped min-widths (`--nff-min-*-field-width`) can target them. */
891
+ get typeClass(): string;
892
+ /** Applies {@link layout} to the host (the entries' container) while stacked. */
893
+ get hostLayout(): LayoutStyles | null;
751
894
  ngOnInit(): void;
752
895
  removeItem($index: number): void;
753
896
  addItem(): void;
754
897
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafListRendererComponent, never>;
755
- 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; }; }, { "message": "message"; }, never, never, true, never>;
898
+ 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>;
756
899
  }
757
900
 
758
901
  declare class LeafEnumRendererComponent {
@@ -773,6 +916,8 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
773
916
  editable: boolean;
774
917
  minItems: number;
775
918
  maxItems: number;
919
+ /** Field-layout appearance from the enclosing group, forwarded to every item form. */
920
+ inheritedAppearance: Appearance | null;
776
921
  message: EventEmitter<any>;
777
922
  items: QueryList<DynamicRecursiveFormComponent>;
778
923
  cdr: ChangeDetectorRef;
@@ -784,7 +929,7 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
784
929
  asFormGroup(group: any): FormGroup;
785
930
  getTitle($index: number): string;
786
931
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeGroupListRendererComponent, never>;
787
- 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; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
932
+ 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; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
788
933
  }
789
934
 
790
935
  /**
@@ -798,9 +943,17 @@ declare class NodeMapRendererComponent implements OnChanges {
798
943
  nodeMap: NodeMap;
799
944
  formGroup: FormGroup<any>;
800
945
  editable: boolean;
946
+ /** Field-layout appearance from the enclosing group, forwarded to group-valued entry forms. */
947
+ inheritedAppearance: Appearance | null;
801
948
  /** Ordered view of entry keys, kept stable across renames (`addControl` appends). */
802
949
  entryKeys: string[];
803
950
  ngOnChanges(changes: SimpleChanges): void;
951
+ /**
952
+ * What entry forms inherit: the enclosing group's layout with the map's own
953
+ * `appearance` merged over it. Counterpart of the config editor's map-entry
954
+ * threading in `buildChildNode`.
955
+ */
956
+ get entryAppearance(): Appearance | null;
804
957
  /** The value schema as a leaf (used when `value.kind === 'leaf'`). */
805
958
  get valueLeaf(): Leaf;
806
959
  /** The value schema as a group (used when `value.kind === 'nodeGroup'`). */
@@ -820,7 +973,7 @@ declare class NodeMapRendererComponent implements OnChanges {
820
973
  protected readonly asFormControl: typeof asFormControl;
821
974
  protected readonly asFormGroup: typeof asFormGroup;
822
975
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
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>;
976
+ 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; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, {}, never, never, true, never>;
824
977
  }
825
978
 
826
979
  export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry",
3
- "version": "0.3.5",
3
+ "version": "0.4.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",